为什么通用IList<>不继承非通用IList

时间:2013-01-28 09:33:42

标签: c# .net generics library-design

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> IList<T> IList<T>实际上是一种糟糕的编程习惯,并不能满足所有目的。

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

ICollection投放到IDictionary<TKey, TValue>IDictionary投放到{{1}}会出现同样的问题。

4 个答案:

答案 0 :(得分:7)

如您所知,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>不会延伸ICollectionIList<T>不会延伸IList

Anders Hejlsberg,C#的创造者之一,explains it like this

  

理想情况下,所有通用集合接口(例如ICollection<T>IList<T>)都将从其非通用对应接口继承,以便通用接口实例可以与通用和非通用代码一起使用。 / p>      

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

1 IEnumerable<T> co <​​/ em> -variant


自.Net 4.5以来,有IReadOnlyCollection<out T>IReadOnlyList<out T>协变接口。但是IList<T>ICollection<T>以及许多列表和集合类都没有实现或扩展它们。坦率地说,我觉得它们不是很有用,因为它们只定义Countthis[int index]


如果我可以从头开始重新设计.Net 4.5,我会将列表界面拆分为包含IList<out T>Contains的只读协变接口IndexOf,以及可变不变接口IMutableList<T>。然后,您可以将IList<Apple>投射到IList<object>。我在这里实现了这个:

  

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

答案 1 :(得分:3)

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

public interface IReadOnlyList<out T>

请参阅its documentation

List<YourClass>Collection<YourClass>YourClass[]这类常用的收集类型会实现IReadOnlyList<YourClass>,因为协方差也可以用作IReadOnlyList<SomeBaseClass>,最终IReadOnlyList<object> 1}}。

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

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

答案 2 :(得分:1)

创建一个界面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和IList的这个具体问题,Jon在问题的评论中已经提到了article from Brad

答案 3 :(得分:0)

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

MSDN IList Remarks: &#34; IList实现分为三类:只读,固定大小和可变大小。 (......)。有关此接口的通用版本,请参阅 System.Collections.Generic.IList<T>&#34;

这有点误导,因为在通用方面,我们IList<T>variable-sizeIReadOnlyList<T>为只读,因为4.5但是AFAIK,没有固定大小的通用列表。