无法将List<Bar>转换为List<Foo>

42

我有这样的一个设置:

abstract class Foo {}
class Bar : Foo {}

还有一个形式不同的方法:

void AddEntries(List<Foo>) {}

我正在尝试使用类型为 Bar 的对象列表调用此方法。

List<Bar> barList = new List<Bar>()
AddEntries(barList);

但是这给了我一个错误:

无法将List<Bar>转换为List<Foo>

有没有什么方法可以解决这个问题?我需要保留使用抽象类的方法定义。


1
C#有像Java中的List<? extends Foo>这样的语法吗? - dbush
2
@dbush 是的,C# 的版本在被接受的答案中。 - Harrichael
4
和 Java 不同,C# 支持泛型协变,因此有一个更好的选择(请参见 Rob 的答案)。 - BlueRaja - Danny Pflughoeft
2
我实际上怀疑Rob的答案更好,因为我打赌OP正在将传递的条目添加到聚合列表中,而不是改变参数。 - Harrichael
3个回答

65
你可以将AddEntries泛型化,并将其更改为以下内容。
void AddEntries<T>(List<T> test) where T : Foo
{
    //your logic 
}

请查看类型参数的约束条件以获取更多信息。

60

请阅读 Microsoft 学习平台上的“泛型中的协变性和逆变性”,特别是其中的“具有协变类型参数的泛型接口”部分,该部分将涵盖您正在处理的情况。

简而言之,如果您可以更改 AddEntries 方法的签名为:

static void AddEntries(IEnumerable<Foo> entries)

请注意使用 IEnumerable<Foo> 而不是 List<Foo>,这样你就能做到你想做的。

这里的具体区别在于 IEnumerable<T>List<T> 的声明方式不同:

public interface IEnumerable<out T> : IEnumerable
public class List<T> : IList<T>, ... ...

重要的区别在于在声明IEnumerable<T>时使用的out,这表示它是协变的,这意味着(使用从learn.microsoft.com获得的定义):

协变: 允许您使用比最初指定的更派生的类型。


1
考虑到方法的名称是AddEntries,很少可能OP会使用IEnumerable。 - Taemyr
@Taemyr 不过,原帖的作者可以定义一个“接口”。 - wizzwizz4
7
我假设AddEntries的意思是“将这些条目添加到其他列表”,而不是“将某些条目添加到此列表”。否则,两个答案都不适用于他,因为他无法将FooChild添加到List<Bar>中。 - BlueRaja - Danny Pflughoeft
1
我只想补充一点,因为我总是记不住哪个是哪个(阅读定义也没有帮助),C#中关于协变/逆变的语法非常棒,因为它允许你表达你的方法是否会接收某种类型的内容,或者是将其传出。如果你接收它们,你可以接收一个子类型(所以可以转换为更具体的类型);如果你将它们传出,你可以始终将它强制转换为更不具体的超类型(列表在类型上既取进又取出,这就是为什么它必须是不变的原因)。选择了非常好的关键字,清晰地表达了意图。 - VisualMelon
@BlueRaja-DannyPflughoeft,这也是我的想法 =) - Rob

43
这是不被允许的,原因很简单。假设以下代码被编译:
AddEntries(new List<Bar>());

void AddEntries(List<Foo> list)
{
   // list is a `List<Bar>` at run-time
   list.Add(new SomethingElseDerivingFromFoo()); // what ?!
}

如果您的代码可以编译,那么您添加了SomethingElseDerivingFromFoo到实际上是一个List<Bar>(运行时类型)中会造成不安全的加法(这使得整个泛型列表变得无意义)。
要解决您的问题,您可以使用泛型:
void AddEntries<T>(List<T> list) where T:Foo
{

}

3
感谢你解释“为什么”无法进行投掷,而不仅仅展示一个使其起作用的方法。 - BJ Myers
我不明白。这是一段完全有效的代码。你只是不能依赖于派生类中提供的任何额外功能(没有显式转换)。 - Phylogenesis
1
@Phylogenesis,你不能将List<Bar>传递给AddEntries,因为如果你向该列表中添加了一个SomethingElseDerivingFromFoo,它会产生奇怪的行为(你在List<Bar>中有一个SomethingElseDerivingFromFoo项)。我发布的上述代码可以完美编译。 - Zein Makki
好的,你编辑后的解释更有意义了。 - Phylogenesis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接