一、两个对象相等的种类:同一性(identity) 和 相等性(equality)。
   同一性:引用相等,标示了两个对象的引用指向同一对象。
   相等性:集合相等,表明两个对象在实际意义中的相等,不去思考数据库ID等无关紧要字段的差异。
二、Microsoft默认的Equals方法工作原理。

1 Public class Object{
2  public virtual  Boolean Equals(Object obj){
3    //如果两个引用指向同一个对象,他们肯定相等
4    if (this == obj) return true;
5 
6    //假定对象不相等
7    return false;
8  }
9 }

      从代码上不难看出,假如this和obj参数引用同一个对象,就返回true。因为Equals认为一个对象肯定等于他的本身。然而,假设参数引用了不同的对象,Equals就不能肯定对象是否包含相同的值,所以会返回false。也就是说Object类的Equals方法实现的只是“同一性“(identity),而不是“相等性”(equality)。
   然而上面的代码看上的并非完美,似乎还缺少些步骤,仔细分析过后得出Equals方法的完整实现步骤:
 1.如果obj参数为null,返回false。因为在调用非静态的Equals方法时,this所标识的当前对象显然不为null。
 2.如果this和obj参数引用的是不同类型的对象,返回fasle。显然,判断一个String对象是否等于一个FileStream对象时,结果应该返回false。
 3.针对类型定义的每个实例字段,将this对象中的值于obj对象中的值进行比较。任何字段不相等,就返回false。
 4.调用基类的Equals方法,比较它定义的任何字段。如果基类的Equals方法返回false,则返回flase;否则返回true。
 
3、重写Equals方法,让其能适应自定义的对象,实现“相等性”(equality)的比较。
   首先抛开数据库中类似ID等,在实际概念中无意义字段的概念。只关注系统中能表示对象特征的字段。只要这些字段相等,那么我们就视为这两个对象是相等的。
   举例说明:
 现在,有3只蓝色的画笔和2只绿色的画笔。
 从数据角度分析,他们的ID(主键)值可以假定为1~5。若从数据角度看来,他们完全不是同一个对象。但在实际应用中,假设我们现在需要拿只笔来在画画,这时我们只关注这些笔的品牌、型号、颜色。若这些属性字段一样,那我们就可以把这些相同字段的笔视为同一个对象,所谓“相等性”(equality)。
   看来,要实现上述例子中的相等性比较,需要改变Equals方法的判断规则,使其不再关注对象的引用,而关注我们想让他关注的属性字段。
   代码示例:

Code

   重写后的Equals方法,便可以满足我们的要求。但在重写Equals方法后,若想检查对象的同一性又该如何处理呢?Microsoft为我我们准备了另外一个静态方法:ReferenceEquals,在比较同一性时调用此方法来进行判断。如果使用==操作符,必须先把两个操作数都转型为Object,否则可能因为其中某个操作数的类型重载了==操作符,为其赋予有别于“同一性”的其他语义。

   在定义自己的类型时,若要重写Equals方法,必须确定他符合相等性的4个特征:
   1.Equals必须是自反的;换言之,x.Equals(x)肯定返回true。
   2.Equals必须是对称的;换言之,x.Equals(y)肯定返回与e.Equals(x)相同的值。
   3.Equals必须是可传递的;换言之,若x.Equals(y)返回true,y.Equals(z)返回true,那么x.Equals(z)也肯定返回true。
   4.Equals必须是一致的。倘若比较的两个值没有发生改变,那么Equals应该始终返回true或false。
   若实现的Equals不符合上述任何一个特征,应用程序酒会行为失常。

   另外,在重写Equals方法时,还需要做下面2件事情:
   1.让类型实现System.IEquatable<T>接口的Equals方法。
     这个泛型接口允许我们定义个类型安全的Equals方法。通常,实现的Equals方法应该获取一个Object参数,以便在内部调用类型安全的Equals方法。

Code

   2.重写==和!=操作符。
     通常应该实现这些操作符方法,并在内部调用类型安全的Equals方法。
     代码示例:
    

 

Code

三、对象的哈希码
    如果一个类重写了Equals方法,那么应该同时重写GetHashCode()方法。
    因为System.Collections.Hashtable和System.Collections.Generic.Dictionary的实现要求相等的任何两个对象必须具有相同的哈希码。所以,如果重写Equals,那么应该同时重写GetHashCode,确保用于计算相等性的算法与用于计算对象的哈希码的算法是一致的。

    示例代码:
 

 

1 internal sealed class Point{
2  private Int32 m_x,m_y;
3  public override Int32 GetHashCode(){
4    return m_x^m_y;   //返回m_x和m_y的XOR结果值
5  }
6  
7 }

    选择一种算法来计算类型实例的哈希码时,应遵守以下规则:
    1.这个算法提供良好的随即分布,使哈希表获得最佳的性能。
    2.可以调用基类的GetHashCode()方法,并在我们自己的算法中包含它的返回值。然而一般不要调用Object或者ValueType的GetHashCode方法,因为这两个方法采用的算法都与高性能的哈希算法格格不入。
    3.这个算法应该使用至少一个实例字段。
    4.理想情况下,这个算法中使用的字段应该是不可变的(immutable);也就是说,字段应在对象构造时初始化,而且在对象生存周期内永不改变。
    5.这个算法应该尽可能快的执行。
    6.具有相同的对象应该返回相同的哈希码。例如,具有相同文本的两个字符串对象应该返回相同的哈希码。

    另外再说明一点
 假如因为某些原因需要实现自己的的哈希表集合,或者在实现的代码中,需要调用GetHashCode方法,千万不要对哈希码进行持久化,因为哈希码很容易改变。例如:一个类型未来的版本可能使用一个不同的算法来计算对象的哈希码。

以上部分内容参考《CLR Via C#》

posted on 2009-10-26 01:15  林博然  阅读(277)  评论(0编辑  收藏  举报