Item 9: Understand the Relationships Among ReferenceEquals(), static Equals(), instance Equals(), and operator== 读后感
C#提供了四种相等比较的函数.
public static bool ReferenceEquals( object left, object right );
public static bool Equals ( object left, object right );
public virtual bool Equals( object right);
public static bool operator==( MyClass left, MyClass right );
上述四个方法override应该遵循如下规则:
1.前两个无论如何不要override
2.第三个和第四个在自定义类型是ValueType的时候要改写
3.当自定义类型是ReferenceType的时候,如果想改变RefrenceType默认的用对象标志判等的方式,可以改写第三个
4.当自定义类型是RefrenceType的时候,最好不要改写operator==.
下面分别给出上述规则的原因:
1.
A.ReferenceEquals()这个方法当且仅当两个变量指向同一对象的时候才相等,它的实现是比较对象标志(Object Identity,任何对象在创建的时候都会生成一个OI),所以任何值类型用ReferenceEquals()比较所得到的结果都是false,即使跟自己比较也是,因为要装箱,装箱的结果肯定不是同一个对象.所以这个方法也就没有改写的必要.object的ReferenceEquals()是实现这种比较最好的方式.
B.Equals (object left, object right )这个方法用于在不知道待比较的参数是何种类型的时候.那如何比较两个变量是否相等那?很简单,把比较相等的职责交给其中一个待比较变量的Equals方法,上述方法的实现大概如下所述:
public static bool Equals( object left, object right ) { // Check object identity if (left == right ) return true; // both null references handled above if ((left == null) || (right == null)) return false; return left.Equals (right); }
之所以不要重写上述两个方法,是因为两者都很好的实现了各自的语意.
2.为什么是值的时候要重写第三个方法?
ValueType是所有值类型的基类,由于要实现值相等的语意,它重写了object的Equals方法,但是在不知道派生类变量和其类型的情况下,ValueType的Equals在实现的时候只能用反射来比较,这样的效率是很低的,所以任何自定义的值类型都要重写Equals,以避免用ValueType的默认实现带来的反射的效率损失.
3.object的实例Equals方法的默认实现跟ReferenceEqual一样,是通过比较对象标志来实现的,有些ReferenceType需要值相等的语意,比如string,这是就要改写实例Equals.
4..Net FrameWork期望所有的ReferenceType的operator==保留引用语意.
OK,终于弄完了,最后说一下文章中介绍的如何改写实例Equals方法.
相等在语意上必须满足三点
1.自反 a=a必须永远成立;
2.对称 a=b则b=a;
3.传递 a=b;b=c则a=c
public class Foo
{
public override bool Equals( object right )
{
//1.判断待比较变量引用是否为空
if (right == null)
return false;
//2.是否指向同一实例,如果是同一实例则必然相等.
if (object.ReferenceEquals( this, right ))
return true;
//3. 判断类型是否相同
if (this.GetType() != right.GetType())
return false;
// 4.比较内容
return CompareFooMembers(
this, right as Foo );
}
}
有个看官可能要说了,为什么不判断this的引用是否为空?得,this为空,在进入Equals方法前就报那个经典的"未....."错误了,还等到这个方法里判断嘛.
那位又说了,1,2,4没异议,可第三步如果可以转化成this引用的对象不行吗?
答案是不行,必须是类型相同的.举例如下:
public class B
{
public override bool Equals( object right )
{
// check null:
if (right == null)
return false;
// Check reference equality:
if (object.ReferenceEquals( this, right ))
return true;
// Problems here, discussed below.
B rightAsB = right as B;
if (rightAsB == null)
return false;
return CompareBMembers( this, rightAsB );
}
}
public class D : B
{
// etc.
public override bool Equals( object right )
{
// check null:
if (right == null)
return false;
if (object.ReferenceEquals( this, right ))
return true;
// Problems here.
D rightAsD = right as D;
if (rightAsD == null)
return false;
if (base.Equals( rightAsD ) == false)
return false;
return CompareDMembers( this, rightAsD );
}
}
//Test:
B baseObject = new B();
D derivedObject = new D();
// Comparison 1.
if (baseObject.Equals(derivedObject))
Console.WriteLine( "Equals" );
else
Console.WriteLine( "Not Equal" );
// Comparison 2.
if (derivedObject.Equals(baseObject))
Console.WriteLine( "Equals" );
else
Console.WriteLine( "Not Equal" );
留意一下加粗的部分就知道,如果B的CompareBMembers和D的CompareDMembers比较的元素相等的话,Comparison 1将输出Equals,而Comparison 2将永远输出Not Equal.所以在这种情况下将违反相等语意中的对称性.
所以还是老老实实在第三步Check类型哈.
心明眼亮的人肯定主意到上面例子中D比B多了一句:
if (base.Equals( rightAsD ) == false)
return false;
很明显,基类的元素肯定要让基类去判断是否相等.但是如果某个类的直接基类是object千万必要调用base.Equals了,要不然跟没有重写实例Equals是一样的效果,为什么?自己去想吧.