装箱/拆箱,值类型/引用类型 和 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的成员引用还指向原来的位置。
|
|
来自: kittywei > 《111.20-c#》