将通用类型 T 转换为 List<> 以进行列表相等性检查
Casting of generic type T to List<> for list equality checking
我有一个通用方法 Compare
,它在签名中接收两个 T
类型的输入参数并比较它们是否相等。返回一个布尔值。 'ordinary' c# .net 类型的逻辑相当简单。
然而,泛型类型 T
也可以是 List<U>
(例如 List<int> xList
和 List<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 倍。所以你付了钱,你做出了选择!
我有一个通用方法 Compare
,它在签名中接收两个 T
类型的输入参数并比较它们是否相等。返回一个布尔值。 'ordinary' c# .net 类型的逻辑相当简单。
然而,泛型类型 T
也可以是 List<U>
(例如 List<int> xList
和 List<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 倍。所以你付了钱,你做出了选择!