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

 更新时间:2026年01月12日 08:59:53   作者:无风听海  
在 C# 中,相等并不是一个简单的问题,很多开发者认为重写 Equals 就够了,但在真实系统中,错误或不完整的相等实现会导致一些bug,本文将从底层机制出发,给出标准、完整、可复用的值相等实现步骤,需要的朋友可以参考下

一、为什么“值相等”是一个需要认真对待的问题

在 C# 中,相等并不是一个简单的问题
很多开发者认为重写 Equals 就够了,但在真实系统中,错误或不完整的相等实现会导致:

  • Dictionary / HashSet 行为异常
  • 对象“看起来相等”,但集合中却当作不相等
  • ==EqualsContains 行为不一致
  • 隐蔽而难以排查的 Bug

这背后的原因在于:

.NET 的相等语义是一个由多个方法和接口共同构成的协作体系,而不是单一方法。

本文将从底层机制出发,给出标准、完整、可复用的实现步骤

二、相等的两种语义:引用相等 vs 值相等

在 .NET 中,存在两种本质不同的“相等”:

1. 引用相等(Reference Equality)

object.ReferenceEquals(a, b)
  • 判断两个变量是否指向同一对象实例
  • 类(reference type)的默认行为

2. 值相等(Value Equality)

  • 判断两个对象的数据内容是否相同
  • 由开发者显式定义和实现
var a = new Person("Tom", 18);
var b = new Person("Tom", 18);

ReferenceEquals(a, b); // false
a.Equals(b);           // true(如果实现了值相等)

三、.NET 相等体系的整体结构

实现值相等,必须理解以下四个关键成员的职责分工:

成员角色
IEquatable<T>.Equals(T)类型安全、性能最优的相等判断
object.Equals(object)所有 .NET API 的统一入口
GetHashCode()哈希集合的基础
== / != 运算符语法层面的相等判断(可选)

一个正确的值相等实现,必须保证这些成员在语义上一致。

四、类(引用类型)实现值相等的标准步骤

以下步骤适用于绝大多数引用类型(class)

Step 1:明确“相等”的语义(设计阶段)

首先必须回答一个设计问题:

哪些字段决定两个对象在业务语义上是“相等”的?

例如:

Person 相等 ⇔ Name 和 Age 都相等

这一步没有代码,但至关重要。

Step 2:实现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>优先调用它
  • 避免装箱(boxing),性能优于 object.Equals
  • 提供类型安全的比较语义

IEquatable<T> 是值相等的主入口。

Step 3:重写object.Equals(object obj)(必须)

public override bool Equals(object obj)
{
    return Equals(obj as Person);
}

为什么必须?

  • 大量 .NET API 只接受 object
  • object.Equals(a, b)、非泛型集合依赖它
  • 保证所有调用路径的相等逻辑一致

规范要求

Equals(object) 必须委托给 Equals(T),而不是重复实现逻辑。

Step 4:重写GetHashCode()(必须)

public override int GetHashCode()
{
    return HashCode.Combine(Name, Age);
}

必须遵守的核心约束

如果 a.Equals(b) 为 true,
那么 a.GetHashCode() 必须等于 b.GetHashCode()。

否则:

  • HashSet<T> 会包含重复元素
  • Dictionary<TKey, TValue> 无法正确查找 key

实践建议

  • 使用参与相等比较的字段
  • 避免使用可变字段
  • 不要依赖 string.GetHashCode() 的持久性

Step 5(可选但推荐):重载== / !=运算符

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);
}

说明

  • 默认情况下,类的 == 比较的是引用
  • 重载后可使 == 与值相等语义一致
  • object.Equals 已处理所有 null 情况,是最安全的写法

五、完整标准实现模板

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);
}

