C#相等性 - 三個方法和一個接口


簡介

C#(.NET)的object類里面有三個關於判斷相等性的方法:

  • public virtual bool Equals(object obj)
  • public static bool Equals(object objA, object objB)
  • public static bool ReferenceEquals(object objA, object objB)

還有一個接口:IEquatable<T>也可以用來判斷相等性。

 

virtual bool Equals()

比較自定義Class

比較這個Class的兩個實例,它們的屬性值是一樣的:

 

輸出結果:

之所以結果是False,是因為object.Equals()評估的是引用的相等性,除非進行了重寫

 

比較string

這是兩個字符串,而且使用string.Copy()可以保證它們不指向同一個地址(如果不使用string.Copy(),而直接賦兩個同樣的值,那么可能會發生字符串駐留問題:https://www.cnblogs.com/artech/archive/2007/03/04/663728.aspx):

這時輸出的結果是:

 

但是我們看一下string這個類,可以發現string有很多Equals()方法:

 

如果按照上面這么寫的話,它並沒有調用object.Equals()方法。所以我們改一下代碼:

這時調用的是object.Equals()方法,它的輸出依然是:

 

這是因為string類對object的Equals()方法進行了重寫,重寫后比較的是字符串的值

除了string之外,delegates和Tuples也對object.Equals()方法進行了重寫。不過對大部分的.NET類型來說,object.Equals()比較的是引用。

 

比較值類型

值類型是存放在Stack上面的,它們通常沒有引用,除非你對它們進行裝箱操作。

那么對值類型使用object.Equals()方法,應該沒有什么意義。。。

 

有這么一個自定義的Struct:

 

然后進行兩組比較:

輸出結果是:

很顯然,結果有點出乎我的意料,針對這個Struct類型,object.Equals()比較的是它們的值。

這是因為所有的struct都繼承於System.ValueType,而System.ValueType繼承於System.Object,System.ValueType它對object.Equals()方法進行了重寫,重寫的方法里會比較值類型里面所有的字段(Field),如果所有字段都相等,那么就返回true

但是System.ValueType的重寫是使用反射來找到所有的字段(Fields),所以性能比較差。

所以針對值類型最好的辦法是自己重寫一下Equals()方法。

 

總結

默認情況下,針對引用類型,object.Equals()比較的是引用;針對值類型,object.Equals()比較的是值。

但是所有的類型都可以重寫object.Equals()方法,例如string。

 

靜態的 Equals() 方法

比較null

使用object virtual的Equals()方法可以應付大部分情況,但是如果該引用是null,那么使用該方法就會報錯了:

這時候我們就可以使用object類的靜態Equals()方法:

(也可以不寫object)

而結果當然是:

 

比較兩個null

結果是:

在.NET/.NET Core 里面,null和null是相等的。

 

源碼

靜態Equals()方法的源碼其實很簡單,除了檢查null之外,它會給出和virtual Equals()方法同樣的結果。

如果你對virtual的Equals()方法進行了重寫,而由於靜態的Equals()方法就會調用重寫的virtual Equals()方法,所以這兩個方法要保持一貫性。

 

靜態 Reference Equals() 方法

它和前兩種方法有點像,但是也不盡相同。

雖然virtual和靜態的Equals()方法通常會比較引用,但是virutal的方法可以被重寫,從而比較的是值,例如string。所以使用ReferenceEquals()來比較兩個變量是否指向同一個實例是更安全准確的

看下面這兩個比較:

第一個比較調用的是object的virtual Equals()方法,但是string對其進行了重寫,比較的是值:

而第二個比較是object的靜態的ReferenceEquals()方法,由於是靜態的,所以沒法重寫:

而C#里的==是什么原理,以后再說。

 

IEquatable<T>

System.Object的static bool Equals(object obj)這個方法,因為其參數是object類型,所以它可以對任何引用類型進行比較。但是如果想比較值類型的話,那么值類型就會被裝箱,然后再進行比較。但是裝箱的動作會有性能損耗,而之所以采用值類型的主要原因就是因為性能。所以這是一個問題。

再者,使用該方法來比較兩個不相干的類型,比如Apple和Book這兩個Class,比較的時候不會報錯,但是這沒有任何意義。這就是因為參數不是強類型,才會出現這些問題。

而IEquatable<T>這個接口就可以解決這些問題。

它只定義了一個方法:bool Equals(T other)。

 

例子,三個int:

使用它的Equals()方法:

可以看到除了object.Equals(object obj)這個方法外,它還有一個Equals(int obj)這個方法,它的參數是強類型的,這是因為int實現了IEquatable<T>接口。

而其源碼大致如下:

所以平時比較int的時候使用==即可。

 

所有的原始類型都實現了IEquatable<T>接口。int, byte...

而IEquatable<T>對值類型非常有用。

但是對引用類型沒有太大的用處,因為引用類型比較時不存在裝箱問題,而且IEquatable<T>在繼承方面還是存在問題的,但是string還是實現了IEquatable<T>接口,因為string是seal的,不存在繼承。

 

需要注意的是如果實現了IEquatable<T>,那么它的實現方法和重寫的object.Equals()方法應該保持一致,做同樣的事。

 


免責聲明!

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



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