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

sam*_*mis 11 c# generics

为什么C#编译器不允许泛型集合中的多态类型(T)参数(即List [T])?

以"A"和"B"为例,其中"B"是"A"的子类

class A { }
class B : A { }
Run Code Online (Sandbox Code Playgroud)

并考虑一个带有'A'类型列表的函数

void f(List<A> aL) { }
Run Code Online (Sandbox Code Playgroud)

用"B"类型的列表调用

List<B> bL = new List<B>();

f(bL);
Run Code Online (Sandbox Code Playgroud)

给出以下错误

ERROR: cannot convert from List<B> to List<A>
Run Code Online (Sandbox Code Playgroud)

违反了什么语义规则?

除此之外,还有一个"优雅"的意思,除了循环和铸造每个元素(我想要一些糖)?谢谢.

pok*_*oke 11

以这个小例子为例说明为什么这不起作用.试想一下,我们有另一个亚型C的A:

class A {}
class B : A {}
class C : A {}
Run Code Online (Sandbox Code Playgroud)

显然,我可以将一个C对象放在一个List<A>列表中.但现在假设以下函数采用A列表:

public void DoSomething (List<A> list)
{
    list.Add(new C());
}
Run Code Online (Sandbox Code Playgroud)

如果你传递List<A>它按预期工作,因为它C是一个有效的类型放在一个List<A>,但如果你传递一个List<B>,那么你不能把一个C放入该列表.

对于此处发生的一般问题,请参阅数组的协方差和逆变.


mil*_*ose 10

List<B>根本不是一个子类型List<A>.(我从来不知道在这种情况下"协变"和"逆变"是什么,所以我会坚持使用"子类型".)考虑你这样做的情况:

void Fun(List<A> aa) {
    aa(new A());
}

var bb = new List<B>();
Fun(bb); // whoopsie
Run Code Online (Sandbox Code Playgroud)

如果你想要做什么被允许,就可以添加一个明显不是类型安全A的Bs 列表.

现在,显然可以安全地从列表中读取元素,这就是为什么C#允许您创建协变(即"只读")接口 - 这让编译器知道它不可能通过它们导致这种类型的损坏.如果你只需要读取权限,对于集合,通常是IEnumerable<T>,所以在你的情况下你可能只是制作方法:

void Fun(IEnumerable<A> aa) { ... }
Run Code Online (Sandbox Code Playgroud)

并使用Enumerable方法 - 如果基础类型是,大多数应该优化List.

不幸的是,由于C#泛型的东西是如何工作的,所以类不能是变体,只能是接口.据我所知,所有的集合接口都比IEnumerable<T>"读写"更"丰富".从技术上讲,您可以创建自己的协变包装器接口,只显示您想要的读取操作.