如何实现IEnumerable<T的不同比较>

How to implement different comparison for IEnumerable<T>

提问人:Yola 提问时间:8/25/2022 最后编辑:Yola 更新时间:8/25/2022 访问量:62

问:

目前,我以这种方式进行比较(从这里开始):

bool foo<T>(T expected, T actual) {
    return EqualityComparer<T>.Default.Equals(expected, actual);
}

它适用于基本类型,但不适用于 .我怎样才能让它为喜欢的人工作?IEnumerable<T>IEnumerable<T>SequenceEqual

编辑:我尝试了David L的版本,但它不适用于数组或列表。

C# 比较 IENUMERABLE

评论

0赞 David L 8/25/2022
您如何期望默认的 EqualityComparer 以不同于它工作的方式工作?您需要提供自己的 EqualityComparer。你打算怎么称呼它?
0赞 Yola 8/25/2022
@DavidL我不知道。我对想法持开放态度。
0赞 Theodor Zoulias 8/25/2022
您是否有两个序列,并且想要逐个元素比较它们,使用专门的比较器来比较每对元素?
0赞 Yola 8/25/2022
@TheodorZoulias我更新了问题。我希望该函数与 IEnumerable 一起使用。目前,我不想要一个专门的元素比较器,我希望它们是整数。
0赞 Theodor Zoulias 8/25/2022
所以你要保留签名,但要包括对 where 的情况的特殊处理。从本质上讲,您需要 Object.Equals () 的变体,该变体具有 和 具有相同类型的附加约束,并包含特定类型的 的特殊相等规则。正确?bool foo<T>(T expected, T actual)TIEnumerable<T2>public static bool Equals (object objA, object objB)objAobjBT

答:

2赞 David L 8/25/2022 #1

虽然没有什么可以阻止你直接调用,但你当然可以将该调用包装在帮助程序方法中。请注意,这将逐个索引进行比较。如果要覆盖比较的处理方式,也可以向其传递实例。SequenceEqualsSequenceEqualsIEqualityComparer<T>

bool foo<T>(IEnumerable<T> expected, IEnumerable<T> actual)
{
    return expected.SequenceEqual(actual);
}

bool foo<T>(List<T> expected, List<T> actual)
{
    return expected.SequenceEqual(actual);
}

bool foo<T>(T[] expected, T[] actual)
{
    return expected.SequenceEqual(actual);
}

bool foo<T>(IEnumerable<T> expected, IEnumerable<T> actual,
    IEqualityComparer<T> comparer)
{
    return expected.SequenceEqual(actual, comparer);
}

使用以下命令进行测试将按预期返回 true:

IEnumerable<int> expected = new List<int> { 1, 2, 3 };
IEnumerable<int> actual = new List<int> { 1, 2, 3 };

List<int> expected2 = new List<int> { 1, 2, 3 };
List<int> actual2 = new List<int> { 1, 2, 3 };

int[] expected3 = new int[] { 1, 2, 3 };
int[] actual3 = new int[] { 1, 2, 3 };

Console.WriteLine(foo(expected, actual));
Console.WriteLine(foo(expected2, actual2));
Console.WriteLine(foo(expected3, actual3));

并返回以下项的 false:

IEnumerable<int> expected = new List<int> { 1, 2, 3 };
IEnumerable<int> actual = new List<int> { 1, 3, 2 };

上述方法的优点是,您可以为要显式支持的每种集合类型获得编译时重载。但是,如果要隐式支持所有集合类型,可以按如下方式扩展当前方法:boo foo<T>

bool foo<T>(T expected, T actual)
{
    // check that both T parameters implement IEnumerable 
    // (meaning they are a collection)
    if (expected is IEnumerable expectedEnumerable && 
        actual is IEnumerable actualEnumerable)
    {
        var expectedEnumerator = expectedEnumerable.GetEnumerator();
        var actualEnumerator = actualEnumerable.GetEnumerator();
        
        while (true)
        {
            // Determine if the enumerators have a next element
            var hasExpected = expectedEnumerator.MoveNext();
            var hasActual = actualEnumerator.MoveNext();

            // Either we have no elements or successfully iterated both
            if (!hasExpected && !hasActual)
            {
                return true;
            }

            // One of the enumerators has a value while the other does not.
            if ((hasExpected && !hasActual) || (!hasExpected && hasActual))
            {
                return false;
            }

            // check the equality of the two current elements
            if (!expectedEnumerator.Current.Equals(actualEnumerator.Current))
            {
                return false;
            }
        }
    }
    
    // fall back to your original equality comparison because
    // one or both parameters do not implement IEnumerable
    return EqualityComparer<T>.Default.Equals(expected, actual);
}

对于我上面列出的三个集合用例,这再次返回 true,对于以下测试用例返回 false:

IEnumerable<int> expected4 = new List<int> { 1, 2, 3 };
IEnumerable<int> actual4 = new List<int> { 1, 3, 2 };

List<int> expected5 = new List<int> { 1, 2, 3, 4 };
List<int> actual5 = new List<int> { 1, 2, 3 };

int[] expected6 = new int[] { 2, 3 };
int[] actual6 = new int[] { 1, 2, 3 };

Console.WriteLine(foo(expected4, actual4));
Console.WriteLine(foo(expected5, actual5));
Console.WriteLine(foo(expected6, actual6));

评论

0赞 Yola 8/25/2022
我希望能够调用所有参数。如果我需要超载它,我没关系。foo
0赞 David L 8/25/2022
@Yola没关系。我已经更新了命名。如果你有一个重载,它需要 ,它可以与你当前的方法并存。IEnumerable<T>foo<T>(T expected, T actual)
0赞 Yola 8/25/2022
我只是尝试了数组和列表,它总是选择.你能提供MWE吗?foo<T>(T expected, T actual)
0赞 David L 8/25/2022
@Yola 由于方法的协定,需要为每个集合类型添加唯一的重载。查看我更新的答案。foo<T>
0赞 Yola 8/25/2022
是的,它是这样工作的。但这样,我需要为我使用的每个集合更新它。似乎不是一个好的设计:( 在 C# 中不可能以更通用的方式做到这一点吗?