通用集合中的多态类型参数

为什么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不从Listinheritance。 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());