為什麼要為值類型重定義相等性 原因主要有以下幾點: 值類型預設無法使用 == 操作符,除非對它進行重寫 再就是性能原因,因為值類型預設的相等性比較會使用裝箱和反射,所以性能很差 根據業務需求,其實際相等性的意義和預設的比較結果可能會不同,但是這種情況可能不較少 所以建議是:所有供外部使用的struc ...
為什麼要為值類型重定義相等性
原因主要有以下幾點:
- 值類型預設無法使用 == 操作符,除非對它進行重寫
- 再就是性能原因,因為值類型預設的相等性比較會使用裝箱和反射,所以性能很差
- 根據業務需求,其實際相等性的意義和預設的比較結果可能會不同,但是這種情況可能不較少
所以建議是:所有供外部使用的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個方法。