| 概念 | 定義 | 關鍵特徵 |
|---|---|---|
| Equality (值相等) | 兩個物件型別相同,且所有欄位值皆相等 | 自反性、對稱性、傳遞性 |
| Identity (參考恆等) | 兩個參考指向同一個記憶體位址 | 記憶體位址相同 |
Object.ReferenceEquals(a, b) — 純參考比較,忽略型別覆寫,值型別會裝箱 ⇒ 永遠回傳 false。Object.Equals(a, b) (static) — Null 安全的相等比較,內部呼叫實例 Equals。instance.Equals(other) (virtual) — 語意相等比較,可覆寫實作值比較邏輯。operator ==(a, b) — 運算子多載,編譯期靜態綁定,非多態。// 值型別 (struct)
struct Point { public int X, Y; }
var p1 = new Point { X = 1, Y = 2 };
var p2 = new Point { X = 1, Y = 2 };
Console.WriteLine(p1.Equals(p2)); // true (ValueType.Equals 逐欄位比較)
Console.WriteLine(p1 == p2); // 編譯錯誤,需自行多載 ==
Console.WriteLine(Object.ReferenceEquals(p1, p2)); // false (裝箱成不同物件)
// 參考型別 (class)
class Person { public string Name; }
var a = new Person { Name = "Tom" };
var b = a;
var c = new Person { Name = "Tom" };
Console.WriteLine(a == b); // true (同一參考)
Console.WriteLine(a == c); // false (不同參考,預設比較參考)
Console.WriteLine(a.Equals(c)); // false (預設同參考比較)
Console.WriteLine(Object.ReferenceEquals(a, c)); // false
string 是參考型別,但覆寫了 Equals 與 == 實作值語意:
string s1 = "hello";
string s2 = "he" + "llo";
Console.WriteLine(s1 == s2); // true (內容相等)
Console.WriteLine(Object.ReferenceEquals(s1, s2)); // true (字串暫存池最佳化)
public class Employee : IEquatable<Employee>
{
public int Id { get; }
public string Name { get; }
public Employee(int id, string name) => (Id, Name) = (id, name);
// 1. 實作 IEquatable<T> 強型別比較
public bool Equals(Employee other)
{
if (other is null) return false;
return Id == other.Id && Name == other.Name;
}
// 2. 覆寫 Object.Equals
public override bool Equals(object obj) => Equals(obj as Employee);
// 3. 覆寫 GetHashCode (配合 Dictionary/HashSet 使用)
public override int GetHashCode() => HashCode.Combine(Id, Name);
// 4. 多載 == / != (建議)
public static bool operator ==(Employee a, Employee b)
{
if (ReferenceEquals(a, b)) return true;
if (a is null || b is null) return false;
return a.Equals(b);
}
public static bool operator !=(Employee a, Employee b) => !(a == b);
}
Animal a1 = new Dog("Lucky");
Animal a2 = new Dog("Lucky");
Console.WriteLine(a1.Equals(a2)); // true (虛擬呼叫 Dog.Equals)
Console.WriteLine(a1 == a2); // false (編譯期綁定 Animal.operator==,預設參考比較)
結論:若型別實作值語意,務必同時覆寫 Equals、GetHashCode、多載 ==/!=,並實作 IEquatable<T> 避免裝箱。
SequenceEqual 或自訂 `IEqualityComparer<T>`。釐清 Equality 與 Identity 的差異,掌握四種比較機制的適用場景,並遵循標準覆寫模式,是撰寫正確、高效、可維護 C# 程式碼的關鍵基石。