[英]Why generic IList<> does not inherit non-generic IList
IList<T>
不继承IList
,其中IEnumerable<out T>
继承IEnumerable
。
如果out
修饰符是唯一的原因,那么为什么大多数IList<T>
的实现(例如Collection<T>
, List<T>
)都实现了IList
接口。
所以任何人都可以说好,如果对于IList<T>
所有实现都是如此,那么在必要时将其直接转换为IList
。 但问题是虽然IList<T>
不继承IList
因此无法保证每个IList<T>
对象都是IList
。
而且使用IList<object>
显然不是解决方案,因为没有out
修饰符泛型不能分配给较少的继承类; 并且创建List的新实例不是解决方案,因为有人可能想要IList<T>
实际引用作为IList
指针; 并使用List<T>
insured IList<T>
实际上是一个糟糕的编程实践,并不适用于所有目的。
如果.NET想要提供灵活性, IList<T>
每个实现都不应该有非泛型实现的合同(即IList
)那么为什么他们没有保留另一个实现泛型和非泛型版本的接口并且没有建议所有希望签订通用和非遗传项目的具体类别应通过该接口签订合同。
将ICollection<T>
为ICollection
并将IDictionary<TKey, TValue>
为IDictionary
时也会出现同样的问题。
当你注意, T
中IList<T>
不是协变的 。 根据经验:任何可以修改其状态的类都不能协变。 原因是这些类通常具有将T
作为其参数之一的类型的方法,例如void Add(T element)
。 输入位置不允许使用协变类型参数。
除其他原因外,还增加了仿制药,以提供类型安全性。 例如,您无法将Elephant
添加到Apple
列表中。 如果ICollection<T>
要扩展ICollection
,那么你可以调用((ICollection)myApples).Add(someElephant)
而没有编译时错误,因为ICollection
有一个方法void Add(object obj)
,它似乎允许你添加列表中的任何对象,而在实践中,您只能添加T
对象。 因此, ICollection<T>
不会扩展ICollection
, IList<T>
不会扩展IList
。
C#的创造者之一Anders Hejlsberg 解释如下 :
理想情况下,所有通用集合接口(例如
ICollection<T>
,IList<T>
)将从其非通用对应物继承,使得通用接口实例可以与通用和非通用代码一起使用。事实证明,唯一可能的通用接口是
IEnumerable<T>
,因为只有IEnumerable<T>
是反变量[sic 1 ] :在IEnumerable<T>
,类型参数T
仅用于“输出“位置(返回值)而不是”输入“位置(参数)。ICollection<T>
和IList<T>
在输入和输出位置都使用T
,因此这些接口是不变的。
1 ) IEnumerable<T>
是共变的
从.Net 4.5开始,有IReadOnlyCollection<out T>
和IReadOnlyList<out T>
协变接口。 但IList<T>
, ICollection<T>
以及许多列表和集合类都没有实现或扩展它们。 坦率地说,我发现它们不是很有用,因为它们只定义了Count
和this[int index]
。
如果我可以从头开始重新设计.Net 4.5,我会将列表接口拆分为一个只读的协变接口IList<out T>
,它Contains
和IndexOf
,以及一个可变的不变接口IMutableList<T>
。 然后你可以将IList<Apple>
为IList<object>
。 我在这里实现了这个:
M42集合 - 协变集合,列表和数组。
请注意,自2012年以来,在.NET 4.5及更高版本中,存在协变( out
修饰符)接口,
public interface IReadOnlyList<out T>
看它的文件 。
通常的集合类型如List<YourClass>
, Collection<YourClass>
和YourClass[]
确实实现了IReadOnlyList<YourClass>
并且由于协方差也可以用作IReadOnlyList<SomeBaseClass>
并最终用作IReadOnlyList<object>
。
正如您所猜测的那样,您将无法通过IReadOnlyList<>
引用修改列表。
使用这个新界面,您可以一起避免使用非通用IList
。 但是,您仍然会遇到IReadOnlyList<T>
不是IList<T>
的基接口的问题。
创建一个接口MyIList<T>
并让它继承IList<T>
和IList
:
public interface MyIList<T> : IList<T>, IList
{ }
现在创建一个MySimpleList
类,让它实现MyIList<T>
:
public class MySimpleList<T> : MyIList<T>
{
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<T> 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的通知:
MSDN IList备注 :“IList实现分为三类:只读,固定大小和可变大小。(...)。对于此接口的通用版本,请参阅System.Collections.Generic.IList<T>
“。
这有点误导,因为在通用方面,我们将IList<T>
作为变量大小 ,并且IReadOnlyList<T>
作为只读,因为4.5但是AFAIK,没有固定大小的通用List。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.