C# 提供四種相等比較機制,定義型別時必須釐清其語意:
Object.ReferenceEquals(a, b) — 參考相等(同一物件實例)。Object.Equals(a, b) — 靜態相等,委派給 a.Equals(b)。a.Equals(b) — 實例相等,可覆寫定義值語意。a == b — 運算子相等,可多載(實值型別建議多載)。ReferenceEquals:永遠比較物件 ID,實值型別裝箱後永回傳 false,語意固定。Object.Equals(static):標準實作已正確處理 null、參考相等、委派給左運算元實例方法。覆寫反而破壞多型行為。a == a 恆真。a == b → b == a。a == b ∧ b == c → a == c。ValueType.Equals() 使用反射逐欄位比較,效能極差。string、DataRowView 比較內容而非參考。public override bool Equals(object right)
{
if (right == null) return false;
if (ReferenceEquals(this, right)) return true;
if (GetType() != right.GetType()) return false; // 嚴格型別檢查,保證對稱性
return CompareMembers(this, (MyType)right);
}
基類用 as Base、衍生類用 as Derived 會導致:
Base b = new Base();
Derived d = new Derived();
b.Equals(d) → 可能 true(d 隱式轉 Base)
d.Equals(b) → 永遠 false(b 無法轉 Derived)
// 破壞對稱性!
解法:GetType() != right.GetType() 嚴格檢查,或不支援跨型別相等比較。
Equals 應呼叫 base.Equals(right) 比較基類部分。base.Equals(right)(即 Object.Equals),而非直接比較參考。Equals() 行為不一致造成困擾。| 型別 | Instance Equals() | Operator == |
|---|---|---|
| 實值型別 | 必須覆寫(效能) | 必須多載(正確性+效能) |
| 參考型別(值語意) | 覆寫 | 不建議 |
| 參考型別(參考語意) | 不覆寫 | 不多載 |
覆寫 Equals() 時必須同步覆寫 GetHashCode()(原則 10)。