2011-06-01 104 views
6

我大致明白接口,继承和多态,但有一件事我不解。的IList <T>和列表<T>转换与接口

在这个例子中,实现IAnimal当然名单实现IList的

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

,但它会产生一个编译错误(无法隐式转换类型...) 。如果我使用Cat继承的asbtract超类[动物],它也不起作用。但是,如果我有更换IAnimal

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

它编译罚款。

在我心中,因为实现IAnimal,第一个例子应该是可以接受的,让我们返回一个接口列表和所包含的类型两者。

谁能解释为什么它是不是有效?我确定有一个合理的解释。

回答

20

有一个合理的解释,并就这个问题询问每天都问差不多在计算器上。

假设这是合法的:

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

(开始从底部。)

+2

+1:内容丰富且和往常一样有用:) – Juliet 2011-06-01 16:26:05

+0

谢谢埃里克。我一定会阅读你的文章。还要问一个不断重复的问题......下次会做更好的搜索。 – Curtmantle 2011-06-01 16:32:04

+2

@Mark:不客气,不用担心;事实上,这个问题被问到这么多是促使首先将功能添加到C#4的原因之一。很明显,人们有直觉认为一般方差应该是类型系统的一部分。现在只是教育人们关于什么样的差异可证明是安全的。 – 2011-06-01 16:36:00

1

这种类型的covariance不支持在C#4.0。期望你想要的行为是合理的,但它不被支持(现在)。

+2

你能澄清吗? C#4.0支持协变和逆变,但IList 不是协变接口。 – 2011-06-01 16:16:17

+0

哈哈,好吧,Eric Lippert只是回答了,所以我认为总结一下吧:D – 2011-06-01 16:24:52

1

可以实现,使用LINQ:

IList<IAnimal> cats = new List<Cat>().Cast<IAnimal>(); 
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>是只读序列,所以没有办法,你可以随后添加DogSquirrelIEnumerable<IAnimal>这实际上是的Cat列表。

1

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

例如,如果在你的榜样:

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

您想添加一个新的猫,从猫,它将使:

cats.Add(new Dog());

假设狗还实施IAnimal ,这显然是不正确的,并且不起作用。这就是为什么IList不是协变或逆变界面。

0

如果你需要一个类似列表的接口上的协方差和逆变,你应该定义一个接口IReadableList <出牛逼>和IWritableList < T中>,并从列表<牛逼>它实现了两个ReadableList <牛逼&派生型和WriteableList <T>。这将使得有可能将NewList <Cat>传递给期望可读列表<动物>或可写列表<SiameseCat>的例程。