| 函數/運算符 | 簽名/形式 | 歸屬 | 核心語義 |
|---|---|---|---|
| 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)` | 類型自定義運算符 | **語法糖值相等** (值類型推薦,引用類型慎用) |
// 僅比較地址
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
💡 **用途極窄**:僅用於「必須確認是否同一實例」場景 (如緩存鍵比較、單例檢查)。**不要用於值類型**。
框架參考實現邏輯:
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
}
特點:
標準重載模板 (值類型/引用類型通用):
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; // 比較所有標識字段
}
必須成對重載 (編譯器強制)。
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. 三者必須語義一致,否則集合/字典行為異常
// 自動生成: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。
| 場景 | ReferenceEquals | Object.Equals | 實例 Equals | 運算符 == |
|---|---|---|---|---|
| 值類型 | ❌ 永遠 false | ✅ 安全入口 | ✅ 重載 (配合 IEquatable) | ✅ **必須重載** (配合 Equals) |
| 引用類型 (實體) | ✅ 同一實例檢查 | ✅ 安全入口 | ✅ 重載 (比較 Id) | ❌ **不要重載** (保持引用比較) |
| 引用類型 (值對象) | ❌ 無意義 | ✅ | ✅ 重載 (全字段比較) | ⚠️ 謹慎重載 (需與 Equals 絕對一致) |