六、结构体(值类型)的补充说明

  • struct 默认按字段比较,但使用反射,性能较低
  • 推荐同样实现 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:值相等的语言级支持(C# 9+)

public record Person(string Name, int Age);

编译器自动生成:

  • IEquatable<T>
  • Equals(object)
  • GetHashCode
  • == / !=
  • 不可变设计

对于值对象(Value Object),record 是首选方案。

八、常见错误总结

  • 只实现 IEquatable<T>,不重写 Equals(object)
  • 重写 Equals,但忘记 GetHashCode
  • ==Equals 语义不一致
  • GetHashCode 中使用可变字段
  • == 中直接调用 left.Equals(right)

九、结论

在 C# 中,实现值相等不是“重写一个方法”,
而是一个需要遵循明确步骤和约束的完整设计。

标准流程可以总结为:

  1. 明确定义相等语义
  2. 实现 IEquatable<T>.Equals(T)
  3. 重写 object.Equals(object)
  4. 重写 GetHashCode()
  5. (可选)重载 == / !=

这套实现,才能在语言层、集合层和框架层都保持一致、可靠的行为。

以上就是C#中实现值相等(Value Equality)的详细步骤的详细内容,更多关于C#实现值相等Value Equality的资料请关注脚本之家其它相关文章!

相关文章

  • C#中函数的创建和闭包的理解

    C#中函数的创建和闭包的理解

    这篇文章主要介绍了C#中函数的创建和闭包的理解,本文讲解了动态创建函数、匿名函数不足之处、理解c#中的闭包、闭包的优点等内容,需要的朋友可以参考下
    2015-04-04
  • C#中Dynamic和Dictionary性能比较

    C#中Dynamic和Dictionary性能比较

    开发中需要传递变参,考虑使用 dynamic 还是 Dictionary,dynamic 的编码体验显著优于 Dictionary,如果性能差距不大的话,我会选择使用dynamic。下面通过本文给大家详细介绍下C#中Dynamic和Dictionary性能比较,一起看看吧
    2016-11-11
  • C#如何通过RFC连接sap系统

    C#如何通过RFC连接sap系统

    这篇文章主要为大家详细介绍了C#如何通过RFC连接sap系统的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • 同步调用和异步调用WebService

    同步调用和异步调用WebService

    本文给大家介绍webservice同步调用和异步调用,同步调用就是一个同步操作会阻塞整个当前的进程,直到这个操作完成才能执行下一段代码,异步调用不会阻塞启动操作的调用线程,调用程序必须通过轮流检测,或者等待完成信号来发现调用的完成。小伙伴们跟着小编一起学习
    2015-09-09
  • C# 泛型字典 Dictionary的使用详解

    C# 泛型字典 Dictionary的使用详解

    本文主要介绍了C# 泛型字典 Dictionary的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • C# Winform按钮中图片实现左图右字的效果实例

    C# Winform按钮中图片实现左图右字的效果实例

    这篇文章主要给大家介绍了关于C# Winform按钮中图片实现左图右字效果的相关资料,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • Unity打开淘宝app并跳转到商品页面功能的实现方法

    Unity打开淘宝app并跳转到商品页面功能的实现方法

    这篇文章主要给大家介绍了关于如何利用Unity打开淘宝app并跳转到商品页面功能的相关资料,这个功能目前在网上找不到相关的解决方法,所以自己写了出来,需要的朋友可以参考下
    2021-07-07
  • WinForm相对路径的陷阱

    WinForm相对路径的陷阱

    这篇文章主要介绍了WinForm相对路径的陷阱,是在进行C#程序设计中尤其需要注意的问题,需要的朋友可以参考下
    2014-08-08
  • 理解C#生成验证码的过程

    理解C#生成验证码的过程

    这篇文章主要介绍了C#生成验证码的过程,通过实例分析C#验证码的生成原理,感兴趣的小伙伴们可以参考一下
    2016-03-03
  • C#播放铃声最简单实现方法

    C#播放铃声最简单实现方法

    这篇文章主要介绍了C#播放铃声最简单实现方法,通过调用系统方法实现播放wav格式音频文件的功能,是非常实用的技巧,需要的朋友可以参考下
    2014-12-12

最新评论