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);
}
{
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;
}
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 != null) return obj.GetHashCode();
}
}
return GetMethodTablePtrAsInt(this);
}
{
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 != null) return obj.GetHashCode();
}
}
return GetMethodTablePtrAsInt(this);
}
可见ValueType的GetHashCode方法是遵循这样一个原则:返回第一个字段的hashcode。由于默认情况下CLR使用AutoLayout对一个类型字段进行布局(我们也可以改成Sequence的方式),所以大多数情况下我们还是自己重写GetHashCode比较靠谱。