如何在不实现相等比较的情况下比较包含记录集合的记录

How to compare records containing collections of records without implementing equality comparisons

提问人:Nigel 提问时间:9/28/2021 最后编辑:Nigel 更新时间:9/29/2021 访问量:3564

问:

C# 9 引入了记录,除其他好处外,还可以非常轻松地进行比较。有没有办法利用该功能来比较由其他记录集合组成的记录?

例如:

record Foo
{
    string Name {get; set;}
    List<Bar> Bars {get; set;}
    public Foo(string name, params int[] values)
    {
        Name = name;
        Bars = values.Select(v => new Bar(v)).ToList();
    }
}

record Bar
{
    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);

return foo1 == foo2; // I want this to return true

顺便说一句,我不是在寻找这段特定代码的解决方案。我知道我可以覆盖运算符或实现 ,等等。 我的目标是利用内置功能,这样我就不必在每次想要比较由数据容器集合组成的数据容器时都实现自己的方法。有没有办法做到这一点?==IComparable<Foo>

谢谢!

C# .NET 集合 按引用传递 C#-9.0

评论

0赞 TheGeneral 9/28/2021
"有没有一种简单的方法可以做到这一点“不是最好的选择,我的意思是我们都是经验丰富的程序员,大多数编码都很容易......没有 BCL 单行(没有支持代码)方法来比较任意类型的集合。所以我想答案是否定的。但是,无论您认为它们是否容易,都有一些方法,¯\_(ツ)_/¯
0赞 Nigel 9/28/2021
@TheGeneral我把它编辑成“有没有办法做到这一点?此外,我指的不是比较任意类型的集合。此问题仅适用于记录类型
0赞 mjwills 9/28/2021
如果你特别想返回 true,那么考虑到你的约束,这是不可能的。return foo1 == foo2;
1赞 mjwills 9/28/2021
你愿意使用继承吗?如果是这样,请考虑在基类中实现一个泛型实现(使用反射来获取属性/字段等)。

答:

1赞 Felk 9/28/2021 #1

不幸的是,没有“好”的方法可以做你想做的事。您通常有以下两种选择之一:

  • 手动实现记录的相等性,在本例中,对列表使用 SequenceEqual,或者
  • 仅使用具有值语义的类。

对于后者,您可以为列表编写一个包装类,例如,如“具有集合属性的记录类型和具有值语义的集合”的回答中所述,然后仅在记录中使用该类。

2赞 Nigel 9/29/2021 #2

我实际上找到了一个不错的解决方案。您可以扩展到覆盖和List<T>EqualsGetHashCode

public class ValueEqualityList<T>:List<T>
{
    private readonly bool _requireMathcingOrder;
    public ValueEqualityList(bool requireMatchingOrder = false) => _requireMathcingOrder = requireMatchingOrder;

    public override bool Equals(object other)
    {
        if (!(other is IEnumerable<T> enumerable)) return false;
        if(!_requireMathcingOrder)return enumerable.ScrambledEquals(this);
        return enumerable.SequenceEqual(this);
    }

    public override int GetHashCode()
    {
        var hashCode = 0;
        foreach (var item in this)
        {
            hashCode ^= item.GetHashCode();
        }

        return hashCode;
    }
}

Foo成为:

record Foo
{
    string Name {get; set;}
    List<Bar> Bars {get; set;}
    public Foo(string name, params int[] values)
    {
        Name = name;

        //this is the line that changed
        Bars = new ValueEqualityList<Bar>(values.Select(v => new Bar(v)));
    }
}

这使用了一些帮助程序代码:

static class EnumerableExtensions
{

    /// <summary>
    /// Returns true if both enumerables contain the same items, regardless of order. O(N*Log(N))
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="first"></param>
    /// <param name="second"></param>
    /// <returns></returns>
    public static bool ScrambledEquals<T>(this IEnumerable<T> first, IEnumerable<T> second)
    {
        var counts = first.GetCounts();

        foreach (var item in second)
        {
            if (!counts.TryGetValue(item, out var count)) return false;
            count -= 1;
            counts[item] = count;
            if (count < 0) return false;
        }

        return counts.Values.All(c => c == 0);
    }


    public static Dictionary<T, int> GetCounts<T>(this IEnumerable<T> enumerable)
    {

        var counts = new Dictionary<T, int>();
        foreach (var item in enumerable)
        {
            if (!counts.TryGetValue(item, out var count))
            {
                count = 0;
            }

            count++;
            counts[item] = count;
        }

        return counts;
    }

}