如何检查“IEnumerable<T1>”是否协变到“IEnumerable<T2>”?

7

如何检查 IEnumerable<T1> 是否协变于 IEnumerable<T2> 的通用规则是什么?

我进行了一些实验:


1.

Object Obj = "Test string";
IEnumerable<Object> Objs = new String[100];

这段代码之所以能够运行,是因为IEnumerable<out T>是协变的,而String继承自Object

2.

interface MyInterface{}
struct MyStruct:MyInterface{}
.....
Object V = new MyStruct();
Console.WriteLine(new MyStruct() is Object); // Output: True. 
IEnumerable<Object> Vs = new MyStruct[100]; // Compilation error here

MyStruct 实际上是一个 Object,但它不能正常工作,因为 Object 是引用类型而 MyStruct 是值类型。好的,我在这里看到了一些逻辑。

3.

Console.WriteLine(new MyStruct() is ValueType); // Output: "True"
ValueType V2 = new MyStruct();
IEnumerable<ValueType> Vs2 = new MyStruct[100]; // Compilation error here

应该可以工作,因为IEnumerable<out T>是协变的,而MyStructValueType,但实际上并不能工作...好吧,也许MyStruct并没有继承ValueType....
MyInterface V3 = new MyStruct(); 
Console.WriteLine(V3 is MyInterface); // Output: "True" 
IEnumerable<MyInterface> Vs3 = new MyStruct[100]; // Compilation error here

即便是这种情况:"无法将 MyStruct 转换为 MyInterface",真的吗??你刚刚在前一行就做到了...

我试图总结出通用规则:

public static bool IsCovariantIEnumerable(Type T1, Type T2  ){          
    return (T2.IsAssignableFrom(T1)) && !T2.IsValueType; // Is this correct??
}

那么问题是如何实际确定 IEnumerable<T1> 是否协变于 IEnumerable<T2>?我的 IsCovariantIEnumerable(...) 函数是否正确?如果是,是否有更简单的方法来检查它?如果不是,应该如何修复?
另请参考这些文章:12

值类型根本不支持协变;这并不令人惊讶,因为值类型根本不支持继承。如果您需要将值类型用作接口和/或object,则需要对其进行装箱 - 例如array.Cast<IMyInterface>。鉴于您提供的链接已经回答了这个问题,因此不清楚您不理解其中的哪一部分 :) - Luaan
@Luaan,我只想要一个完整的规则来检查IEnumerable<T1>是否协变到IEnumerable<T2>。我仍然不确定我的IsCovariantIEnumerable()函数是否正确。 - Astronavigator
1
就支持接口方面而言,IsAssignableFrom 可能比 IsSubclassOf 更好。 - grek40
@grek40,是的,我错过了接口。 - Astronavigator
请注意,您的示例4无法正常工作,因为 MyStruct 是一个 struct。如果它是一个 class,它就可以正常工作,因此与第一个示例一致。 - fknx
@fknx,是的,但是MyStruct实现了MyInterface。这就是为什么我尝试过它的原因。 - Astronavigator
2个回答

5

在您的特定情况下,它不起作用,因为值类型不支持协变。

但是对于如何确定 是否 一个 IEnumerable<T2> 是协变于 IEnumerable<T1> 的问题:

方法 Type.IsAssignableFrom() 告诉您某个类型的实例是否可分配给此类型的变量。因此,您可以像这样实现您的方法:

public static bool IsCovariantIEnumerable(Type T1, Type T2)
{
    Type enumerable1 = typeof(IEnumerable<>).MakeGenericType(T1);
    Type enumerable2 = typeof(IEnumerable<>).MakeGenericType(T2);
    return enumerable1.IsAssignableFrom(enumerable2);
}

使用方法:

if (IsCovariantIEnumerable(typeof(object), typeof(string))
    Console.WriteLine("IEnumerable<string> can be assigned to IEnumerable<object>");

但是,IsCovariantIEnumerable(typeof(object), typeof(MyStruct))由于上述原因将返回false


为了完整起见:当然,您不需要额外的方法,因为您可以轻松地执行typeof(IEnumerable<object>).IsAssignableFrom(typeof(IEnumerable<string>)


实际上,检查 IEnumerable<...> 类型的可分配性比猜测项类型行为要直接得多。这是一个干净的解决方案,我没有想到过。 - grek40
@grek40 你说得对,我在考虑OP的方法实现时忘记了直接调用IsAssignableFrom。为了完整性,我已经添加了它。 - René Vogt
我看到你的 IsCovariantIEnumerable 工作得很好。但问题仍然是:“我的 IsCovariantIEnumerable 是否正确?” 有没有任何例子,你的函数可以工作,但我的不能? - Astronavigator
@Astronavigator,就目前而言,我认为你的是正确的(在回答之前,我只阅读了你使用“IsSubClass”的第一个版本)。 - René Vogt
@RenéVogt,是的,在grek40的评论后我已经纠正了它。 - Astronavigator

2

值类型不支持协变,因为这会改变它们的内部表示[1]

如果你想避免奇怪的情况,我建议使用IsAssignableFrom代替:

public static bool IsCovariantIEnumerable(Type T1, Type T2) => T1.IsAssignableFrom(T2);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接