如何无需自定义相等逻辑即可比较含嵌套record集合的C#9 record实例?
实现原理
C# record 默认的等值比较逻辑是逐成员调用EqualityComparer<T>.Default.Equals(x,y)进行校验,默认返回false的本质原因是List<T>的默认相等比较走的是引用匹配而非内容匹配。
内置通用实现方案
你可以借助.NET内置的IStructuralEquatable接口和StructuralComparisons工具类实现一次通用逻辑,所有需要支持集合内容比较的record直接复用即可,无需为每个业务类型单独重写比较方法:
- 首先实现通用的结构相等基类,该基类全局只需要实现一次:
using System.Reflection; using System.Collections; public abstract record StructuralEqualityRecord { protected virtual bool StructuralEquals(StructuralEqualityRecord other) { if (other == null) return false; if (GetType() != other.GetType()) return false; PropertyInfo[] properties = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (PropertyInfo prop in properties) { object val1 = prop.GetValue(this); object val2 = prop.GetValue(other); if (val1 is IStructuralEquatable structuralEquatable) { if (!structuralEquatable.Equals(val2, StructuralComparisons.StructuralEqualityComparer)) return false; } else if (!Equals(val1, val2)) { return false; } } return true; } protected virtual int StructuralGetHashCode() { HashCode hashCode = default; PropertyInfo[] properties = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (PropertyInfo prop in properties) { object val = prop.GetValue(this); if (val is IStructuralEquatable structuralEquatable) { hashCode.Add(structuralEquatable.GetHashCode(StructuralComparisons.StructuralEqualityComparer)); } else { hashCode.Add(val); } } return hashCode.ToHashCode(); } public override bool Equals(object obj) { return obj is StructuralEqualityRecord other && StructuralEquals(other); } public override int GetHashCode() => StructuralGetHashCode(); public static bool operator ==(StructuralEqualityRecord left, StructuralEqualityRecord right) { return left?.Equals(right) ?? right is null; } public static bool operator !=(StructuralEqualityRecord left, StructuralEqualityRecord right) { return !(left == right); } }
- 业务使用时只需两点修改:
- 将集合属性的类型替换为实现了
IStructuralEquatable的内置类型:比如数组、ImmutableArray<T>、ImmutableList<T>(后者需要引入System.Collections.Immutable内置命名空间) - 让业务record继承上述通用基类即可
修改后的示例代码如下:
using System.Collections.Immutable; record Foo : StructuralEqualityRecord { public string Name { get; set; } public ImmutableList<Bar> Bars { get; set; } public Foo(string name, params int[] values) { Name = name; Bars = values.Select(v => new Bar(v)).ToImmutableList(); } } record Bar { public int Value { get; set; } public Bar(int value) => Value = value; }
此时调用测试代码:
var foo1 = new Foo("Hi",1,2,3); var foo2 = new Foo("Hi",1,2,3); Console.WriteLine(foo1 == foo2); // 输出 true
该方案所有逻辑均基于.NET内置API实现,仅需一次基类开发,后续所有包含集合属性的record都可以直接继承使用,无需单独实现比较逻辑。
内容的提问来源于stack exchange,提问作者Nigel
相关产品推荐
相关产品推荐

