代码改变世界

CLR中对象的等值性与唯一性

2010-12-29 12:50  姜 萌@cnblogs  阅读(333)  评论(0编辑  收藏  举报

比较引用类型的字段

引用对象相等有两种含义:一是两个变量指向的是同一引用,二是二者的Equals方法相同(约定)。

Object有一个静态方法Equals,它会先比较是否为同一引用再返回Equals方法的结果。

 

Object中的静态Equals方法
public static bool Equals(Object objA, Object objB) 
    {
        
if (objA==objB) {
            
return true;
        } 
        
if (objA==null || objB==null) {
            
return false
        } 
        
return objA.Equals(objB);
    } 

 

 

比较值类型

上篇讲到,所有值类型都继承自ValueType,值对象本身具备引用特性,其实ValueType本身已经重写了Equals,但是性能比较低下。我们查看FCL的源代码找到ValueType.cs如下:

ValueType中的Equals
public override bool Equals (Object obj) { 
            BCLDebug.Perf(
false"ValueType::Equals is not fast.  "+this.GetType().FullName+" should override Equals(Object)"); 
            
if (null==obj) {
                
return false
            }
            RuntimeType thisType 
= (RuntimeType)this.GetType();
            RuntimeType thatType 
= (RuntimeType)obj.GetType();
 
            
if (thatType!=thisType) {
                
return false
            } 

            Object thisObj 
= (Object)this
            Object thisResult, thatResult;

            
// if there are no GC references in this object we can avoid reflection
            
// and do a fast memcmp 
            if (CanCompareBits(this))
                
return FastEqualsCheck(thisObj, obj); 
 
            FieldInfo[] thisFields 
= thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
 
            
for (int i=0; i<thisFields.Length; i++) {
                thisResult 
= ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false);
                thatResult 
= ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false);
 
                
if (thisResult == null) {
                    
if (thatResult != null
                        
return false
                }
                
else 
                
if (!thisResult.Equals(thatResult)) {
                    
return false;
                }
            } 

            
return true
        } 

 

 

 

可见它是通过反射所有值对象的字段并依次比较来判断是否相等。反射会降低效率,其中还可能会涉及大量的装箱/拆箱操作。所以我们尽量对值类型重写Equals。

 

GetHashCode

CLR编译约定,一个类型如果重载了Equals,就必须重写GetHashCode(在java上也是有这个要求),如果没有按照这样的约定去做,编译器会给出警告。相反,我们重写GetHashCode就无需重写Equals。

对于引用类型默认的GetHashCode,返回的是这个对象在所有堆对象中一个唯一标示值,而对于值类型,我们同样可以再源代码中找到答案:

 

ValueType中的GetHashCode
public override Int32 GetHashCode() 
{
 FieldInfo[] fields 
= this.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if(fields.Length > 0
{
 
for(int i = 0; i < fields.Length; i++)
  {
    Object obj 
= fields[i].GetValue();
    
if(obj != nullreturn obj.GetHashCode();
  }
}
return GetMethodTablePtrAsInt(this);
}

 

 

可见ValueType的GetHashCode方法是遵循这样一个原则:返回第一个字段的hashcode。由于默认情况下CLR使用AutoLayout对一个类型字段进行布局(我们也可以改成Sequence的方式),所以大多数情况下我们还是自己重写GetHashCode比较靠谱。