核心概念區分
| 概念 | 定義 | 記憶體語意 | 必要條件 |
| Equality (相等) | 型別相同且所有欄位值相等 | 不同記憶體位址也可相等 | Reflexive (a==a)、Symmetric (a==b ⇒ b==a)、Transitive (a==b ∧ b==c ⇒ a==c) |
| Identity (恆等/同一性) | 參考同一個物件實例 | 共享同一記憶體位址 | ReferenceEquals 為 true |
四種比較機制
// 1. 靜態 ReferenceEquals - 永遠比較 Identity
public static bool ReferenceEquals(object left, object right);
// 2. 靜態 Object.Equals - 比較 Equality (內部呼叫實例 Equals)
public static bool Equals(object left, object right);
// 3. 實例虛擬 Equals - 型別自訂 Equality 邏輯
public virtual bool Equals(object right);
// 4. 運算子 == - 可多載,行為由型別定義
public static bool operator ==(MyType left, MyType right);
預設行為差異表
| 型別類別 | ReferenceEquals | Object.Equals (靜態/實例) | 運算子 == |
| 參考型別 (class) | 比較參考 | 比較參考 (同 ReferenceEquals) | 比較參考 (未多載時) |
| 實值型別 (struct) | **永遠 false** (裝箱產生不同物件) | **逐欄位值比較** (透過反射,較慢) | **不支援** (未多載無法編譯) |
| String (特殊參考型別) | 比較參考 | **比較字元內容** (已多載) | **比較字元內容** (已多載) |
正確實作 Equals / == / GetHashCode (實值型別範例)
public readonly struct Point : IEquatable
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) { X = x; Y = y; }
// 1. 強型別 Equals (避免裝箱)
public bool Equals(Point other) => X == other.X && Y == other.Y;
// 2. 覆寫 Object.Equals
public override bool Equals(object obj) => obj is Point p && Equals(p);
// 3. 覆寫 GetHashCode (哈希碼一致性:Equals 為 true ⇒ HashCode 相同)
public override int GetHashCode() => HashCode.Combine(X, Y); // .NET Core 2.1+
// 4. 多載 == / !=
public static bool operator ==(Point left, Point right) => left.Equals(right);
public static bool operator !=(Point left, Point right) => !left.Equals(right);
}
參考型別實作指南 (Entity 類別)
public class User : IEquatable
{
public Guid Id { get; init; }
public string Email { get; init; }
// 業務鍵相等 (如同一 Email 視為同一用戶)
public bool Equals(User other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return Email == other.Email; // 或 Id 比較
}
public override bool Equals(object obj) => Equals(obj as User);
public override int GetHashCode() => Email?.GetHashCode() ?? 0; // 不可變欄位
public static bool operator ==(User left, User right)
{
if (left is null) return right is null;
return left.Equals(right);
}
public static bool operator !=(User left, User right) => !(left == right);
}
多態陷阱:== vs Equals
object a = new Point(1,2);
object b = new Point(1,2);
Console.WriteLine(a == b); // false (編譯期型別 object,呼叫 object.== → 參考比較)
Console.WriteLine(a.Equals(b)); // true (虛擬呼叫 Point.Equals)
Console.WriteLine((Point)a == (Point)b); // true (編譯期型別 Point,呼叫 Point.==)
**結論**:泛型程式碼/物件型別變數**永遠用 `Equals`**,避免 `==` 多態失效。
ValueTuple / Record 現代簡化 (.NET 5+ / C# 9+)
// Record 自動產生 Equals/GetHashCode/==/!=/ToString/with-expression
public record Person(string Name, int Age, Address Address);
// ValueTuple 結構化相等
var t1 = (Name: "Tom", Age: 30);
var t2 = (Name: "Tom", Age: 30);
Console.WriteLine(t1 == t2); // true
最佳實務總結
- 實值型別:務必實作 `IEquatable`、`Equals`、`GetHashCode`、`==`/`!=`,避免反射效能損耗。
- 參考型別 (Entity):依業務鍵實作 `Equals`/`GetHashCode`,多載 `==`/`!=` 代理 `Equals`。
- 不可變型別:`GetHashCode` 使用不可變欄位,確保字典/集合正確運作。
- 泛型/物件變數:永遠用 `Equals`,不用 `==`。
- 集合/字典鍵:正確實作 `GetHashCode`,否則查找失效。
- Record/ValueTuple:優先使用現代語法自動生成正確相等邏輯。