我通常理解接口,继承和多态,但有一件事让我感到困惑。
在此示例中, Cat 实现 IAnimal ,当然列表实现 IList :
IList<IAnimal> cats = new List<Cat>();
但它会生成编译错误(无法隐式转换类型...)。如果我使用Cat继承的asbtract超类[Animal],它也将无效。但是,如果我将 IAnimal 替换为 Cat :
IList<Cat> cats = new List<Cat>();
编译得很好。
在我看来,因为 Cat 实现 IAnimal ,第一个示例应该可以接受,允许我们为列表返回一个接口和包含的类型。
任何人都可以解释为什么它无效?我确信这是一个合乎逻辑的解释。
答案 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>
是一个只读序列,因此您无法随后将Dog
或Squirrel
添加到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;。