C#筆記 – 對象相等性和同一性

對象相等性和同一性
  • System.Object提供了Equals虛方法,其實現為:

    • public class Object
      {
         public virtual bool Equals(Object obj)
        {
             if(this == obj) { return true; }
             return false;
        }
      }
    • 該實現只是判斷「對象」是否與自身包含一樣的值,是「同一性」,而不是「相等性」

    • 另外,由於類型能重寫Object的Equals方法,因此不能再通過Equals來測試同一性;同理 == 操作符也能被重載。因此想要安全地測試同一性,應使用ReferenceEquals

  • 正確的相等性實現應該包含:

    • 參數對象空判斷

    • 自身對象與參數對象的引用同一性

    • 自身對象與參數對象中的每個實例字段的值比較

    • 調用基類的Equals方法

    • public class Object
      {
         public virtual bool Equals(Object obj)
        {
             if(obj == null) { return false; }
             if(this.GetType() != obj.GetType()) { return false; }
             return true;
        }
      }
    • 實際上,System.ValueType就進行了正確的相等性測試實現

      • 判斷對象是否為空

      • 判斷引用對象類型是否一致

      • 利用反射,對this對象和obj對象中的所有值進行比較

        • CLR的反射機制慢,因此自定義值類型時應重寫Equals方法,而Equals方法應符合相等性的4個特徵:

          • 自反:x.Equals(x)必然為true

          • 對稱:x.Equals(y) = y.Equals{x}

          • 可傳遞:x.Equals(y) = true; y.Equals(z) = true; then x.Equals(z) = true

          • 一致:比較的兩個值不變,其返回值也不變

          • 另外,可以通過實現IEquatable接口或重載==和!=操作符來確保內部調用類型安全的Equals方法

對象哈希碼
  • System.GetHashCode獲取任意對象的int32哈希碼

  • 如果重寫了Equals,那就必須重寫GetHashCode

    • 因為在一些集合(如Dictionary)的實現中,要求兩個對象必須具有相同的HashCode才被視為相等,所以重寫Equals就必須重寫GetHashCode。

      • 向集合添加KeyValuePair時,首先要獲取鍵對象的HashCode。該HashCode指出鍵值對要存儲到哪個bucket中。

      • 查找Key時,會獲取指定Key對象的HashCode。該HashCode標識要搜索的bucket,然後在bucket中查找與指定Key對象相等的Key對象

      • 因此,一旦修改了集合中的一個Key對象,集合就再也找不到該對象

        • 所以,需要修改HashTable中的Key對象時,應先移除原來的KeyValuePair,修改,再把改完的加回去

  • 哈希碼算法規則:

    • 提供良好隨機分布

    • 不要調用Object/ValueType的GetHashCode

    • 至少使用一個實例字段

    • 算法使用的字段應該是在生存期不可變的

    • 夠快

    • 包含相同值的不同對象應返回相同的HashCode

參考書目

  • 《CLR via C#》(第4版) Jeffrey Richter
  • 《深入理解C#》(第3版) Jon Skeet