当我们创建自己的类型时(无论是class还是struct),都应为类型定义“等同性”的含义。
C#提供了4种不同的函数来判断两个对象是否“相等”。
public static bool ReferenceEquals(object left, object right);
public static bool Equals(object left, object right);
public virtual bool Equals(object obj);
public static bool operator == (MyClass left, MyClass right);
当然,这4个方法并不是等同性比较的唯一选择。覆写Equals()方法的类型也应该实现 IEquatable<T>。实现值语义(value semantics)的类型同时还应该实现IStructralEquatable 接口。这就意味着一共有6种方法来表达等同性。
C#允许我们创建两种类型:值类型和引用类型。如果两个引用类型的变量指向的是同一个对象,它们将被认为是“引用相等”。如果两个值类型的变量类型相同,而且包含同样的内容,它们被认为是“值相等”。这也正是等同性判断需要如此多方法的原因。
虽然看起来做等同性判断有些过于复杂,但是不要担心,我们可以简化这个问题。
不应该覆写的方法
对于前两个静态函数,我们永远都不应该去重新定义。
1. Object.ReferenceEquals()
如果两个变量指向同一个对象,也就是它们拥有同样的对象标识,那么Object.ReferenceEquals() 方法将返回true。无论比较的是引用类型还是值类型,该方法判断的依据都是对象标识,而不是对象内容。这也意味着,如果我们使用ReferenceEquals() 来比较两个值类型,其结果将永远返回false,即使我们将一个值类型和它自身进行比较,ReferenceEquals() 的返回值仍是false,其原因在于装箱。
我们永远都不应该去重新定义Object.ReferenceEquals() 方法,因为它已经完美地完成了所需要完成的工作——判断两个不同变量的对象标识是否相等。
2. Object.Equals()
当你不知道两个变量的运行时类型时,可以使用该方法来判断两个变量是否相等。任何时候我们比较两个变量都是System.Object 的实例,值类型变量和引用类型变量都是如此。那么该方法又将如何判断两个变量是否相等呢?因为该方法并不知道它们的类型,而等同性判断又是依赖类型的。答案很简单,该方法会将判断的具体操作委托给其中一个类型来做。
public static bool Equals(object left, object right)
{
// check object identity
if (Object.ReferenceEquals(left, right))
return true;
// both null reference handled above
if (Object.ReferenceEquals(left, null) ||
Object.RefernceEquals(right, null))
return false;
return left.Equals(right);
}
从上面的代码可以看出,在静态 Equals() 方法的内部,实际上是通过调用left参数的实例Equals() 方法来判断两个对象是否相等的。
和 ReferenceEquals() 方法一样,你永远不要去重新定义或覆写静态的 Object.Equals() 方法,因为它也已经很好地完成了其应该做的动作,即在不知道对象的运行时类型时,判断二者是否相等。
可以覆写的方法
下面我们讨论那些可以被覆写的方法,但是在此之前,我们先来简要谈谈等同性关系在数学上的定义。对于等同性判断,需要保证我们给出的定义是满足等同性在数学方面的几个