CLR via C#學習筆記-第五章-對象相等性和同一性
5.3.2 對象相等性和同一性
開發人員經常寫代碼比較對象。例如,有時要將對象放到集合,寫代碼對集合中的對象排序、搜索或比較。
本節將討論相等性和同一性謀害將討論如何定義正確實現了對象相等性的類型。
System.Object的Equals方法的代碼演示
System.Object類型提供了名為Equals的虛方法。作用是在兩個對象包含相同值的前提下返回true。
Object的Equals方法是像下面這樣實現的。
public class Object { public virtual Boolean Equals(Object obj) { if(this==obj)return true; return false; } }
乍一看,這似乎就是Equals的合理實現:加入this和obj實參引用同一個對象,就返回true。
似乎合理時因為Equals知道對象肯定包含和她自身一樣的值。
但假如實參引用不同對象,Equals就不肯定對象是否包含相同的值,所以返回false。
換言之,對Object的Equals方法的默認實現,它實現的實際是同一性(identity),而非相等性(equalioy)。
遺憾的是Object的Equals方法的默認實現並不合理,而且永遠都不應該這樣實現。
研究一下類的繼承層次結構,並思考如何正確重寫Equals方法,馬上會發現問題出在哪裏。
正確實現Equals的要求
下面展示了Equals方法應該如何正確地實現。
1.如果obj實參為null,就返回false,因為調用非靜態Euqals方法時,this所表示的當前對象顯然不為null。
2.如果this和obj實參引用同一個對象時,就返回true。在比較包含大量字段的對象時,這有助於提升性能。
3.如果this和obj實參引用不同類型的對象,就返回false。
4.針對類型定義的每個實例字段,將this對象中的值與obj中的值進行比較,任何字段不相等,就返回false。
5.調用基類的Equals方法來比較它定義的任何字段。如果基類的Equals返回false就返回false,否則返回true。
正確實現Equals的代碼演示
所以微軟本應該像下面這樣實現Object的Equals方法。
public class Object { public virtual Boolean Equals(Object obj) { //要比較的對象不能為null if(obj==null)return true; //對象屬於不同類型則肯定不相等 if(this.GetType()!=obj.GetType())return false; //如果對象屬於相同類型,那麽他們的所有字段都匹配的前提下返回true //由於System.Object沒有定義任何字段,所以字段是匹配的 return false; } }
但微軟沒有這樣實現Equals,所以Equals的實現規則遠比想象的復雜。
類型重寫Equals方法時應調用其基類的Equals實現(除非基類就是Object)。
檢查同一性的方法
另外,由於類型能重寫Object的Equals方法,所以不能再用他測試同一性。
為了解決這個問題,Object提供了靜態方法ReferenceEquals,其原型如下。
public class Object { public virtual Boolean Equals(Object objA,Object objB) { return(objA==objB); } }
檢查同一性(看兩個引用是否指向同一個對象)務必調用ReferenceEquals,不應使用C#的==操作符(儲非先把兩個操作數都轉型為Object)。
因為某個操作數的類型可能重載了==操作符,為其賦予不同於“同一性”的語義。
可以看出在設計對象相等性和同一性時,.NET的設計很容易使人混淆。
System.ValueType就重寫了Object的Equals方法,並進行了正確的實現來執行值的相等性檢查。實現與上述示例相同,不做贅述。
在內部,ValueType的Equals方法利用反射完成上述步驟中的針對每個實例字段比較this和obj。
由於CLR反射機制慢,定義自己的值類型時應重寫Equals方法來提供自己的實現。
自己的實現不調用base.Euqals。
自己重寫Equals時的要求
定義自己的類型時,你重寫的Equals要符合以下四個特征
1.Equals必須自反,x.Equals(x)肯定返回true。
2.Equals必須對稱,x.Equals(y)和y.Equals(y)返回相同的值。
3.Equals必須可傳遞:x.Equals(y)返回true,y.Equals(z)返回true,則x.Equals(z)肯定返回true。
4.Equals必須一直。比較的兩個值不變,Equals返回值也不能變。
重寫Equals方法時,可能還需要做下面幾件事
1.讓類型實現System.IEquatable<T>接口的Equals方法。這個泛型接口允許定義類型安全的Equals方法。通常你實現的Equals方法應獲取一個Object參數,以便在內部調用類型安全的Equals方法。
2.重載==和!=操作符方法。通常應實現這些操作符方法,在內部調用類型安全的Equals。
CLR via C#學習筆記-第五章-對象相等性和同一性