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 判斷相等:
`Product` 類別未覆寫 `Equals`/`GetHashCode`,故比較的是記憶體位址,導致「同 ID 不同實例」視為不相等。
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; }
}
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 一致
}
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 ✓
.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 必須穩定 |
若無法修改類別(第三方庫),或需不同比較邏輯(如不區分大小寫),可傳入自訂比較器:
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` 正確運作的基石。