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

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

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> 等。我的目标是利用内置功能,这样我就不必每次都实现自己的方法我想比较一个由数据容器集合组成的数据容器。有没有办法做到这一点?

谢谢!

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

  • 为您的记录手动实现相等性,在本例中使用 SequenceEqual 作为列表,或
  • 仅使用 class具有值语义的元素。

对于后者,您可以为列表编写包装器 class,例如如 中所述,然后仅在您的记录中使用该 class。

我实际上找到了一个好的解决方案。您可以扩展 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;
    }

}