首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

《Effective C#》精華:.NET 四大判等函數深度解析與正確重載指南

     編輯:關於C#

.NET 判等四重奏

函數/運算符簽名/形式歸屬核心語義
ReferenceEquals`static bool ReferenceEquals(object, object)``System.Object` 靜態方法**引用同一性**:兩引用是否指向**同一堆對象**
靜態 Equals`static bool Equals(object, object)``System.Object` 靜態方法**空值安全的值相等**:處理 null → 調用實例 `Equals`
實例 Equals`virtual bool Equals(object obj)``System.Object` 虛方法**類型定義的值相等** (可重載)
運算符 ==`static bool operator ==(T, T)`類型自定義運算符**語法糖值相等** (值類型推薦,引用類型慎用)

1. `Object.ReferenceEquals`:純引用同一性

// 僅比較地址
object a = new object();
object b = a;
Object.ReferenceEquals(a, b); // true

// ⚠️ 值類型永遠返回 false (裝箱產生不同對象)
int n = 10;
Object.ReferenceEquals(n, n); // false! 兩次裝箱地址不同
Object.ReferenceEquals(n, 10); // false
💡 **用途極窄**:僅用於「必須確認是否同一實例」場景 (如緩存鍵比較、單例檢查)。**不要用於值類型**。

2. `Object.Equals(object, object)`:空值安全的靜態入口

框架參考實現邏輯:

public static bool Equals(object left, object right) {
    if (left == right) return true;        // 1. 同一引用 (含雙 null)
    if (left == null || right == null) return false; // 2. 單邊 null
    return left.Equals(right);             // 3. 委託給實例 Equals
}

特點:

  • **不拋出異常**,安全處理 `null`。
  • **依賴實例 `Equals` 和 `==`** 實現 (步驟 1 用 `==`)。
  • **值類型正常工作** (裝箱後調用重載的 `Equals`)。

3. 實例 `Equals(object)`:核心重載點

標準重載模板 (值類型/引用類型通用):

public override bool Equals(object obj) {
    // 1. null 檢查
    if (obj == null) return false;
    
    // 2. 同一引用快速返回
    if (Object.ReferenceEquals(this, obj)) return true;
    
    // 3. **類型精確匹配** (關鍵!防止子類通過 as 基類比較破壞傳遞性)
    if (this.GetType() != obj.GetType()) return false;
    
    // 4. 安全轉型比較核心字段
    var other = (MyType)obj; // 或 obj as MyType (已保證類型同)
    return this.Id == other.Id 
        && this.Name == other.Name; // 比較所有標識字段
}

⚠️ 關鍵點:`GetType()` vs `is`/`as`

  • **必須用 `GetType()` 精確匹配**。用 `obj is MyType` 會導致子類實例通過檢查,破壞**傳遞性** (a==b, b==c 但 a!=c)。
  • 值類型**不建議重載此方法**:參數為 `object` 導致**裝箱**,性能損耗。改用強類型 `Equals(T)` + `IEquatable`。

4. 運算符 `==` / `!=`:語法糖與強類型契約

必須成對重載 (編譯器強制)。

值類型標準實現 (推薦)

public struct Point : IEquatable {
    public int X, Y;
    
    // 強類型 Equals (無裝箱,高性能)
    public bool Equals(Point other) => X == other.X && Y == other.Y;
    public override bool Equals(object obj) => obj is Point p && Equals(p);
    public override int GetHashCode() => HashCode.Combine(X, Y);
    
    // 運算符 ==/!=
    public static bool operator ==(Point left, Point right) => left.Equals(right);
    public static bool operator !=(Point left, Point right) => !left.Equals(right);
}

引用類型:為什麼不建議重載 `==`?

// ❌ 反例:引用類型重載 ==
public class Person {
    public string Name { get; set; }
    public static bool operator ==(Person a, Person b) {
        if (ReferenceEquals(a, b)) return true;
        if (a is null || b is null) return false;
        return a.Name == b.Name;
    }
    public static bool operator !=(Person a, Person b) => !(a == b);
}

// 陷阱:
var p1 = new Person { Name = "A" };
var p2 = new Person { Name = "A" };
Console.WriteLine(p1 == p2); // true (值相等)
Console.WriteLine(Object.ReferenceEquals(p1, p2)); // false (不同實例)
// 混淆:集合 Contains 用 Equals,Dictionary Key 用 GetHashCode/Equals
// 若 == 與 Equals 不一致 → 極難排查 Bug
💡 **鐵律**:**引用類型保持 `==` 為引用比較** (默認行為),值相等用 `Equals`。值類型重載 `==` 配合 `Equals` 保持一致。

判等函數相互關係與調用鏈

// 調用鏈示意
Object.Equals(a, b) 
    → (a == b)  // 步驟1: 引用同一性 (引用類型用運算符==, 值類型總是false除非同一實例)
    → (a == null || b == null) // 步驟2: null檢查 (同樣用==)
    → a.Equals(b) // 步驟3: 實例Equals (虛調用)

// 因此:
// 1. 重載實例 Equals 會影響 Object.Equals 結果
// 2. 重載運算符 == 會影響 Object.Equals 步驟1/2 結果
// 3. 三者必須語義一致,否則集合/字典行為異常

正確重載判等完整清單 (值類型)

  1. 實現 `IEquatable`:`bool Equals(T other)` (強類型、無裝箱)。
  2. 重載 `Equals(object)`:`obj is T t && Equals(t)`。
  3. 重載 `GetHashCode()`:`HashCode.Combine(字段...)` (C# 7.0+) 或手工組合。
  4. 重載 `==` / `!=`:委託 `Equals`。
  5. (可選) 實現 `IComparable` / `IComparable` 排序支持。

引用類型判等指南

  • **實體類型** (有標識 Id):重載 `Equals`/`GetHashCode` 比較 **Id**,**不重載 `==`**。
  • **值對象** (無標識、全字段定義相等):**改用 `record` (C# 9) 或 `struct`**。
  • **不可變引用類型** 罕見需值相等:謹慎重載 `==`/`Equals` 並**嚴格保持一致**,文檔標註。

現代 C# 簡化:`record` 類型 (C# 9+)

// 自動生成:Equals/GetHashCode/==/!=/with-expression/ToString
public record Person(string Name, int Age); 

// 等價於手寫 50+ 行樣板代碼
var p1 = new Person("A", 10);
var p2 = new Person("A", 10);
Console.WriteLine(p1 == p2);     // true (值相等)
Console.WriteLine(p1.Equals(p2)); // true
Console.WriteLine(ReferenceEquals(p1, p2)); // false
💡 **新項目首選 `record` 定義數據載體**,徹底消除判等樣板代碼與一致性 Bug。

最佳實踐總結表

場景ReferenceEqualsObject.Equals實例 Equals運算符 ==
值類型❌ 永遠 false✅ 安全入口✅ 重載 (配合 IEquatable)✅ **必須重載** (配合 Equals)
引用類型 (實體)✅ 同一實例檢查✅ 安全入口✅ 重載 (比較 Id)❌ **不要重載** (保持引用比較)
引用類型 (值對象)❌ 無意義✅✅ 重載 (全字段比較)⚠️ 謹慎重載 (需與 Equals 絕對一致)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved