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,所以第一个例子应该是可以接受的,允许我们为列表和包含的类型返回一个接口。
有人能解释为什么它无效吗?我相信有一个合乎逻辑的解释。
有一个合乎逻辑的解释,这个确切的问题几乎每天都会在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
(从底部开始。)
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>
是只读序列,因此不可能随后将Dog
或Squirrel
添加到实际上是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<暹罗猫>。