首先,我推荐大家先看下Equals 和 == 的区别一文再继续往下看本文(虽然文中有些解释还是让人有点感觉不对),因为我就是从园友@一沐阳光的这篇文章中滋生出的问题,才写的本文。当然你也可以直接看本文,因为我并没有把本文与它强行联系在一起。
先来看下面的代码:
1 int i1 = 8; 2 int i2 = 8; 3 bool bo1 = i1 == i2; // true 4 bool bo2 = (object)i1 == (object)i2; // false 5 bool bo3 = i1.Equals(i2); // true 6 bool bo4 = i1.Equals((object)i2); // true 7 bool bo5 = ((object)i1).Equals(i2); // true
1、bo1=true。这点是没有疑问的,值类型的比较,就是比较它的值。
2、bo2=false。这是把两个int型的值类型装箱了,然后“==”比较时,是比较其引用的地址,所以为false。
3、bo3=true。这是调用了int对应的Int32的Equals(int)方法:
1 public bool Equals(int obj) 2 { 3 return (this == obj); 4 }
很显然,this就是i1,obj就是i2,最终返回的还是i1==i2,为true。
4、bo4=true。可能我们经常都认为Equals(object)是继承自Object类:
1 public virtual bool Equals(object obj) 2 { 3 return RuntimeHelpers.Equals(this, obj); 4 }
但经常忽略了一点,它是个virtual方法,像Int32、UInt32、Double、String等等常用类型,都是重写了这个方法的,而int对应的Int32类型重写为:
1 public override bool Equals(object obj) 2 { 3 return ((obj is int) && (this == ((int) obj))); 4 }
所以遇到i1.Equals(i2),首先调用了(obj is int)来判断i2是不是int型的,如果是就会有强制转化,实际上执行返回的是 i1==(int)((object)i2) ,可见,这必然也是true。
其它类型的重写也与这类似,都会首先判断参数obj是不是当前类型,是则强制转化,再进行比较,如果不是,则看情况进行处理。这里说的看情况,主要包括了两种情况:第一种,本身是值类型。尝试转化参数的类型,如果参数与本身类型相同,这时直接调用==号进行比较(也有特殊情况),如果参数与本身类型不同,则返回false;第二种,本身是引用类型(注意string是引用类型),会尝试转化参数为本身的类型,失败则直接返回false,否则再比较引用的地址是否相同,进行返回。string是引用类型中的特例,不仅引用地址相同的string可以为真,内容相同的string比较也为真,可以看下面关于string类型的Equals(object)重写方法:
1 public override bool Equals(object obj) 2 { 3 if (this == null) 4 { 5 throw new NullReferenceException(); 6 } 7 string strB = obj as string; 8 if (strB == null) 9 { 10 return false; 11 } 12 if (object.ReferenceEquals(this, obj)) 13 { 14 return true; 15 } 16 if (this.Length != strB.Length) 17 { 18 return false; 19 } 20 return EqualsHelper(this, strB); 21 }
说明一下,最后的EqualsHelper(this,strB)函数内部就是对这两个字符串的内容进行逐字符地比较了。
5、bo5=ture。这与前面的bo3、bo4并不一样了,这时编译器调用的不是Int32的Equals(int)或Equals(object)方法,而确实就是Object类型的Equals(object)方法,通过IL代码得知的,i1是通过(object)i1手动装箱的,而i2则是协变,自动完成装箱:
1 IL_0032: ldloc.0 // 读取i0 2 IL_0033: box [mscorlib]System.Int32 // 装箱 3 IL_0038: ldloc.1 // 读取i1 4 IL_0039: box [mscorlib]System.Int32 // 装箱 5 IL_003e: callvirt instance bool [mscorlib]System.Object::Equals(object) // 调用Object.Equals(object)方法 6 IL_0043: stloc.s bo5 // 保存结果到bo5
那么此时为什么又相等了,注意前面贴的关于Object的代码中,只有一行:RuntimeHelpers.Equals(this, obj);这句话我使用Reflector也没有找到具体实现,目测应该是用于帮助编译器实现运行时代码生成工作的,在其内部可能实现了转化,会在运行期根据i1的类型,再调用Int32的Equals方法来完成最终比较。为了验证我的想法,我在MSDN上找到了下面关于RuntimeHelpers.Equals(object, object)的话:
RuntimeHelpers 类
提供一组为编译器提供支持的静态方法和属性。无法继承此类。
RuntimeHelpers.Equals 方法 (Object, Object)
返回值
类型:System.Boolean
如果 o1 参数与 o2 参数是同一个实例,或者二者均为 null,或者 o1.Equals(o2) 返回 true,则为 true;否则为 false。
由于(object)i1与(object)i2并不是同一个实例,二者又均不为null,所以进入i1.Equals(i2)的逻辑运算阶段,最终返回true。本文就此完结,这样一来,结合最开始贴出的文章,最少我自己已经能大概搞清楚实际发生了什么,而不需要去死记了,也不会在遇到这种情况时不知所措了。
转载请注明原址:http://www.cnblogs.com/lekko/archive/2013/03/06/2946282.html
<script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>