为什么你不能这样做:`List<IFoo> foo = new List<Bar>();`——关于C#中的List<Interface>

75

如果您有一个接口IFoo和一个类Bar : IFoo,为什么可以执行以下操作:

List<IFoo> foo = new List<IFoo>();  
foo.Add(new Bar());

但是你不能这样做:

List<IFoo> foo = new List<Bar>();

顺便问一下,在第一个例子中它是如何工作的?如果 Bar1 和 Bar2 都实现了 IFoo,那么你就可以将新的 Bar1() 和新的 Bar2() 添加到同一个列表中了吗? - KevinVictor
9个回答

147

乍一看,这似乎应该(就像啤酒应该是免费的)可以工作。然而,快速的实用性检查向我们展示了它为什么不能工作。请记住以下代码无法编译。它的目的是显示为什么即使它看起来没什么问题,但仍然不被允许。

public interface IFoo { }
public class Bar : IFoo { }
public class Zed : IFoo { }

//.....

List<IFoo> myList = new List<Bar>(); // makes sense so far

myList.Add(new Bar()); // OK, since Bar implements IFoo
myList.Add(new Zed()); // aaah! Now we see why.

//.....

myList 是一个 List<IFoo>,这意味着它可以接受任何 IFoo 的实例。但这与它实例化为 List<Bar> 的事实产生了冲突。由于拥有一个 List<IFoo> 意味着我可以添加一个新的 Zed 实例,但我们不能允许这样做,因为底层列表实际上是 List<Bar>,无法容纳 Zed


抱歉,此示例出现相同的错误:“无法隐式转换类型'System.Collections.Generic.List<WebApplication1.Models.Bar>'为'System.Collections.Generic.List<WebApplication1.Models.IFoo>”,针对此行代码:List<IFoo> myList = new List<Bar>(); - AhammadaliPK
4
@AhammadaliPK(以及任何其他阅读此评论的人):这个代码意图上是要失败的。答案中已经提到了这一点。这个示例的目的是为了说明为什么那一行代码是不合逻辑的,因此编译器不允许它存在。它的目的不是展示如何实现它,而是展示为什么它无法被执行! - ADyson

50

原因是C# 3.0或之前版本不支持泛型的协变和逆变。但这在C# 4.0中得到实现,因此您将能够执行以下操作:

IEnumerable<IFoo> foo = new List<Bar>();
注意,在C# 4.0中,你可以将其转换为IEnumerable<IFoo>,但你不能将其转换为List<IFoo>。原因是由于类型安全性,如果你能够将List<Bar>转换为List<IFoo>,那么你就可以向列表中添加其他IFoo实现者,从而破坏类型安全性。
了解更多关于C#中协变和逆变的背景知识,请参阅Eric Lippert的优秀博客系列

16
如果您需要将列表转换为基类或接口的列表,可以使用以下方法:
using System.Linq;

---

List<Bar> bar = new List<Bar>();
bar.add(new Bar());

List<IFoo> foo = bar.OfType<IFoo>().ToList<IFoo>();

2
我认为 .OfType<IFoo>() 部分是多余的。我能够不使用它。 - ScubaSteve

4

这与列表的创建有关,您已指定T为IFoo,因此即使Bar支持IFoo,也不能将其实例化为Bar,因为它们是不同的类型。


3

我使用了一些Linq来使转换更加容易

List<Bar> bar = new List<Bar>();
bar.add(new Bar());

List<IFoo> foo = bar.Select(x => (IFoo)x).ToList();

相较于其他答案,它的描述稍显简洁,但效果良好。


2
如果你有一个类型为List<IFoo>的列表,你可以调用list.add(new Baz());,假设Baz实现了IFoo。然而,你不能对List<Bar>这样做,因此你不能在可以使用List<IFoo>的任何地方使用List<Bar>
但是由于Bar实现了IFoo,所以你可以在使用IFoo的任何地方使用Bar,因此当它期望一个IFoo时,将Bar传递给add函数是有效的。

2
由于 IFoo 列表可能包含一些 Bar,但 IFoo 列表并不等同于 Bar 列表。
请注意,我上面使用的是英语而非 C#。我想强调这不是一个深奥的问题;你只是被语法细节搞糊涂了。要理解答案,你需要超越语法,思考它的实际意义。 IFoo 列表可以包含一个 Bar,因为 Bar 也是一个 IFoo。这里我们谈论的是列表的元素。列表仍然是 IFoo 列表。我们没有改变这一点。
现在,你称之为 foo 的列表仍然是 IFoo 列表(更严格地说,foo 被声明为 List<IFoo>)。它不能是其他任何东西。特别是,它不能变成 Bar 列表(List<Bar>)。Bar 列表是完全不同的对象,与 IFoo 列表不同。

1
在这种情况下,List是类型,而不是继承问题。List<IFoo>的确与List<Bar>不同。List不知道任何关于IFoo或Bar的特征,也没有继承它们的任何特征。
希望能对您有所帮助。

1

List<Bar> 不继承自 List<IFoo>


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