C# - 為值類型重定義相等性


為什么要為值類型重定義相等性

原因主要有以下幾點:

  • 值類型默認無法使用 == 操作符,除非對它進行重寫
  • 再就是性能原因,因為值類型默認的相等性比較會使用裝箱和反射,所以性能很差
  • 根據業務需求,其實際相等性的意義和默認的比較結果可能會不同,但是這種情況可能不較少

所以建議是:所有供外部使用的struct都實現相等性。

 

實現步驟

  • 重寫object.Equals()方法
  • 實現IEquatable<T>.Equals()接口方法
  • 重寫 == 和 != 操作符
  • 重寫object.GetHashCode()

具體來說:

重寫object.Equals()方法,是避免了反射,因為System.ValueType里面對object.Equals()方法的重寫實現如下:

這里用到了反射。

而實現IEquatable<T>.Equals()接口方法,可以避免裝箱,並且保證類型安全。

而實現==和!=,也就允許值類型使用該操作符了,寫起來更方便直觀,易於理解。而且這兩個操作符必須一同實現。

而重寫object.GetHashCode(),則是一個最佳實踐。

 

所有為值類型重定義相等性,一共分4步,每步都是必須的

 

實現

先看實例struct:

有構造函數,涉及到一個enum,並重寫了ToString()方法。

 

實現IEquatable<T>接口

首先來實現IEquatable<T>接口。

(如果你使用resharper或者Rider,那么實現該接口的時候它會自動把object的Equals和GetHashCode方法都重寫了,並且自動完成了有意義的代碼)

這里面我對三個屬性進行了比較,使用了==操作符。其中==對於string來說就是比較值,而enum其實就是int,DateTime也是值類型,並且已經實現了相等性判斷的功能。

 

重寫object.Equals()方法

這個代碼是resharper生成的。

代碼很簡單,首先檢查是否為null,然后檢查這個object是不是一個Person,這里使用了 is 操作符,並把它轉型為Person,賦給了一個叫做other的變量。最后調用的這個Equals()方法,是我們上面寫的那個強類型的方法,因為other變量的類型是Person。

但是這個方法仍然涉及到裝箱操作,所以還是IEquatable<T>的實現方法更快一些。

 

如果只重寫了object.Equals()方法,而沒有重寫GetHasCode()方法,那么resharper會有提示:

 

實現 == 和 != 操作符

 

這個很簡單,直接調用強類型的Equals()方法即可,而且由於Person是值類型,所以不用檢查null,值類型不會為null。

 

如果只實現了其中一個操作符,那么會報錯的。

 

實現object.GetHashCode()

GetHashCode()這個方法會返回一個32位的哈希碼,它代表着對象內容的哈希值。

而類型里擁有GetHashCode()方法(返回Hash)的真正目的是,允許該類型在內部使用HashTable的集合中可以作為Key,因為HashTable需要這些哈希碼。例如Dictionary<TK, TV>。

為了讓HashTable可以正確的工作,Hash碼有一個要求:如果兩個實例被認為是相等的,那么它們必須返回相同的hash碼。如果沒有實現這個要求,那么你可能會發現這個類型作為Dictionary的Key的時候,會有一些意想不到的結果。

所以如果重寫了object.Equals()方法,那么就得重寫object.GetHashCode()方法。

 

看一下resharper自動實現的代碼:

這里使用了unchecked,防止拋出溢出異常。

Name是引用類型,可能為null,所以判斷一下。

然后其它兩個int和DateTime類型,微軟都做好了其GetHashCode()的實現。

這里對它們進行異或操作。之所以使用397這個數,可能因為397是一個足夠大的質數,可以導致溢出,並混淆各位,之所以使用質數,是因為用質數相乘會得到比用其他任意數相乘更均勻的結果。

 

檢驗

結果如預期,OK。

 

總結

在這幾個動作里,實際的邏輯寫在了IEquatable<T>.Equals()方法里,object.Equals()就是檢查類型然后調用IEquatable<T>.Equals(),== 和 != 操作符也是調用IEquatable<T>.Equals(),而GetHashCode()則使用了按位異或。

 

最后再重復一次,為值類型定義相等性一定要實現上述4各步驟的5個方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM