《Effective C#》讀書筆記——條目6:理解幾個等同性判斷之間的關係

來源:互聯網
上載者:User
文章目錄
  • 引用相等和值相等
  • 為什麼不應該重新定義靜態ReferenceEquals()和Equals()方法
  • 什麼情況下需要重寫Equals()執行個體方法
  • 如何覆寫Equals()執行個體方法

  建立自訂的類型時(無論是類還是struct),應為類型定義”同等性“的含義。在C#中為我們提供了四種不同的函數來判斷兩個對象是否”相等“:

1 public static bool ReferenceEquals(object left, object right);2 public static bool Equals(object left, object right);3 public virtual bool Equals(object right);4 public static bool operator ==(MyClass left, MyClass right);
引用相等和值相等

  C#允許我們建立兩種類型:實值型別和參考型別。如果兩個參考型別的變數指向的是同一個對象,它們將被認為是“引用相等”。如果兩個實值型別的變數類型相同且包含同樣的內容,它們被認為是“值相等”。這也正是同等性判斷需要如此多方法的原因。

 

為什麼不應該重新定義靜態ReferenceEquals()和Equals()方法

  對於前兩個靜態函數,我們永遠都不應該去重新定義,因為它們已經很好的完成了它們的工作,且判斷與運行時具體類型無關:判斷兩個不同變數的對象標誌(object identity)是否相等。無論比較的是實值型別還是參考型別靜態ReferenceEquals方法的判斷依據都是對象標誌,所以比較兩個實值型別永遠返回false,即使是實值型別和它本身比較也是,這是因為裝箱的原因。當我們不知道兩個變數的運行時類型時,可以使用靜態Equals方法來判斷兩個變數是否相等,同等判斷是以來類型,所以靜態Equals通過委託其中一個類型來做的判斷的,靜態Ojbect.Equals()方法實現如下:

 1   public static new bool Equals(object left, object right) 2         { 3             //檢查對象引用 4             if (Object.ReferenceEquals(left, right)) 5                 return true; 6             //是否為null 7             if (Object.ReferenceEquals(left, null)) 8                 return false; 9             //調用執行個體的Equals()方法10             return left.Equals(right);11         }

我們可以看到靜態Equals()方法將判斷的工作交給left參數的執行個體Equals()方法執行,所以它會使用left參數的類型中定義的規則來進行等同性判斷。

 

什麼情況下需要重寫Equals()執行個體方法

  當Equals()執行個體方法的預設行為與我們的類型要求不一致時,自然需要覆寫。該方法預設使用對象標誌判斷,即比較兩個對象是否引用相等。

實值型別(使用Struct關鍵字建立的類型):System.ValueType(所有實值型別的基類)覆寫了Object.Equals()方法:兩個實值型別變數類型相同,內容一致,兩個變數才認為相等。由於ValueType是所有實值型別的基類,為了提供正確的行為,必須能夠在不知道對象運行時類型的情況下比較其衍生類別中的所有成員變數,這意味著要使用反射來實現。而反射又是非常損耗效能的。而等同性判斷又是一個非常基礎的功能,所以我們有必要(追求效能時)為自己的實值型別提供一個更快的Equals()覆寫版本。

參考型別:只有我們希望更改其預定義的語義時,才應該覆寫Equals()方法。在.NET類庫中許多類都是使用值語義而不是引用語義來做等同判斷的,例如:如果兩個string對象包含相同的內容就被認為相等;若兩個DataRowView對象引用同一個DataRow,那麼將被認為相等。

 

如何覆寫Equals()執行個體方法

   覆寫Equlas()執行個體方法是需要實現IEquatable<T>介面,該介面包含了一個方法Equals(Tother),實現了IEquatable<T>以為著你的類型支援型別安全的等同性比較。若你認為Equals()僅僅應該在比較的兩邊屬於同一個類型時才返回true,那麼IEquatable<T>將會讓編譯器幫你找到可能出現的種種類型相關的不相等情況。

下面是覆寫System.Object.Equals()執行個體方法的標準實現模式(只是一個樣本,具體情況還需根據我們的代碼需求來確定):

 1         public class foo : IEquatable<foo> 2         { 3             public override bool Equals(object right) 4             { 5                 //是否為null 6                 if (Object.ReferenceEquals(right, null)) 7                     return false; 8                 //是否引用相等 9                 if (Object.ReferenceEquals(this, right))10                     return true;11                 //可能是子類,所以需要精確的類型判斷12                 if (this.GetType() != right.GetType())13                     return false;14                 //調用執行個體的Equals()方法15                 return this.Equals(right as foo);16             }17 18             //IEquatable<foo> 成員19             public bool Equals(foo other)20             {21                 //略去22                 return true;23             }24         }

我們仔細觀察這個實現:第一個堅持判斷右邊對象是否為null,對於this指標引用則不需要這一步,因為在C#中this指標永遠不會為null。第二個判斷兩個對象是否為同一個引用,如果兩個對象引用相同,則對象內容一定相等。第三個函數用來判斷兩個對象的類型是否相同。這裡使用精確的比較是非常重要的。首先,沒有假設this指標的類型為Foo,而是再次調用this.GetType()擷取,因為實際的類型可能繼承自Foo。

 

小節:

對於所有的實值型別,都應該覆寫其Equals()方法;對於參考型別,當System.Object提供的引用語義不能滿足我們的需求時,才應該去覆寫Equals()方法。覆寫Equals()方法時也應該同時覆寫GetHashCode()方法(條目7)。對於operator==()則比較簡單,只要建立的是實值型別,都必須重新覆寫一個operator==(),理由和覆寫ValueType.Equals()執行個體函數完全一樣。參考型別應該盡量避免覆寫operator==(),.NET 希望所有參考型別都應用的operator==()都遵循引用語義,因為系統提供的預設版本時通過比較兩個實值型別執行個體的內容,並且是用反射來實現的。(其實如果對於效能不是那麼敏感的話可以忽略)。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.