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

时间:2013-07-30 16:31:41

标签: c# generics

为什么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>

违反了哪些语义规则?

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

6 个答案:

答案 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>());