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