IList<;T>;并且List<;T>;与接口的转换

本文关键字:gt lt 转换 接口 List IList 并且 | 更新日期: 2023-09-27 17:58:22

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

在本例中,Cat实现IAnimal,当然List执行IList

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

但它生成了一个编译错误(无法隐式转换类型…)。如果我使用Cat继承的asbtract超类[Emal],它也不会工作。但是,如果我用Cat替换IAnimal

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

它编译得很好。

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

有人能解释为什么它无效吗?我相信有一个合乎逻辑的解释。

IList<;T>;并且List<;T>;与接口的转换

有一个合乎逻辑的解释,这个确切的问题几乎每天都会在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+和+contrasvariance/default.aspx

(从底部开始。)

由于类型安全原因,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添加到实际上是Cat列表的IEnumerable<IAnimal>中。

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

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

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

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

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

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

cats.Add(new Dog());

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

C#4.0中不支持这种类型的协方差。期望你想要的行为是合理的,只是(目前)不支持。

如果您需要在类似列表的接口上进行协方差和逆变,则应该定义一个接口IReadableList<out T>以及IWritableList<在T>中;,并从List<T>其实现ReadableList<T&,和WriteableList<T>。这将使得可以通过NewList<猫>到期望ReadableList<动物>或者WritableList<暹罗猫>。