與 ReferenceEquals()一樣,你或許從來不會重新定義靜態的Object.Equals()方法 ,因為它已經確實的完成了它應該完成的事:在你不知道兩個對象的確切類型時 斷定它們是否是一樣的。因為靜態的Equals()方法把比較委托給左邊參數實例的 Equals(),它就是用這一原則來處理另一個類型的。
現在你應該明白為 什麼你從來不必重新定義靜態的ReferenceEquals()以及靜態的Equals()方法了 吧。現在來討論你須要重載的方法。但首先,讓我們先來討論一下這樣的一個與 相等相關的數學性質。你必須確保你重新定義的方法的實現要與其它程序員所期 望的實現是一致的。這就是說你必須確保這樣的一個數學相等性質:相等的自反 性,對稱性和傳遞性。自反性就是說一個對象是等於它自己的,不管對於什麼類 型,a==a總應該返回true;對稱就是說,如果有a==b為真,那麼b==a也必須為真 ;傳遞性就是說,如果a==b為真,且b==c也為真,那麼a==c也必須為真,這就是 傳遞性。
現在是時候來討論實例的Object.Equals()函數了,包括你應該 在什麼時候來重載它。當默認的行為與你的類型不一致時,你應該創建你自己的 實例版本。Object.Equals()方法使用對象的ID來斷定兩個變量是否相等。這個 默認的Object.Equals()函數的行為與Object.ReferenceEquals()確實是一樣的 。但是請注意,值類型是不一樣的。System.ValueType並沒有重載 Object.Equals(),記住,System.ValueType是所有你所創建的值類型(使用關鍵 字struct創建)的基類。兩個值類型的變量相等,如果它們的類型和內容都是一 樣的。ValueType.Equals()實現了這一行為。不幸的是,ValueType.Equals()並 不是一個高效的實現。ValueType.Equals()是所有值類型的基類(譯注:這裡是 說這個方法在基類上進行比較)。為了提供正確的行為,它必須比較派生類的所 有成員變量,而且是在不知道派生類的類型的情況下。在C#裡,這就意味著要使 用反射。正如你將會在原則44裡看到的,對反射而言它們有太多的不利之處,特 別是在以性能為目標的時候。
相等是在應用中經常調用的基礎結構之一 ,因此性能應該是值得考慮的目標。在大多數情況下,你可以為你的任何值類型 重載一個快得多的Equals()。簡單的推薦一下:在你創建一個值類型時,總是重 載ValueType.Equals()。
你應該重載實例的Equals()函數,僅當你想改 變一個引用類型所定義的(Equals()的)語義時。.Net結構類庫中大量的類是使用 值類型的語義來代替引用類型的語義。兩個字符中對象相等,如果它們包含相同 的內容。兩個DataRowVIEwc對象相等,如果它們引用到同一個DataRow。關鍵就 是,如果你的類型須要遵從值類型的語義(比較內容)而不是引用類型的語義(比 較對象ID)時,你應該自己重載實例的Object.Equals()方法。
好了,現 在你知道什麼時候應該重載你自己的Object.Equals(),你應該明白怎樣來實現 它。值類型的比較關系有很多裝箱的實現,裝箱在原則17中討論。對於用戶類型 ,你的實例方法須要遵從原先定義行為(譯注:前面的數學相等性質),從而避免 你的用戶在使用你的類時發生一些意想不到的行為。這有一個標准的模式:
public class Foo
{
public override bool Equals( object right )
{
// check null:
// the this pointer is never null in C# methods.
if (right == null)
return false;
if (object.ReferenceEquals( this, right ))
return true;
// Discussed below.
if (this.GetType() != right.GetType())
return false;
// Compare this type's contents here:
return CompareFooMembers(
this, right as Foo );
}
}