通用集合中的多态类型参数
为什么C#编译器不允许generics集合中的多态类型(T)参数(即List [T])?
以“A”和“B”为例,其中“B”是“A”的子类
class A { } class B : A { }
并考虑一个带有’A’类型列表的函数
void f(List aL) { }
用“B”类型列表调用
List bL = new List(); f(bL);
给出以下错误
ERROR: cannot convert from List to List
违反了什么语义规则?
除此之外,还有一个“优雅”的意思,除了循环和铸造每个元素(我想要一些糖)? 谢谢。
List
根本不是List
的子类型。 (我从来不知道在这种情况下“协变”和“逆变”是什么,所以我会坚持使用“子类型”。)考虑你这样做的情况:
void Fun(List aa) { aa(new A()); } var bb = new List(); Fun(bb); // whoopsie
如果你想要做什么是允许的话,就可以将A
添加到B
的列表中,这显然不是类型安全的。
现在,显然可以安全地从列表中读取元素,这就是为什么C#允许您创建协变(即“只读”)接口 – 这让编译器知道它不可能通过它们导致这种类型的损坏。 如果你只需要读取权限,对于集合,通常的是IEnumerable
,所以在你的情况下你可能只是制作方法:
void Fun(IEnumerable aa) { ... }
并使用Enumerable
方法 – 如果基础类型为List
则应对其进行优化。
不幸的是,由于C#generics的东西是如何工作的,所以类不能是变体,只能是接口。 据我所知,所有比IEnumerable
“更丰富”的集合接口都是“读写”。 从技术上讲,您可以创建自己的协变包装器接口,只显示您想要的读取操作。
以这个小例子为例说明为什么这不起作用。 想象一下,我们有另一个A
子类型C
:
class A {} class B : A {} class C : A {}
显然,我可以将一个C
对象放在List
列表中。 但现在假设以下函数采用A列表:
public void DoSomething (List list) { list.Add(new C()); }
如果传递List
它按预期工作,因为C
是放入List
的有效类型,但是如果传递List
,则不能将C
放入该列表中。
对于此处发生的一般问题,请参阅数组的协方差和逆变 。
将B
集合传递给需要A
集合的方法没有任何内在错误。 但是,根据您对集合的处理方式,有许多问题可能会出错。
考虑:
void f(List aL) { aL.(new A()); // oops! what happens here? }
显然这里存在一个问题:如果允许aL
成为List
那么这个实现会导致某种类型的运行时错误,无论是在现场还是(更糟糕的是)如果稍后代码处理我们放置的A
实例作为B
编译器不允许您使用List
作为List
以保持类型安全性并保证您的代码不需要运行时检查是正确的。 请注意,这种行为不同于(不幸的是)数组发生的行为 – 语言设计者的决定是权衡,他们在不同的场合决定不同:
void f(A[] arr) { arr[0] = new A(); // exception thrown at runtime } f(new B[1]);
我想你可能正在寻找’out’generics修饰符,它允许两种generics类型之间的协方差。
http://msdn.microsoft.com/en-us/library/dd469487.aspx
在该页面上发布的示例:
// Covariant delegate. public delegate R DCovariant(); // Methods that match the delegate signature. public static Control SampleControl() { return new Control(); } public static Button SampleButton() { return new Button(); } public void Test() { // Instantiate the delegates with the methods. DCovariant dControl = SampleControl; DCovariant
我不确定C#目前是否支持其当前集合的协方差。
你的错误是Binheritance自A; 但List
不从List
inheritance。 List != A
;
你可以这样做:
List aL = new List(); aL.Add(new B()); f (aL)
您可以在void f(List list)
检测类型
foreach(A a in list) { if (a is B) //Do B stuff else //Do A stuff }
你的问题与我的问题非常相似:答案是你不能这样做,因为thoose是由模板类创建的不同类型,并且它们不会inheritance。 你能做的是:
f(bL.Cast());