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.Add(new Dog());
假设Dog也实现了IAnimal,这显然是不正确的,不会起作用。 这就是IList不是协变或逆变接口的原因。
C#4.0不支持这种协方差 。 期望你想要的行为是合理的,只是不支持(现在)。
如果在类似列表的接口上需要协方差和逆变,则应该在T>中定义接口IReadableList