为什么通用IList 不inheritance非通用IList

IList不inheritanceIList ,其中IEnumerableinheritanceIEnumerable

如果out修饰符是唯一的原因,那么为什么大多数IList的实现(例如CollectionList )都实现了IList接口。

所以任何人都可以说好,如果对于IList所有实现都是如此,那么在必要时将其直接转换为IList 。 但问题是虽然IList不inheritanceIList因此无法保证每个IList对象都是IList

而且使用IList显然不是解决方案,因为没有out修饰符generics不能分配给较少的inheritance类; 并且创建List的新实例不是解决方案,因为有人可能想要IList实际引用作为IList指针; 并使用List insured IList实际上是一个糟糕的编程实践,并不适用于所有目的。

如果.NET希望提供灵活性, IList每个实现都不应该有非generics实现的契约(即IList )那么为什么他们没有保留另一个实现generics和非generics版本的接口并且没有t建议所有希望签订通用和非遗传项目的具体类别应通过该接口签订合同。

ICollectionICollection并将IDictionaryIDictionary时也会出现同样的问题。

如您所知, IList中的IList不是协变的 。 根据经验:任何可以修改其状态的类都不能协变。 原因是这些类通常具有将T作为其参数之一的类型的方法,例如void Add(T element)输入位置不允许使用协变类型参数。

除其他原因外,还增加了仿制药,以提供类型安全性。 例如,您无法将Elephant添加到Apple列表中。 如果ICollection要扩展ICollection ,那么你可以调用((ICollection)myApples).Add(someElephant)而没有编译时错误,因为ICollection有一个方法void Add(object obj) ,它似乎允许你添加列表中的任何对象,而在实践中,您只能添加T对象。 因此, ICollection不扩展ICollectionIList不扩展IList

C#的创造者之一Anders Hejlsberg 解释如下 :

理想情况下,所有通用集合接口(例如ICollection,IList)都将从其非generics对应物inheritance,以便通用接口实例可以与通用和非通用代码一起使用。

事实certificate,唯一可能的通用接口是IEnumerable,因为只有IEnumerable是反变量的:在IEnumerable中,类型参数T仅用于“输出”位置(返回值)而不是“输入”职位(参数)。 ICollection和IList在输入和输出位置都使用T,因此这些接口是不变的。


从.Net 4.5开始,有IReadOnlyCollectionIReadOnlyList协变接口。 但IListICollection以及许多列表和集合类都没有实现或扩展它们。 坦率地说,我发现它们不是很有用,因为它们只定义了Countthis[int index]


如果我可以从头开始重新设计.Net 4.5,我会将列表接口拆分为包含ContainsIndexOf的可读协变接口IList ,以及一个可变的不变接口IMutableList 。 然后你可以将IListIList 。 我在这里实现了这个:

M42集合 – 协变集合,列表和数组。

请注意,自2012年以来,在.NET 4.5及更高版本中,存在协变( out修饰符)接口,

 public interface IReadOnlyList 

看它的文件 。

通常的集合类型如ListCollectionYourClass[]确实实现了IReadOnlyList并且由于协方差也可以用作IReadOnlyList并最终用作IReadOnlyList

正如您所猜测的,您将无法通过IReadOnlyList<>引用修改列表。

使用这个新界面,您可以一起避免使用非通用IList 。 但是,您仍然会遇到IReadOnlyList不是IList的基接口的问题。

创建一个接口MyIList并让它inheritanceIListIList

 public interface MyIList : IList, IList { } 

现在创建一个MySimpleList类,让它实现MyIList

 public class MySimpleList : MyIList { public int Count { get { throw new NotImplementedException(); } } public bool IsFixedSize { get { throw new NotImplementedException(); } } public bool IsReadOnly { get { throw new NotImplementedException(); } } public bool IsSynchronized { get { throw new NotImplementedException(); } } public object SyncRoot { get { throw new NotImplementedException(); } } object IList.this[int index] { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public T this[int index] { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public void Add(T item) { throw new NotImplementedException(); } public int Add(object value) { throw new NotImplementedException(); } public void Clear() { throw new NotImplementedException(); } public bool Contains(T item) { throw new NotImplementedException(); } public bool Contains(object value) { throw new NotImplementedException(); } public void CopyTo(T[] array, int arrayIndex) { throw new NotImplementedException(); } public void CopyTo(Array array, int index) { throw new NotImplementedException(); } public IEnumerator GetEnumerator() { throw new NotImplementedException(); } IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } public int IndexOf(T item) { throw new NotImplementedException(); } public int IndexOf(object value) { throw new NotImplementedException(); } public void Insert(int index, T item) { throw new NotImplementedException(); } public void Insert(int index, object value) { throw new NotImplementedException(); } public bool Remove(T item) { throw new NotImplementedException(); } public void Remove(object value) { throw new NotImplementedException(); } public void RemoveAt(int index) { throw new NotImplementedException(); } } 

您现在可以轻松看到的是,您必须双重实现一堆方法。 一个用于T型,一个用于物体。 在正常情况下,你想避免这种情况。 这是共方差和反方差的问题。

您可以找到的最佳解释(对于IList和IList的这个具体问题,是Jon在问题的评论中已经提到的Brad的文章 。

已经给出了很好的答案。 关于IList的通知:

MSDN IList备注 :“IList实现分为三类:只读,固定大小和可变大小。(…)。对于此接口的通用版本,请参阅System.Collections.Generic.IList “。

这有点误导,因为在通用方面,我们将IList作为变量大小 ,并且IReadOnlyList作为只读,因为4.5但是AFAIK,没有固定大小的通用List。