将通用类型 T 转换为 List<> 以进行列表相等性检查

Casting of generic type T to List<> for list equality checking

我有一个通用方法 Compare,它在签名中接收两个 T 类型的输入参数并比较它们是否相等。返回一个布尔值。 'ordinary' c# .net 类型的逻辑相当简单。

然而,泛型类型 T 也可以是 List<U>(例如 List<int> xListList<int> yList),我想比较它们是否相等。编译时不知道U的类型,但可以动态获取,如下例所示

我正在尝试使用 Enumerable.SequenceEqual<V>() 进行列表比较,但此方法需要在编译时知道的类型。为了解决这个问题,我尝试将 T 动态转换为 List<U>,但没有成功。在运行时抛出错误(在代码片段中指出)。

有没有像下面尝试的那样进行列表比较的优雅方法,其中列表内部类型在编译时是未知的?我最接近的答案是:,它似乎非常接近我需要的,但我无法利用它来获得成功的解决方案。

public bool Compare(T x, T y)
{
    bool isEqual;

    if (typeof(T).IsSubClassOfGenericBaseType(typeof(List<>)))
    {

        Type listElementType = 
            x.GetType().GetGenericArguments().Single();
        Type specificListType = (typeof(List<>).MakeGenericType(listElementType));
        dynamic xList = x.GetType().MakeGenericType(specificListType);  // <-- Exception is thrown here.
        dynamic yList = y.GetType().MakeGenericType(specificListType);

        isEqual = Enumerable.SequenceEqual(xList, yList);
    }
    else
    {
        isEqual = EqualityComparer<T>.Default.Equals(x, y);
    }            

    return isEqual;
}

Type扩展方法IsSubClassOfGenericBaseType如下:

internal static bool IsSubClassOfGenericBaseType(
            this Type type, Type genericBaseType)
{
    if (   type.IsGenericType 
        && type.GetGenericTypeDefinition() == genericBaseType)
    {
        return true;
    }

    if (type.BaseType != null)
    {
        return IsSubClassOfGenericBaseType(
            type.BaseType, genericBaseType);
    }

    return false;
}

这样的事情对你有用吗?

public static bool Compare(object? x, object? y)
{
    if (ReferenceEquals(x, y))
        return true;

    if (x is null || y is null)
        return false;

    if (x is IEnumerable a && y is IEnumerable b)
        return a.Cast<object>().SequenceEqual(b.Cast<object>());

    return x.Equals(y);
}

用法:

var a = new List<int> { 1, 2, 3 };
var b = new List<int> { 1, 2, 3 };
var c = new List<int> { 1, 2, 4 };
var d = new List<bool>{ true, false, true };
var e = "string";
var f = new Collection<int> { 1, 2, 3 };
var g = new List<short> { 1, 2, 3 };

Console.WriteLine(Compare(a, b)); // True
Console.WriteLine(Compare(a, c)); // False
Console.WriteLine(Compare(a, d)); // False
Console.WriteLine(Compare(a, e)); // False
Console.WriteLine(Compare(a, f)); // True - might not be what you want,
Console.WriteLine(Compare(a, g)); // False

Console.ReadLine();

请注意,因为我使用的是 SequenceEqual()(根据您的建议),所以 List<int> 与等效的 Collection<int> 比较为真。我认为这可能是您想要的,因为 List<int> Collection<int>,但您知道。

另请注意,您不应在代码中调用方法 Compare() - 这可能会导致人们将其与 IComparer.Compare().

混淆

请注意,此解决方案可能比仅执行以下操作要慢得多:

public static bool CompareDynamic<T>(T x, T y)
{
    return typeof(T).IsSubClassOfGenericBaseType(typeof(List<>)) 
        ? Enumerable.SequenceEqual((dynamic)x, (dynamic)y) 
        : EqualityComparer<T>.Default.Equals(x, y);
}

这是由于所有的转换(如果元素是值类型,则装箱)。如果元素很少,那么这没什么大不了的,但对于大型集合来说可能是个问题。


为了完整起见,这里有一些基准代码,比较了对相当大的 int 列表使用强制转换和使用动态的性能:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;

namespace Demo
{
    [SimpleJob(RuntimeMoniker.Net50)]
    public class Program
    {
        List<int> a = Enumerable.Range(1, 100_000).ToList();
        List<int> b = Enumerable.Range(1, 100_000).ToList();

        static void Main()
        {
            BenchmarkRunner.Run<Program>();
            Console.ReadLine();
        }

        [Benchmark]
        public void ViaCompareCast()
        {
            CompareCast(a, b);
        }

        [Benchmark]
        public void ViaCompareDynamic()
        {
            CompareDynamic(a, b);
        }

        public static bool CompareCast(object? x, object? y)
        {
            if (ReferenceEquals(x, y))
                return true;

            if (x is null || y is null)
                return false;

            if (x is IEnumerable a && y is IEnumerable b)
                return a.Cast<object>().SequenceEqual(b.Cast<object>());

            return x.Equals(y);
        }

        public static bool CompareDynamic<T>(T x, T y)
        {
            return IsSubClassOfGenericBaseType(typeof(T), typeof(List<>)) 
                ? Enumerable.SequenceEqual((dynamic)x, (dynamic)y) 
                : EqualityComparer<T>.Default.Equals(x, y);
        }

        static bool IsSubClassOfGenericBaseType(Type type, Type genericBaseType)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == genericBaseType)
                return true;

            if (type.BaseType != null)
                return IsSubClassOfGenericBaseType(type.BaseType, genericBaseType);

            return false;
        }
    }
}

结果(对于 .net 5.0 发布版本)如下:

|            Method |       Mean |    Error |    StdDev |     Median |
|------------------ |-----------:|---------:|----------:|-----------:|
|    ViaCompareCast | 4,930.6 us | 96.79 us | 191.04 us | 4,832.1 us |
| ViaCompareDynamic |   667.6 us | 12.67 us |  28.07 us |   652.7 us |

如您所见,对于这个特定数据集,使用 dynamic 的版本快 70 倍。所以你付了钱,你做出了选择!