IList <t>和List <t>与接口的转换</t> </t>

时间:2011-06-01 16:11:28

标签: c# generics interface polymorphism

我通常理解接口,继承和多态,但有一件事让我感到困惑。

在此示例中, Cat 实现 IAnimal ,当然列表实现 IList

IList<IAnimal> cats = new List<Cat>();

但它会生成编译错误(无法隐式转换类型...)。如果我使用Cat继承的asbtract超类[Animal],它也将无效。但是,如果我将 IAnimal 替换为 Cat

IList<Cat> cats = new List<Cat>();

编译得很好。

在我看来,因为 Cat 实现 IAnimal ,第一个示例应该可以接受,允许我们为列表返回一个接口和包含的类型。

任何人都可以解释为什么它无效?我确信这是一个合乎逻辑的解释。

6 个答案:

答案 0 :(得分:20)

有一个合乎逻辑的解释,这个问题几乎每天都在StackOverflow上提出。

假设这是合法的:

IList<IAnimal> cats = new List<Cat>(); 

是什么阻止了这种合法性?

cats.Add(new Giraffe());

无。 “猫”是动物名单,长颈鹿是动物,因此你可以将长颈鹿添加到猫的名单中。

显然,这不是类型安全的。

在C#4中,我们添加了一个功能,如果元数据注释允许编译器证明它是类型安全的,那么你可以这样做。在C#4中,你可以这样做:

IEnumerable<IAnimal> cats = new List<Cat>(); 

因为IEnumerable<IAnimal>没有Add方法,所以无法违反类型安全。

有关详细信息,请参阅我在C#4中如何设计此功能的系列文章。

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx

(从底部开始。)

答案 1 :(得分:6)

出于类型安全原因,C#不支持IList<T>上的这种差异。

如果C#支持这个,那么你期望在这里发生什么?

IList<IAnimal> cats = new List<Cat>();

cats.Add(new Dog());         // a dog is an IAnimal too
cats.Add(new Squirrel());    // and so is a squirrel

在C#4中你可以做到这样的事情:

IEnumerable<IAnimal> cats = new List<Cat>();

这是因为IEnumerable<T>接口确实支持这种差异。 IEnumerable<T>是一个只读序列,因此您无法随后将DogSquirrel添加到IEnumerable<IAnimal>,而Cat实际上是{{1}的列表}}

答案 2 :(得分:1)

C#4.0不支持此类covariance。期望你想要的行为是合理的,只是不支持(现在)。

答案 3 :(得分:1)

您可以使用LINQ实现这一点:

IList<IAnimal> cats = new List<Cat>().Cast<IAnimal>();

答案 4 :(得分:1)

IList<T>不是协变接口(或者它是IList<out T>)。这是因为IList都将类型T作为参数并将其作为方法的返回值返回,这使得协方差成为问题。

例如,如果在您的示例中:

IList<IAnimal> cats = new List<Cat>();

你想从猫中添加一只新猫,它会允许:

cats.Add(new Dog());

假设Dog也实现了IAnimal,这显然是不正确的,不起作用。这就是IList不是协变或逆变接口的原因。

答案 5 :(得分:0)

如果在类似列表的界面上需要协方差和逆变,则应该定义接口IReadableList&lt; out T&gt;和IWritableList&lt; in T&gt;,并从List&lt; T&gt;中导出类型。它实现ReadableList&lt; T&amp;和WriteableList&lt; T&gt;。这将使得传递NewList&lt; Cat&gt;成为可能。到期望ReadableList&lt; Animal&gt;的例行程序或WritableList&lt; SiameseCat&gt;。