equals方法默认比较引用;重写equals时必须同时重写gethashcode,否则dictionary等集合会失效;安全重写需判空、类型检查、安全转型和字段比较;record可自动生成equals,但仅适用于纯数据场景。

Equals方法默认比较的是引用还是值?
默认情况下,Equals 方法在 object 上是引用比较——只要两个变量指向堆上同一块内存,就返回 true。这对自定义类几乎总是错的,比如你 new 了两个内容完全一样的 Person 实例,Equals 却返回 false。
只有 string、int 等值类型或重写过 Equals 的类型(如 DateTime)才做值比较。
- 值类型(
int、struct)自动有值语义,Equals比较字段内容 - 引用类型(
class)不重写时,只看是否为同一对象 - 别依赖
==:它默认也是引用比较,且不能被所有类型重载
重写Equals必须同时重写GetHashCode吗?
必须。否则放进 Dictionary<tkey tvalue></tkey> 或 HashSet<t></t> 就会出问题——比如明明相等的对象查不到,或重复添加。
原因很简单:Dictionary 先用 GetHashCode 快速分桶,再用 Equals 精确比对。如果两个逻辑相等的对象 GetHashCode 返回不同值,它们根本不会进同一个桶,Equals 根本没机会被调用。
-
GetHashCode不必唯一,但相等对象的哈希码必须相同 - 避免用可变字段(如
public string Name)参与哈希计算,否则对象修改后哈希值变化,集合行为失控 - 推荐用只读字段(
readonly)或属性 getter 中不变的值,例如this.Id或this.Name?.GetHashCode() ?? 0
怎么安全地重写Equals(object obj)?
直接覆盖 Equals(object obj) 是标准做法,但漏掉几个检查就会崩溃或逻辑错误。
典型错误包括:没判 null、没做类型检查、强制转型失败、忽略基类 Equals 调用。
- 第一行加
if (obj == null || GetType() != obj.GetType()) return false;——GetType()比obj is MyType更严格,防止子类误判 - 转型用
var other = (MyType)obj;,别用as后判空,因为前面已确保类型一致 - 字段比较时,对引用类型字段用
Equals(a, b)(静态重载),它能安全处理null;值类型直接用== - 如果继承自非密封类,最后要调用
base.Equals(obj),否则父类字段被忽略
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType()) return false;
var other = (Person)obj;
return Equals(Name, other.Name) && Age == other.Age;
}
记录类型(record)真的能省事吗?
能,但只适用于“纯数据容器”场景。C# 9+ 的 record 自动生成基于所有属性的 Equals 和 GetHashCode,连 with 表达式都自带。
但它不是万能解药:
- 属性里含
List<t></t>或自定义类时,仍按引用比较——你得自己重写Equals或把字段改成ImmutableArray<t></t> - record 默认是引用类型,
==仍走重载后的值比较,但如果你混用class和record,多态调用可能不符合直觉 - 如果类有业务逻辑、状态变更或需要深比较,record 反而掩盖复杂度,不如手动控制
真正省心的是那种“DTO + 配置类”,比如 record Person(string Name, int Age); —— 这种一行搞定,其余全是坑。










