C#中实现值相等(Value Equality)的详细步骤
一、为什么“值相等”是一个需要认真对待的问题在 C# 中相等并不是一个简单的问题。很多开发者认为重写Equals就够了但在真实系统中错误或不完整的相等实现会导致Dictionary/HashSet行为异常对象“看起来相等”但集合中却当作不相等、Equals、Contains行为不一致隐蔽而难以排查的 Bug这背后的原因在于.NET 的相等语义是一个由多个方法和接口共同构成的协作体系而不是单一方法。本文将从底层机制出发给出标准、完整、可复用的实现步骤。二、相等的两种语义引用相等 vs 值相等在 .NET 中存在两种本质不同的“相等”1. 引用相等Reference Equality1object.ReferenceEquals(a, b)判断两个变量是否指向同一对象实例类reference type的默认行为2. 值相等Value Equality判断两个对象的数据内容是否相同由开发者显式定义和实现12345var a newPerson(Tom, 18);var b newPerson(Tom, 18);ReferenceEquals(a, b);// falsea.Equals(b);// true如果实现了值相等三、.NET 相等体系的整体结构实现值相等必须理解以下四个关键成员的职责分工成员角色IEquatableT.Equals(T)类型安全、性能最优的相等判断object.Equals(object)所有 .NET API 的统一入口GetHashCode()哈希集合的基础 / !运算符语法层面的相等判断可选一个正确的值相等实现必须保证这些成员在语义上一致。四、类引用类型实现值相等的标准步骤以下步骤适用于绝大多数引用类型class。Step 1明确“相等”的语义设计阶段首先必须回答一个设计问题哪些字段决定两个对象在业务语义上是“相等”的例如1Person 相等 ⇔ Name 和 Age 都相等这一步没有代码但至关重要。Step 2实现IEquatableT.Equals(T other)核心步骤1234567891011publicsealedclassPerson : IEquatablePerson{publicstringName {get; }publicintAge {get; }publicboolEquals(Person other){if(otherisnull)returnfalse;returnName other.Name Age other.Age;}}为什么这是核心泛型集合HashSetT、DictionaryTKey, TValue优先调用它避免装箱boxing性能优于object.Equals提供类型安全的比较语义IEquatableT 是值相等的主入口。Step 3重写object.Equals(object obj)必须1234publicoverrideboolEquals(objectobj){returnEquals(objasPerson);}为什么必须大量 .NET API 只接受objectobject.Equals(a, b)、非泛型集合依赖它保证所有调用路径的相等逻辑一致规范要求Equals(object) 必须委托给 Equals(T)而不是重复实现逻辑。Step 4重写GetHashCode()必须1234publicoverrideintGetHashCode(){returnHashCode.Combine(Name, Age);}必须遵守的核心约束如果 a.Equals(b) 为 true那么 a.GetHashCode() 必须等于 b.GetHashCode()。否则HashSetT会包含重复元素DictionaryTKey, TValue无法正确查找 key实践建议使用参与相等比较的字段避免使用可变字段不要依赖string.GetHashCode()的持久性Step 5可选但推荐重载 / !运算符123456789publicstaticbooloperator(Person left, Person right){returnobject.Equals(left, right);}publicstaticbooloperator!(Person left, Person right){return!object.Equals(left, right);}说明默认情况下类的比较的是引用重载后可使与值相等语义一致object.Equals已处理所有null情况是最安全的写法五、完整标准实现模板1234567891011121314151617181920212223242526272829publicsealedclassPerson : IEquatablePerson{publicstringName {get; }publicintAge {get; }publicPerson(stringname,intage){Name name;Age age;}publicboolEquals(Person other){if(otherisnull)returnfalse;returnName other.Name Age other.Age;}publicoverrideboolEquals(objectobj) Equals(objasPerson);publicoverrideintGetHashCode() HashCode.Combine(Name, Age);publicstaticbooloperator(Person left, Person right)object.Equals(left, right);publicstaticbooloperator!(Person left, Person right) !object.Equals(left, right);}六、结构体值类型的补充说明struct默认按字段比较但使用反射性能较低推荐同样实现IEquatableT1234567891011121314publicstructPoint : IEquatablePoint{publicintX;publicintY;publicboolEquals(Point other) X other.X Y other.Y;publicoverrideboolEquals(objectobj) objisPoint p Equals(p);publicoverrideintGetHashCode() HashCode.Combine(X, Y);}七、record值相等的语言级支持C# 91publicrecord Person(stringName,intAge);编译器自动生成IEquatableTEquals(object)GetHashCode / !不可变设计对于值对象Value Objectrecord 是首选方案。八、常见错误总结只实现IEquatableT不重写Equals(object)重写Equals但忘记GetHashCode与Equals语义不一致在GetHashCode中使用可变字段在中直接调用left.Equals(right)九、结论
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2640469.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!