程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> Effective C#原則9:明白幾個相等運算之間的關系(3)

Effective C#原則9:明白幾個相等運算之間的關系(3)

編輯:關於C語言

首先,Equals()決不應該拋出異常,這感覺不大好。兩個變 量要麼相等,要麼不等;沒有其它失敗的余地。直接為所有的失敗返回false, 例如null引用或者錯誤參數。現在,讓我們來深入的討論這個方法的細節,這樣 你會明白為什麼每個檢測為什麼會在那裡,以及那些方法可以省略。第一個檢測 斷定右邊的對象是否為null,這樣的引用上沒有方法檢測,在C#裡,這決不可能 為null。在你調用任何一個引用到null的實例的方法之前,CLR可能拋出異常。 下一步的檢測來斷定兩個對象的引用是否是一樣的,檢測對象ID就行了。這是一 個高效的檢測,並且相等的對象ID來保證相同的內容。

接下來的檢測來 斷定兩個對象是否是同樣的數據類型。這個步驟是很重要的,首先,應該注意到 它並不一定是Foo類型,它調用了this.GetType(),這個實際的類型可能是從Foo 類派生的。其次,這裡的代碼在比較前檢測了對象的確切類型。這並不能充分保 證你可以把右邊的參數轉化成當前的類型。這個測試會產生兩個細微的BUG。考 慮下面這個簡單繼承層次關系的例子:

public class B
{
 public override bool Equals( object right )
 {
   // check null:
  if (right == null)
   return false;
  // Check reference equality:
  if (object.ReferenceEquals( this, right ))
   return true;
  // Problems here, discussed below.
  B rightAsB = right as B;
  if (rightAsB == null)
   return false;
   return CompareBMembers( this, rightAsB );
 }
}
public class D : B
{
 // etc.
 public override bool Equals( object right )
 {
  // check null:
  if (right == null)
   return false;
  if (object.ReferenceEquals( this, right ))
   return true;
  // Problems here.
  D rightAsD = right as D;
  if (rightAsD == null)
    return false;
  if (base.Equals( rightAsD ) == false)
   return false;
  return CompareDMembers( this, rightAsD );
 }
}
//Test:
B baSEObject = new B();
D derivedObject = new D();
// Comparison 1.
if (baSEObject.Equals(derivedObject))
 Console.WriteLine( "Equals" );
else
 Console.WriteLine( "Not Equal" );
// Comparison 2.
if (derivedObject.Equals (baSEObject))
 Console.WriteLine( "Equals" );
else
 Console.WriteLine( "Not Equal" );

在任何可能的情況下,你都希望要麼看到兩個Equals或者兩個 Not Equal。因為一些錯誤,這並不是先前代碼的情形。這裡的第二個比較決不 會返回true。這裡的基類,類型B,決不可能轉化為D。然而,第一個比較可能返 回true。派生類,類型D,可以隱式的轉化為類型B。如果右邊參數以B類型展示 的成員與左邊參數以B類型展示的成員是同等的,B.Equals()就認為兩個對象是 相等的。你將破壞相等的對稱性。這一架構被破壞是因為自動實現了在繼承關系 中隱式的上下轉化。

當你這樣寫時,類型D被隱式的轉化為B類型:

baSEObject.Equals( derived )

如果 baSEObject.Equals()在它自己所定義的成員相等時,就斷定兩個對象是相等的 。另一方面,當你這樣寫時,類型B不能轉化為D類型,

derivedObject.Equals( base )

B對象不能轉化為 D對象,derivedObject.Equals()方法總是返回false。如果你不確切的檢測對象 的類型,你可能一不小心就陷入這樣的窘境,比較對象的順序成為一個問題。

當你重載Equals()時,這裡還有另外一個可行的方法。你應該調用基類 的System.Object或者System.ValueType的比較方法,除非基類沒有實現它。前 面的代碼提供了一個示例。類型D調用基類,類型B,定義的Equals()方法,然而 ,類B沒有調用baSEObject.Equals()。它調用了Systme.Object裡定義的那個版 本,就是當兩個參數引用到同一個對象時它返回true。這並不是你想要的,或者 你是還沒有在第一個類裡的寫你自己的方法。

原則是不管什麼時候,在 創建一個值類型時重載Equals()方法,並且你不想讓引用類型遵從默認引用類型 的語義時也重載Equals(),就像System.Object定義的那樣。當你寫你自己的 Equals()時,遵從要點裡實現的內容。重載Equals()就意味著你應該重寫 GetHashCode(),詳情參見原則10。

解決了三個,最後一個:操作符==() ,任何時候你創建一個值類型,重新定義操作符==()。原因和實例的Equals()是 完全一樣的。默認的版本使用的是引用的比較來比較兩個值類型。效率遠不及你 自己任意實現的一個,所以,你自己寫。當你比較兩個值類型時,遵從原則17裡 的建議來避免裝箱。

注意,我並不是說不管你是否重載了實例的Equals (),都還要必須重載操作符==()。我是說在你創建值類型時才重載操作符==()。 .Net框架裡的類還是期望引用類型的==操作符還是保留引用類型的語義。

C#給了你4種方法來檢測相等性,但你只須要考慮為其中兩個提供你自 己的方法。你決不應該重載靜態的Object.ReferenceEquals()和靜態的 Object.Equals(),因為它們提供了正確的檢測,忽略運行時類型。你應該為了 更好的性能而總是為值類型實例提供重載的Equals()方法和操作符==()。當你希 望引用類型的相等與對象ID的相等不同時,你應該重載引用類型實例的Equals() 。簡單,不是嗎?

返回教程目錄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved