C#含自定义类成员的类相等性校验返回false问题排查
问题背景
定义了包含Name类成员的Student类,两个类都实现了IEquatable<T>接口做相等性判断,但出现不符合预期的行为:
- 两个属性值完全一致的不同
Student实例对比,返回false - 两个变量引用同一个
Student实例时,对比返回true
相关实现代码如下:
public class Student : IEquatable<Student> { public Name Name { get; } public Student(Name name) => Name = name; public bool Equals(Student other) { if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(other, null)) return false; return Name == (other.Name); } } public class Name : IEquatable<Name> { public string First { get; } public Name(string first) => First = first; public bool Equals(Name other) { if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(other, null)) return false; return First == other.First; } }
复现异常的测试代码:
var s1 = new Student(new Name("A")); var s2 = new Student(new Name("A")); Console.WriteLine(s1.Equals(s2).ToString()); // 输出False,不符合值相等预期
验证同引用场景正常的测试代码:
var s1 = new Student(new Name("A")); var s2 = s1; Console.WriteLine(s1.Equals(s2).ToString()); // 输出True
问题根因
错误出在Name类型的运算符调用逻辑上:
- 你虽然为
Name类实现了IEquatable<Name>接口的Equals值比较逻辑,但没有重载Name类的==运算符。 - C#中引用类型的默认
==运算符执行的是引用相等判断,即只有两个变量指向同一个堆内存实例时才返回true,不会自动调用你实现的IEquatable<Name>.Equals方法。 - 你在
Student.Equals中写的Name == (other.Name)走的就是默认的引用比较:测试用例中s1和s2持有的Name对象是分别通过new Name("A")创建的两个独立实例,引用地址不同,因此直接返回false,根本没有执行你写的First字段值对比逻辑。 - 当
s2 = s1时,两个变量不仅引用同一个Student实例,也自然引用同一个Name实例,所以引用判断返回true,和你的值比较预期巧合一致。
修复方式
两种方案都可以解决问题,推荐第二种做规范实现:
方案1:最小改动,直接调用Equals方法
把Student.Equals中的==比较替换为对Name.Equals的调用即可:
// 替换原来的 return Name == (other.Name); return Name.Equals(other.Name);
如果存在Name属性为null的场景,补充空判断更稳妥:
if (ReferenceEquals(Name, null)) return ReferenceEquals(other.Name, null); return Name.Equals(other.Name);
方案2:规范实现,重载相等运算符
为实现了IEquatable<T>的类型重载==和!=运算符,保证运算符逻辑和Equals逻辑一致,这是C#自定义值相等类型的标准实践。
在Name类中补充以下代码:
public static bool operator ==(Name left, Name right) { if (ReferenceEquals(left, null)) return ReferenceEquals(right, null); return left.Equals(right); } public static bool operator !=(Name left, Name right) { return !(left == right); } // 建议同时重写object类的Equals和GetHashCode,避免父类方法逻辑不一致 public override bool Equals(object obj) { return Equals(obj as Name); } public override int GetHashCode() { return First?.GetHashCode() ?? 0; }
同样建议给Student类也补上对应的运算符重载和object.Equals、GetHashCode重写,避免其他场景下出现相等判断不一致的问题。补完后你原来写的Name == other.Name就会正确执行值比较逻辑。
内容的提问来源于stack exchange,提问作者na_sacc
相关产品推荐
相关产品推荐

