IList 和List 与接口的转换

我一般都了解接口,inheritance和多态,但有一件事令我困惑。

在这个例子中, Cat实现了IAnimal ,当然List实现了IList

IList cats = new List(); 

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

 IList cats = new List(); 

它编译得很好。

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

谁能解释为什么它无效? 我确信这是一个合乎逻辑的解释。

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

假设这是合法的:

 IList cats = new List(); 

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

 cats.Add(new Giraffe()); 

没有。 “猫”是动物的名单,长颈鹿是动物,因此你可以在猫的名单中添加长颈鹿。

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

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

 IEnumerable cats = new List(); 

因为IEnumerable没有Add方法,所以没有办法违反类型安全。

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

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

(从底部开始。)

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

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

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

在C#4中,您可以执行以下操作:

 IEnumerable cats = new List(); 

这是因为IEnumerable接口确实支持这种方差。 IEnumerable是一个只读序列,因此您无法随后将一个Dog或一只Squirrel添加到IEnumerable ,而这实际上是一个Cat列表。

您可以使用LINQ实现:

 IList cats = new List().Cast(); 

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

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

IList cats = new List();

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

cats.Add(new Dog());

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

C#4.0不支持这种协方差 。 期望你想要的行为是合理的,只是不支持(现在)。

如果在类似列表的接口上需要协方差和逆变,则应该在T>中定义接口IReadableList 和IWritableList <,并从List 派生一个类型,它实现ReadableList 。 这样就可以将NewList 传递给期望ReadableList 或WritableList 的例程。