前一篇介紹的兩個方法——運算子 ==() 和執行個體 Equals()——尚未討論。我將詳細說明,但先完成靜態 Equals() 的探討。請理解:靜態 Equals() 使用左側參數執行個體的 Equals() 方法判斷兩物件是否相等。
同 ReferenceEquals(),你或許永遠不需重新定義靜態 Object.Equals(),因它已確實完成任務:在不知兩物件確切型別時判斷是否相同。靜態 Equals() 將比較委派給左側參數執行個體的 Equals(),以此處理另一型別。
重新定義方法時,實作必須符合其他程式設計師預期的數學性質:
a==a 恆為 true。a==b 為真,則 b==a 必為真。a==b 且 b==c 為真,則 a==c 必為真。當預設行為與你的型別不一致時,應建立自己的執行個體版本。Object.Equals() 使用物件 ID 判斷兩變數是否相等,預設行為同 Object.ReferenceEquals()。但實值型別不同:System.ValueType 未覆寫 Object.Equals()(它是所有 struct 的基類)。兩實值型別變數相等,若型別與內容皆相同。ValueType.Equals() 實作此行為,但效率不高:必須在不知衍生型別情況下比較所有成員,意味著需用反射(原則 44 將詳述反射缺點)。
相等比較是應用程式頻繁呼叫的基礎設施,效能值得重視。大多數情況下,可為任何實值型別覆寫更快的 Equals()。簡單建議:建立實值型別時,總是覆寫 ValueType.Equals()。
僅當你想改變參考型別定義的 Equals() 語意時。.NET 框架中大量類別使用實值型別語意(比較內容)取代參考型別語意(比較物件 ID)。例如:兩 String 內容相同則相等;兩 DataRowView 參考同一 DataRow 則相等。關鍵在於:若你的型別須遵循實值型別語意(比較內容)而非參考型別語意,應自行覆寫執行個體 Object.Equals()。
public class Foo
{
public override bool Equals(object right)
{
if (right == null) return false;
if (object.ReferenceEquals(this, right)) return true;
if (this.GetType() != right.GetType()) return false;
return CompareFooMembers(this, right as Foo);
}
}