值类型和引用类型
工作两年多了,虽然一直用.NET,却一直没有系统的看过C#方面的专业书籍,用到哪里就看到哪里,有时候觉得很乱,不了解C#里面到底有哪些内容,回答别人的问题时,也不是很有信心,就决定把.NET的知识系统的看一下,顺便把基础和重要的知识记下来。
值类型和引用类型是.NET中最基本的知识,虽然是最基本的,但在最近看书的过程中,才发现自己有很多不了解的。
1) 内存分配上的区别.
值类型分配在栈上的,引用类型分配在托管堆中。
int maxAge; //此时CLR已经在栈上创建了一个为"0"的整型.
IList list; //此时CLR仅仅在栈上创建了一个引用,
//但是并没有指向任何对象实例,如果此时编译是通不过的.
list = new ArrayList();//此时在托管堆上创建了一个ArrayList的实例,
//并且使list指向ArrayList的实例在托管堆中的地址.
IList list; //此时CLR仅仅在栈上创建了一个引用,
//但是并没有指向任何对象实例,如果此时编译是通不过的.
list = new ArrayList();//此时在托管堆上创建了一个ArrayList的实例,
//并且使list指向ArrayList的实例在托管堆中的地址.
2) 作为参数传给方法的区别.
值类型是以拷贝值的方式进行传递。在传参数时,CLR在栈上创建了一个新的和原来一模一样的值类型。
引用类型是拷贝引用的方式进行传递.在传参数时,CLR在栈上创建了一个新的引用,但他和原来的引用指向同一个托管堆中的实例.
int maxAge = 10;
public void changeAge(int maxAge); // 此时是值拷贝
IList list = new ArrayList();
public void AddList(IList list, object obj); // 此时是引用拷贝
public void changeAge(int maxAge); // 此时是值拷贝
IList list = new ArrayList();
public void AddList(IList list, object obj); // 此时是引用拷贝
当然.NET中使用ref, out 关键字也可以示值类型也以引用类型的方式传递参数.
ref 在传递给方法时必须先被初始化.
out 在传递前不必先初始化,但在方法中必须显式的给参数付值.
值类型和引用类型在使用ref(out)作为参数传递时的区别.
int maxAge = 10;
public void changeAge(int valAge); // 此时进行了值拷贝
{
valAge = 20; // 此时maxAge = 10; valAge = 20;
}
public void changeAge(ref int refAge)// 此时创建一个指向maxAge的引用
{
refAge = 20; // 此时maxAge = 20; valAge = 20;
}
IList list = new ArrayList();
public void AddList(IList lst, object obj) // 此时创建一个新的引用lst和list指向同一个实例;
{
lst.Add(obj); // lst和list里有相同的元素.
lst = new ArrayList(); // lst是一个指向新的实例的引用(0个元素),
//list仍然指向原来的实例(一个元素).
}
public void AddList(ref IList lst, object obj) // 此时list将自己传给方法.
{
lst.Add(obj); // lst和list里有相同的元素(一个元素).
lst = new ArrayList(); // lst和list都指向了新的元素(0个元素).
}
public void changeAge(int valAge); // 此时进行了值拷贝
{
valAge = 20; // 此时maxAge = 10; valAge = 20;
}
public void changeAge(ref int refAge)// 此时创建一个指向maxAge的引用
{
refAge = 20; // 此时maxAge = 20; valAge = 20;
}
IList list = new ArrayList();
public void AddList(IList lst, object obj) // 此时创建一个新的引用lst和list指向同一个实例;
{
lst.Add(obj); // lst和list里有相同的元素.
lst = new ArrayList(); // lst是一个指向新的实例的引用(0个元素),
//list仍然指向原来的实例(一个元素).
}
public void AddList(ref IList lst, object obj) // 此时list将自己传给方法.
{
lst.Add(obj); // lst和list里有相同的元素(一个元素).
lst = new ArrayList(); // lst和list都指向了新的元素(0个元素).
}
在使用ref和out的时候,要注意下面的情况:
public class SomwClass
{
public static void Main(string[] args)
{
string str = string.Empty;
Method(str);
Method(ref str); //这行编译是通不过的.无法将ref string转为ref Object.
// 你看这个方法的实现,obj被指向了另一个实例,
//而这个实例类型不一定是string(而是OtherClass),这是非常危险的,
// 所以编译是不会通过的.
}
public static void Method(Object obj)
{
}
public static void Method(ref Object obj)
{
obj = new OtherClass();
}
public static void Method(out Object obj) // 这个方法编译是通不过的,
//因为重载时不能指通过ref和out来区别.
{
}
}
{
public static void Main(string[] args)
{
string str = string.Empty;
Method(str);
Method(ref str); //这行编译是通不过的.无法将ref string转为ref Object.
// 你看这个方法的实现,obj被指向了另一个实例,
//而这个实例类型不一定是string(而是OtherClass),这是非常危险的,
// 所以编译是不会通过的.
}
public static void Method(Object obj)
{
}
public static void Method(ref Object obj)
{
obj = new OtherClass();
}
public static void Method(out Object obj) // 这个方法编译是通不过的,
//因为重载时不能指通过ref和out来区别.
{
}
}
3) 他们之间的转换.
值类型和引用类型之间的转换称之为装箱和拆箱,具体概念就不说了,在性能方面装箱耗费较多的系统资源.
public interface IMulti
{
int Multi();
}
public Struct Point : IMulti
{
public int x;
public int y;
public override ToString()
{
Console.WriteLine(x + " : " + y);
}
public int Multi()
{
return x * y;
}
}
public void static Main(string[] args)
{
Point p = new Point();
p.x = 10;
p.y = 10;
p.ToString(); //此时并没有进行装箱(CLR会自动调用Point的方法).
p.Multi(); //此时并没有进行装箱(CLR会自动调用Point的方法).
Type t = p.GetType(); //此时进行了装箱.因为调用了基类的GetType方法.
IMulti m = (IMulti)p; //此时进行了装箱.因为借口是引用类型.
m.Multi();
}
{
int Multi();
}
public Struct Point : IMulti
{
public int x;
public int y;
public override ToString()
{
Console.WriteLine(x + " : " + y);
}
public int Multi()
{
return x * y;
}
}
public void static Main(string[] args)
{
Point p = new Point();
p.x = 10;
p.y = 10;
p.ToString(); //此时并没有进行装箱(CLR会自动调用Point的方法).
p.Multi(); //此时并没有进行装箱(CLR会自动调用Point的方法).
Type t = p.GetType(); //此时进行了装箱.因为调用了基类的GetType方法.
IMulti m = (IMulti)p; //此时进行了装箱.因为借口是引用类型.
m.Multi();
}
4) 还有和他们有关的,我联想不到了.阁下可以补充.