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

C# List.Remove 失效之謎:物件相等性比較與 IEquatable 實作指南

     編輯:關於C語言

現象重現:為什麼 Remove 沒移除?

var products = new List<Product>();
products.Add(GetProduct("1412"));      // 新增實例 A
products.Remove(GetProduct("1412"));   // 嘗試移除實例 B
Console.WriteLine(products.Count);     // 輸出 1 —— 沒移除成功!

GetProduct 每次呼叫都從資料庫建立新實例,雖然 ID 相同,但參考不同。

根本原因:預設相等性比較邏輯

List<T>.Remove 內部使用 EqualityComparer<T>.Default 判斷相等:

  1. 若 `T` 實作 `IEquatable<T>` → 呼叫其 `Equals(T other)`
  2. 否則 → 使用 `Object.Equals`(參考相等)與 `GetHashCode`

`Product` 類別未覆寫 `Equals`/`GetHashCode`,故比較的是記憶體位址,導致「同 ID 不同實例」視為不相等。

Product 類別原始碼

class Product
{
    public Product(string id) { m_ID = id; }
    private string m_ID;
    public string ID { get { return m_ID; } }
    public override string ToString() { return "ID: " + m_ID; }
}

兩種修正方案

方案一:實作 IEquatable<T>(推薦,效能最佳)

class Product : IEquatable<Product>
{
    public Product(string id) { ID = id; }
    public string ID { get; }

    public bool Equals(Product other)
    {
        if (other is null) return false;
        return ID == other.ID; // 業務鍵比較
    }

    public override bool Equals(object obj) => Equals(obj as Product);
    public override int GetHashCode() => ID?.GetHashCode() ?? 0; // 必須與 Equals 一致
}

方案二:覆寫 Object.Equals 與 GetHashCode

class Product
{
    public Product(string id) { ID = id; }
    public string ID { get; }

    public override bool Equals(object obj)
    {
        return obj is Product other && ID == other.ID;
    }

    public override int GetHashCode() => ID?.GetHashCode() ?? 0;
}

驗證修正結果

var products = new List<Product>();
products.Add(new Product("1412"));
products.Remove(new Product("1412")); // 現在能移除
Console.WriteLine(products.Count);    // 輸出 0 ✓

深度解析:EqualityComparer<T> 內部機制

.NET 內建多種比較器:

  • GenericEqualityComparer<T>:`T` 實作 `IEquatable<T>` 時使用
  • ObjectEqualityComparer<T>:回退至 `Object.Equals`/`GetHashCode`
  • ByteEqualityComparer、`NullableEqualityComparer<T>` 等特化版本

所有比較器最終皆呼叫 `Equals`,因此正確實作 Equals/GetHashCode 是萬靈丹。

集合類型差異補充

集合比較機制修正生效條件
List<T>EqualityComparer<T>.Default實作 `IEquatable<T>` 或覆寫 `Equals`/`GetHashCode`
ArrayList`Object.Equals`僅覆寫 `Equals`/`GetHashCode` 有效(非泛型)
HashSet<T>/Dictionary<TKey,TValue>EqualityComparer<T>.Default + HashCode同 List,且 GetHashCode 必須穩定

最佳實踐清單

  1. 值語意類型(DTO、Entity、Value Object)必須實作 `IEquatable<T>` + 覆寫 `Equals`/`GetHashCode`。
  2. GetHashCode 必須不可變:僅基於不可變欄位(如 ID)計算;若欄位可變,放入 `HashSet`/`Dictionary` 後修改會導致找不到。
  3. 一致性:`Equals` 回傳 `true` 的兩個物件,`GetHashCode` 必須相同;反向不一定。
  4. 效能:`IEquatable<T>` 避免裝箱/拆箱,泛型集合效能更好。
  5. 工具輔助:Visual Studio / Rider / ReSharper 皆可自動產生 `Equals`/`GetHashCode` 實作(Alt+Insert → Equality members)。

進階:自訂 IEqualityComparer<T>

若無法修改類別(第三方庫),或需不同比較邏輯(如不區分大小寫),可傳入自訂比較器:

var set = new HashSet<Product>(new ProductIdComparer());

class ProductIdComparer : IEqualityComparer<Product>
{
    public bool Equals(Product x, Product y) => x?.ID == y?.ID;
    public int GetHashCode(Product obj) => obj?.ID?.GetHashCode() ?? 0;
}

總結

`List.Remove` 失效 99% 是物件相等性定義缺失。養成習慣:凡代表「同一實體」的類別,必寫 Equals/GetHashCode。這不僅修復 Remove,更是 `HashSet`、`Dictionary`、LINQ `Distinct`/`GroupBy` 正確運作的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved