董明德 發表於 2026-1-12 08:59:53

C#中实现值相等(Value Equality)的详细步骤

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>一、为什么&ldquo;值相等&rdquo;是一个需要认真对待的问题</li><li>二、相等的两种语义:引用相等 vs 值相等</li><ul class="second_class_ul"><li>1. 引用相等(Reference Equality)</li><li>2. 值相等(Value Equality)</li></ul><li>三、.NET 相等体系的整体结构</li><ul class="second_class_ul"></ul><li>四、类(引用类型)实现值相等的标准步骤</li><ul class="second_class_ul"><li>Step 1:明确&ldquo;相等&rdquo;的语义(设计阶段)</li><li>Step 2:实现IEquatable&lt;T&gt;.Equals(T other)(核心步骤)</li><ul class="third_class_ul"><li>为什么这是核心?</li></ul><li>Step 3:重写object.Equals(object obj)(必须)</li><ul class="third_class_ul"><li>为什么必须?</li></ul><li>Step 4:重写GetHashCode()(必须)</li><ul class="third_class_ul"><li>必须遵守的核心约束</li><li>实践建议</li></ul><li>Step 5(可选但推荐):重载== / !=运算符</li><ul class="third_class_ul"><li>说明</li></ul></ul><li>五、完整标准实现模板</li><ul class="second_class_ul"></ul><li>六、结构体(值类型)的补充说明</li><ul class="second_class_ul"></ul><li>七、record:值相等的语言级支持(C# 9+)</li><ul class="second_class_ul"></ul><li>八、常见错误总结</li><ul class="second_class_ul"></ul><li>九、结论</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>一、为什么&ldquo;值相等&rdquo;是一个需要认真对待的问题</h2>
<p>在 C# 中,<strong>相等并不是一个简单的问题</strong>。<br />很多开发者认为重写 <code>Equals</code> 就够了,但在真实系统中,错误或不完整的相等实现会导致:</p>
<ul><li><code>Dictionary</code> / <code>HashSet</code> 行为异常</li><li>对象&ldquo;看起来相等&rdquo;,但集合中却当作不相等</li><li><code>==</code>、<code>Equals</code>、<code>Contains</code> 行为不一致</li><li>隐蔽而难以排查的 Bug</li></ul>
<p>这背后的原因在于:</p>
<blockquote><p><strong>.NET 的相等语义是一个由多个方法和接口共同构成的协作体系,而不是单一方法。</strong></p></blockquote>
<p>本文将从底层机制出发,给出<strong>标准、完整、可复用的实现步骤</strong>。</p>
<p class="maodian"></p><h2>二、相等的两种语义:引用相等 vs 值相等</h2>
<p>在 .NET 中,存在两种本质不同的&ldquo;相等&rdquo;:</p>
<p class="maodian"></p><h3>1. 引用相等(Reference Equality)</h3>
<div class="jb51code"><pre class="brush:csharp;">object.ReferenceEquals(a, b)
</pre></div>
<ul><li>判断两个变量是否指向<strong>同一对象实例</strong></li><li>类(reference type)的默认行为</li></ul>
<p class="maodian"></p><h3>2. 值相等(Value Equality)</h3>
<ul><li>判断两个对象的<strong>数据内容是否相同</strong></li><li>由开发者显式定义和实现</li></ul>
<div class="jb51code"><pre class="brush:csharp;">var a = new Person("Tom", 18);
var b = new Person("Tom", 18);

ReferenceEquals(a, b); // false
a.Equals(b);         // true(如果实现了值相等)
</pre></div>
<p class="maodian"></p><h2>三、.NET 相等体系的整体结构</h2>
<p>实现值相等,必须理解以下四个关键成员的职责分工:</p>
<table><thead><tr><th>成员</th><th>角色</th></tr></thead><tbody><tr><td><code>IEquatable&lt;T&gt;.Equals(T)</code></td><td>类型安全、性能最优的相等判断</td></tr><tr><td><code>object.Equals(object)</code></td><td>所有 .NET API 的统一入口</td></tr><tr><td><code>GetHashCode()</code></td><td>哈希集合的基础</td></tr><tr><td><code>== / !=</code> 运算符</td><td>语法层面的相等判断(可选)</td></tr></tbody></table>
<p><strong>一个正确的值相等实现,必须保证这些成员在语义上一致。</strong></p>
<p class="maodian"></p><h2>四、类(引用类型)实现值相等的标准步骤</h2>
<p>以下步骤适用于<strong>绝大多数引用类型(class)</strong>。</p>
<p class="maodian"></p><h3>Step 1:明确&ldquo;相等&rdquo;的语义(设计阶段)</h3>
<p>首先必须回答一个设计问题:</p>
<blockquote><p><strong>哪些字段决定两个对象在业务语义上是&ldquo;相等&rdquo;的?</strong></p></blockquote>
<p>例如:</p>
<div class="jb51code"><pre class="brush:csharp;">Person 相等 ⇔ Name 和 Age 都相等
</pre></div>
<p>这一步没有代码,但至关重要。</p>
<p class="maodian"></p><h3>Step 2:实现IEquatable&lt;T&gt;.Equals(T other)(核心步骤)</h3>
<div class="jb51code"><pre class="brush:csharp;">public sealed class Person : IEquatable&lt;Person&gt;
{
    public string Name { get; }
    public int Age { get; }

    public bool Equals(Person other)
    {
      if (other is null) return false;
      return Name == other.Name &amp;&amp; Age == other.Age;
    }
}
</pre></div>
<p class="maodian"></p><h4>为什么这是核心?</h4>
<ul><li>泛型集合(<code>HashSet&lt;T&gt;</code>、<code>Dictionary&lt;TKey, TValue&gt;</code>)<strong>优先调用它</strong></li><li>避免装箱(boxing),性能优于 <code>object.Equals</code></li><li>提供类型安全的比较语义</li></ul>
<blockquote><p><strong>IEquatable&lt;T&gt; 是值相等的主入口。</strong></p></blockquote>
<p class="maodian"></p><h3>Step 3:重写object.Equals(object obj)(必须)</h3>
<div class="jb51code"><pre class="brush:csharp;">public override bool Equals(object obj)
{
    return Equals(obj as Person);
}
</pre></div>
<p class="maodian"></p><h4>为什么必须?</h4>
<ul><li>大量 .NET API 只接受 <code>object</code></li><li><code>object.Equals(a, b)</code>、非泛型集合依赖它</li><li>保证所有调用路径的相等逻辑一致</li></ul>
<p><strong>规范要求</strong>:</p>
<blockquote><p>Equals(object) 必须委托给 Equals(T),而不是重复实现逻辑。</p></blockquote>
<p class="maodian"></p><h3>Step 4:重写GetHashCode()(必须)</h3>
<div class="jb51code"><pre class="brush:csharp;">public override int GetHashCode()
{
    return HashCode.Combine(Name, Age);
}
</pre></div>
<p class="maodian"></p><h4>必须遵守的核心约束</h4>
<blockquote><p><strong>如果 a.Equals(b) 为 true,<br />那么 a.GetHashCode() 必须等于 b.GetHashCode()。</strong></p></blockquote>
<p>否则:</p>
<ul><li><code>HashSet&lt;T&gt;</code> 会包含重复元素</li><li><code>Dictionary&lt;TKey, TValue&gt;</code> 无法正确查找 key</li></ul>
<p class="maodian"></p><h4>实践建议</h4>
<ul><li>使用参与相等比较的字段</li><li>避免使用可变字段</li><li>不要依赖 <code>string.GetHashCode()</code> 的持久性</li></ul>
<p class="maodian"></p><h3>Step 5(可选但推荐):重载== / !=运算符</h3>
<div class="jb51code"><pre class="brush:csharp;">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);
}
</pre></div>
<p class="maodian"></p><h4>说明</h4>
<ul><li>默认情况下,类的 <code>==</code> 比较的是引用</li><li>重载后可使 <code>==</code> 与值相等语义一致</li><li><code>object.Equals</code> 已处理所有 <code>null</code> 情况,是最安全的写法</li></ul>
<p class="maodian"></p><h2>五、完整标准实现模板</h2>
<div class="jb51code"><pre class="brush:csharp;">public sealed class Person : IEquatable&lt;Person&gt;
{
    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 &amp;&amp; Age == other.Age;
    }

    public override bool Equals(object obj)
      =&gt; Equals(obj as Person);

    public override int GetHashCode()
      =&gt; HashCode.Combine(Name, Age);

    public static bool operator ==(Person left, Person right)
      =&gt; object.Equals(left, right);

    public static bool operator !=(Person left, Person right)
      =&gt; !object.Equals(left, right);
}
</pre></div>
<p class="maodian"></p><h2>六、结构体(值类型)的补充说明</h2>
<ul><li><code>struct</code> 默认按字段比较,但使用反射,性能较低</li><li>推荐同样实现 <code>IEquatable&lt;T&gt;</code>:</li></ul>
<div class="jb51code"><pre class="brush:csharp;">public struct Point : IEquatable&lt;Point&gt;
{
    public int X;
    public int Y;

    public bool Equals(Point other)
      =&gt; X == other.X &amp;&amp; Y == other.Y;

    public override bool Equals(object obj)
      =&gt; obj is Point p &amp;&amp; Equals(p);

    public override int GetHashCode()
      =&gt; HashCode.Combine(X, Y);
}
</pre></div>
<p class="maodian"></p><h2>七、record:值相等的语言级支持(C# 9+)</h2>
<div class="jb51code"><pre class="brush:csharp;">public record Person(string Name, int Age);
</pre></div>
<p>编译器自动生成:</p>
<ul><li><code>IEquatable&lt;T&gt;</code></li><li><code>Equals(object)</code></li><li><code>GetHashCode</code></li><li><code>== / !=</code></li><li>不可变设计</li></ul>
<p><strong>对于值对象(Value Object),record 是首选方案。</strong></p>
<p class="maodian"></p><h2>八、常见错误总结</h2>
<ul><li>只实现 <code>IEquatable&lt;T&gt;</code>,不重写 <code>Equals(object)</code></li><li>重写 <code>Equals</code>,但忘记 <code>GetHashCode</code></li><li><code>==</code> 与 <code>Equals</code> 语义不一致</li><li>在 <code>GetHashCode</code> 中使用可变字段</li><li>在 <code>==</code> 中直接调用 <code>left.Equals(right)</code></li></ul>
<p class="maodian"></p><h2>九、结论</h2>
<blockquote><p><strong>在 C# 中,实现值相等不是&ldquo;重写一个方法&rdquo;,<br />而是一个需要遵循明确步骤和约束的完整设计。</strong></p></blockquote>
<p>标准流程可以总结为:</p>
<ol><li>明确定义相等语义</li><li>实现 <code>IEquatable&lt;T&gt;.Equals(T)</code></li><li>重写 <code>object.Equals(object)</code></li><li>重写 <code>GetHashCode()</code></li><li>(可选)重载 <code>== / !=</code></li></ol>
<p>这套实现,才能在语言层、集合层和框架层都保持一致、可靠的行为。</p>
<p>以上就是C#中实现值相等(Value Equality)的详细步骤的详细内容,更多关于C#实现值相等Value Equality的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>C# 如何实现一个基于值相等性比较的字典</li><li>C#&nbsp;空值处理运算符??、?.&nbsp;及其它常用符号</li><li>C#中字符串插值($)&nbsp;和&nbsp;逐字字符串(@)的使用</li><li>C#窗体间传值的两种实现方法</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: C#中实现值相等(Value Equality)的详细步骤