C#中實(shí)現(xiàn)值相等(Value Equality)的詳細(xì)步驟
一、為什么“值相等”是一個(gè)需要認(rèn)真對(duì)待的問(wèn)題
在 C# 中,相等并不是一個(gè)簡(jiǎn)單的問(wèn)題。
很多開(kāi)發(fā)者認(rèn)為重寫(xiě) Equals 就夠了,但在真實(shí)系統(tǒng)中,錯(cuò)誤或不完整的相等實(shí)現(xiàn)會(huì)導(dǎo)致:
Dictionary/HashSet行為異常- 對(duì)象“看起來(lái)相等”,但集合中卻當(dāng)作不相等
==、Equals、Contains行為不一致- 隱蔽而難以排查的 Bug
這背后的原因在于:
.NET 的相等語(yǔ)義是一個(gè)由多個(gè)方法和接口共同構(gòu)成的協(xié)作體系,而不是單一方法。
本文將從底層機(jī)制出發(fā),給出標(biāo)準(zhǔn)、完整、可復(fù)用的實(shí)現(xiàn)步驟。
二、相等的兩種語(yǔ)義:引用相等 vs 值相等
在 .NET 中,存在兩種本質(zhì)不同的“相等”:
1. 引用相等(Reference Equality)
object.ReferenceEquals(a, b)
- 判斷兩個(gè)變量是否指向同一對(duì)象實(shí)例
- 類(reference type)的默認(rèn)行為
2. 值相等(Value Equality)
- 判斷兩個(gè)對(duì)象的數(shù)據(jù)內(nèi)容是否相同
- 由開(kāi)發(fā)者顯式定義和實(shí)現(xiàn)
var a = new Person("Tom", 18);
var b = new Person("Tom", 18);
ReferenceEquals(a, b); // false
a.Equals(b); // true(如果實(shí)現(xiàn)了值相等)
三、.NET 相等體系的整體結(jié)構(gòu)
實(shí)現(xiàn)值相等,必須理解以下四個(gè)關(guān)鍵成員的職責(zé)分工:
| 成員 | 角色 |
|---|---|
IEquatable<T>.Equals(T) | 類型安全、性能最優(yōu)的相等判斷 |
object.Equals(object) | 所有 .NET API 的統(tǒng)一入口 |
GetHashCode() | 哈希集合的基礎(chǔ) |
== / != 運(yùn)算符 | 語(yǔ)法層面的相等判斷(可選) |
一個(gè)正確的值相等實(shí)現(xiàn),必須保證這些成員在語(yǔ)義上一致。
四、類(引用類型)實(shí)現(xiàn)值相等的標(biāo)準(zhǔn)步驟
以下步驟適用于絕大多數(shù)引用類型(class)。
Step 1:明確“相等”的語(yǔ)義(設(shè)計(jì)階段)
首先必須回答一個(gè)設(shè)計(jì)問(wèn)題:
哪些字段決定兩個(gè)對(duì)象在業(yè)務(wù)語(yǔ)義上是“相等”的?
例如:
Person 相等 ? Name 和 Age 都相等
這一步?jīng)]有代碼,但至關(guān)重要。
Step 2:實(shí)現(xiàn)IEquatable<T>.Equals(T other)(核心步驟)
public sealed class Person : IEquatable<Person>
{
public string Name { get; }
public int Age { get; }
public bool Equals(Person other)
{
if (other is null) return false;
return Name == other.Name && Age == other.Age;
}
}
為什么這是核心?
- 泛型集合(
HashSet<T>、Dictionary<TKey, TValue>)優(yōu)先調(diào)用它 - 避免裝箱(boxing),性能優(yōu)于
object.Equals - 提供類型安全的比較語(yǔ)義
IEquatable<T> 是值相等的主入口。
Step 3:重寫(xiě)object.Equals(object obj)(必須)
public override bool Equals(object obj)
{
return Equals(obj as Person);
}
為什么必須?
- 大量 .NET API 只接受
object object.Equals(a, b)、非泛型集合依賴它- 保證所有調(diào)用路徑的相等邏輯一致
規(guī)范要求:
Equals(object) 必須委托給 Equals(T),而不是重復(fù)實(shí)現(xiàn)邏輯。
Step 4:重寫(xiě)GetHashCode()(必須)
public override int GetHashCode()
{
return HashCode.Combine(Name, Age);
}
必須遵守的核心約束
如果 a.Equals(b) 為 true,
那么 a.GetHashCode() 必須等于 b.GetHashCode()。
否則:
HashSet<T>會(huì)包含重復(fù)元素Dictionary<TKey, TValue>無(wú)法正確查找 key
實(shí)踐建議
- 使用參與相等比較的字段
- 避免使用可變字段
- 不要依賴
string.GetHashCode()的持久性
Step 5(可選但推薦):重載== / !=運(yùn)算符
public static bool operator ==(Person left, Person right)
{
return object.Equals(left, right);
}
public static bool operator !=(Person left, Person right)
{
return !object.Equals(left, right);
}
說(shuō)明
- 默認(rèn)情況下,類的
==比較的是引用 - 重載后可使
==與值相等語(yǔ)義一致 object.Equals已處理所有null情況,是最安全的寫(xiě)法
五、完整標(biāo)準(zhǔn)實(shí)現(xiàn)模板
public sealed class Person : IEquatable<Person>
{
public string Name { get; }
public int Age { get; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public bool Equals(Person other)
{
if (other is null) return false;
return Name == other.Name && Age == other.Age;
}
public override bool Equals(object obj)
=> Equals(obj as Person);
public override int GetHashCode()
=> HashCode.Combine(Name, Age);
public static bool operator ==(Person left, Person right)
=> object.Equals(left, right);
public static bool operator !=(Person left, Person right)
=> !object.Equals(left, right);
}
六、結(jié)構(gòu)體(值類型)的補(bǔ)充說(shuō)明
struct默認(rèn)按字段比較,但使用反射,性能較低- 推薦同樣實(shí)現(xiàn)
IEquatable<T>:
public struct Point : IEquatable<Point>
{
public int X;
public int Y;
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);
}
七、record:值相等的語(yǔ)言級(jí)支持(C# 9+)
public record Person(string Name, int Age);
編譯器自動(dòng)生成:
IEquatable<T>Equals(object)GetHashCode== / !=- 不可變?cè)O(shè)計(jì)
對(duì)于值對(duì)象(Value Object),record 是首選方案。
八、常見(jiàn)錯(cuò)誤總結(jié)
- 只實(shí)現(xiàn)
IEquatable<T>,不重寫(xiě)Equals(object) - 重寫(xiě)
Equals,但忘記GetHashCode ==與Equals語(yǔ)義不一致- 在
GetHashCode中使用可變字段 - 在
==中直接調(diào)用left.Equals(right)
九、結(jié)論
在 C# 中,實(shí)現(xiàn)值相等不是“重寫(xiě)一個(gè)方法”,
而是一個(gè)需要遵循明確步驟和約束的完整設(shè)計(jì)。
標(biāo)準(zhǔn)流程可以總結(jié)為:
- 明確定義相等語(yǔ)義
- 實(shí)現(xiàn)
IEquatable<T>.Equals(T) - 重寫(xiě)
object.Equals(object) - 重寫(xiě)
GetHashCode() - (可選)重載
== / !=
這套實(shí)現(xiàn),才能在語(yǔ)言層、集合層和框架層都保持一致、可靠的行為。
以上就是C#中實(shí)現(xiàn)值相等(Value Equality)的詳細(xì)步驟的詳細(xì)內(nèi)容,更多關(guān)于C#實(shí)現(xiàn)值相等Value Equality的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
同步調(diào)用和異步調(diào)用WebService
本文給大家介紹webservice同步調(diào)用和異步調(diào)用,同步調(diào)用就是一個(gè)同步操作會(huì)阻塞整個(gè)當(dāng)前的進(jìn)程,直到這個(gè)操作完成才能執(zhí)行下一段代碼,異步調(diào)用不會(huì)阻塞啟動(dòng)操作的調(diào)用線程,調(diào)用程序必須通過(guò)輪流檢測(cè),或者等待完成信號(hào)來(lái)發(fā)現(xiàn)調(diào)用的完成。小伙伴們跟著小編一起學(xué)習(xí)2015-09-09
C# Winform按鈕中圖片實(shí)現(xiàn)左圖右字的效果實(shí)例
這篇文章主要給大家介紹了關(guān)于C# Winform按鈕中圖片實(shí)現(xiàn)左圖右字效果的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Unity打開(kāi)淘寶app并跳轉(zhuǎn)到商品頁(yè)面功能的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于如何利用Unity打開(kāi)淘寶app并跳轉(zhuǎn)到商品頁(yè)面功能的相關(guān)資料,這個(gè)功能目前在網(wǎng)上找不到相關(guān)的解決方法,所以自己寫(xiě)了出來(lái),需要的朋友可以參考下2021-07-07
C#播放鈴聲最簡(jiǎn)單實(shí)現(xiàn)方法
這篇文章主要介紹了C#播放鈴聲最簡(jiǎn)單實(shí)現(xiàn)方法,通過(guò)調(diào)用系統(tǒng)方法實(shí)現(xiàn)播放wav格式音頻文件的功能,是非常實(shí)用的技巧,需要的朋友可以參考下2014-12-12

