查看: 28|回复: 0

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

[复制链接]

3

主题

0

回帖

0

积分

积极分子

金币
0
阅读权限
220
精华
0
威望
0
贡献
0
在线时间
0 小时
注册时间
2008-10-3
发表于 2026-1-12 08:59:53 | 显示全部楼层 |阅读模式

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

在 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#窗体间传值的两种实现方法
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

相关侵权、举报、投诉及建议等,请发 E-mail:qiongdian@foxmail.com

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部