转自:http://www.cnblogs.com/mgen/archive/2011/05/06/2038339.html
.NET(C#):理解值类型/引用类型,装箱/拆箱,Object类
装箱/拆箱,值类型/引用类型 和 Object类,这些都是.NET程序员人人皆知且人人都应该掌握的概念。大多数人都对他们非常了解,可是和一些同行们交流时我发现一些细节其实很多人并不了解,尤其是它们结合讨论的情景,本文通过一些代码来阐述一些我知道的概念。
目录
返回目录
代码1:Object.Equals
考虑下面代码的结果:
Console.WriteLine(Object.Equals(1, 1));
Console.WriteLine(Object.Equals(1, (byte)1));
答案:True, False
首先Object.Equals参数是两个object,所以1(值类型)会被装箱成引用类型,这会使CLR在托管堆中创建两个全新的Object对象,然后Object.Equals先判断两个object是否有null,没有则调用Object的对象方法Equals,而由于1是值类型,值类型改写Object.Equals并进行比特比较,最终由于object1的比特值完全等于object2的比特值第一句True,第二句很显然Int和Byte内存大小不一样,比特比较不会成功。
返回目录
代码2:Object.ReferenceEquals
代码2:Object.ReferenceEquals
Console.WriteLine(Object.ReferenceEquals(1, 1));
答案:False
同样,Object.ReferenceEquals参数是两个object,所以CLR会在托管堆中建立两个object来分别装Int值,但Object.ReferenceEquals的函数就是判断两个引用的是否指向同一个在托管堆的空间对象,这里当然是False了。
返回目录
代码3:再强化一下理解
下面代码,如果MyType是class或struct时,分别会输出什么?
struct/class MyType
{
public int Data;
}
class Program
{
static void Main(string[] args)
{
MyType s1 = new MyType();
MyType s2 = new MyType();
s1.Data = s2.Data = 1990;
Console.WriteLine(Object.Equals(s1, s2));
}
}
答案:
struct输出:True
class输出:False
这个为了强化下理解,原理和上面的一样,值类型和引用类型针对Object.Equals的执行是不一样的
返回目录
代码4:问候了Equals,我们再看看==
下面代码输出什么?
struct MyType
{
public int Data;
}
class Program
{
static void Main(string[] args)
{
MyType s1 = new MyType();
MyType s2 = new MyType();
s1.Data = s2.Data = 1990;
Console.WriteLine(s1 == s2);
}
}
如果MyType是class,那么结果所有人会知道是False,那如果MyType是struct,结果是?……结果是编译错误,是的,值类型中的用户自定义结构体默认==运算符是不被预先重载的,但是引用类型,枚举,原始值类型的==有。
返回目录
代码5:神奇的String
下面输出结果?
string a = "aaa";
string b = "aaa";
Console.WriteLine(Object.Equals(a, b));
Console.WriteLine(Object.ReferenceEquals(a, b));
Console.WriteLine(a == b);
Console.WriteLine((object)a == b);
//这句VS会提示警告:
//Possible unintended reference comparison; to get a value comparison,
//cast the left hand side to type 'string'
答案:都是True,但True的方式不一样,呵呵,我们一句一句分析
第一句: 调用a.Equals(b),String类的执行是字符串比较,true
第二句:注意这里不进行字符串比较,这里是判断两个引用是不是指向同一个对象,因为Object.ReferenceEquals参数是两个object,但是.NET中相同的字符串(编译器可预知判断的)CLR会确保它们只向同一个内存空间,这个又称字符串的Interning。
第三句:直接调用String的重载==,字符串比较。
第四句:调用引用类型(Object)的重载==,其实等于调用Object.ReferenceEquals。参考第二句,这里Visual Studio提示警告也验证了第二句的结论,这里不会进行字符串比较,而是判断两个引用是否指向同一片内存空间对象。
返回目录
代码6:字段和属性
考虑如果Point是class或struct下面程序的结果?
struct/class Point
{
public int X, Y;
}
class MyCls
{
public Point PField;
public Point PProperty { get; set; }
}
class Program
{
static void Main()
{
MyCls cls = new MyCls();
cls.PField.X = 3;
cls.PProperty.X = 3;
}
}
答案:如果是Point是struct(即值类型),cls.PField.X会赋值成功,而cls.FProperty.X不会赋值成功(其实根本无法编译成功),因为属性本质上就是函数调用,这里PProperty返回一个值类型的拷贝,编辑这个拷贝的内部字段是没有意义的。
如果Point是class(即引用类型),会抛出NullReference异常,因为类内的引用类型默认CLR不为他们分配空间的,所以他们保持默认值(null)。
返回目录
代码7:MemberwiseClone()
MemberwiseClone()是一个非常有用的函数,但很多人不会用它,它不是引用的直接拷贝,而是将成员字段进行复制,如果成员是值类型,那么将进行深层拷贝,如果是引用类型,那么只拷贝引用指针(前后两个引用指向托管堆中的同一份空间)。
考虑下面代码输出?
class a
{
public object obj;
public object ShadowCopy()
{
return MemberwiseClone();
}
}
class Program
{
static void Main(string[] args)
{
a oa = new a() { obj = new object() };
a ob = oa;
a oc = (a)oa.ShadowCopy();
oa.obj = null;
Console.WriteLine(ob.obj == null);
Console.WriteLine(oc.obj == null);
}
}
答案:True, False
ob和oa指向同一个对象,所以oa变了,ob也变,oc是oa的MemberwiseClone的结果,oa的改变仅将自己的引用改成null。而oc没变,oc的成员引用还指向原来的位置。