You need to enable JavaScript to run this app. 为何C# 9 record的Equals方法性能远低于自定义类实现?
优惠活动
大模型
产品
解决方案
定价
更多

为何C# 9 record的Equals方法性能远低于自定义类实现?

性能差异的核心原因

你测出来的7.5倍性能差,根本不是C#团队写不出更快的Equals代码,是你的自定义MyClass为了快,砍掉了大量通用相等语义必须的校验逻辑,而record的默认实现是要覆盖全场景正确性的,两者的实现开销自然差很多。具体的差异点可以拆成几个实实在在的代码层面对比:

  • 你的MyClass根本没做类型校验和空值检查
    你的Equals(MyClass other)上来就直接解引用other的字段做比对:既没有判断other是不是null,也没有检查两个实例的运行时类型是不是完全一致。哪怕你传一个MyClass的派生类实例进来,只要字段值一样,你的方法就会判定两个对象相等——这其实是违反面向对象相等规范的,这种写法在有继承的场景下会出隐蔽的bug。
    而record的核心设计约定是:两个相等的record实例,必须属于完全相同的运行时类型。为了实现这个约定,record生成的Equals方法里,会通过GetEqualityContract这个虚方法,比对两个实例的类型信息,保证派生类和基类哪怕字段全一样也不会被误判为相等。这个虚调用因为可能被派生类重写,JIT没法提前优化掉,这部分就占了不小的开销。
  • record的比对逻辑走了通用比较器的间接调用
    你手写类的字段比对,是直接编译成静态绑定的比较操作:int类型直接生成CPU级别的寄存器比较指令,string类型直接调用静态的字符串相等方法,DateTime类型直接比对内部的Ticks数值,整个链路没有任何多余的跳转。
    而record生成的比对逻辑,是通过EqualityComparer<T>.Default来逐个比对每个属性的:每次比对前要从全局缓存取出对应类型的比较器实例,再通过虚方法调用执行具体的比较逻辑。哪怕对于int、string这些已经有高效相等实现的类型,这个间接调用的开销,也比直接静态绑定的==操作高很多。
  • record的Equals是虚方法,没法被完全内联
    因为record默认是支持继承的,所以生成的Equals方法是virtual的。在你没有把record标记为sealed的情况下,JIT不能确定这个方法没有被派生类重写,也就不敢把整个方法体内联到你的循环代码里——你每一次调用Equals,都要走虚表查找的流程,没法像你手写的非虚Equals方法那样,被直接内联成循环里的几条简单比较指令。
    你可以自己做个简单验证:把测试里的record MyRecord(...)改成sealed record MyRecord(...),重新跑基准测试,性能会直接追平你手写的MyClass。这时候因为类是密封的,JIT可以把所有虚调用全部devirtualize,把EqualityComparer<T>.Default的调用直接优化成字段比对,整个Equals方法完全内联到循环里,开销就降下来了。

为什么不默认做极致优化?

C#团队的设计取舍是很明确的:record作为通用的相等语义类型,必须保证在支持继承的场景下相等逻辑是正确的。如果默认就把record标记为sealed,或者把类型校验、通用比较器的逻辑砍掉,那就等于直接砍掉了record支持继承的核心能力,会在跨类型比较的场景下产出大量难以排查的bug。
你手写的MyClass的快,本质上是针对你这个“无继承、无跨类型比较、不需要空值校验”的特定场景做了极致简化,这种简化的代价是丢失了通用正确性,自然不可能作为record的默认实现。

内容的提问来源于stack exchange,提问作者wezten

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.03 20:36:04