15

IList<T>不继承IListwhereIEnumerable<out T>继承IEnumerable

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

所以任何人都可以说好的,如果该陈述对于所有实现都是正确的,那么在必要时IList<T>直接将其转换为。IList但问题是虽然IList<T>不继承IList,所以不能保证每个IList<T>对象都是IList.

此外 usingIList<object>显然不是解决方案,因为没有out修饰符泛型不能分配给继承较少的类;并且在这里创建 List 的新实例不是解决方案,因为有人可能希望将 实际引用IList<T>IList指针;并且使用List<T>insteed ofIList<T>实际上是一种糟糕的编程习惯,并不能满足所有目的。

如果 .NET 想要提供灵活性,即每个实现IList<T>都不应该有非泛型实现的合同(即IList),那么为什么他们不保留另一个实现泛型和非泛型版本的接口,并且不建议所有具体的想要为通用和非遗传项目收缩的类应该通过该接口收缩。

铸造ICollection<T>toICollectionIDictionary<TKey, TValue>to也会出现同样的问题IDictionary

4

4 回答 4

9

正如您所注意到的,TinIList<T>不是covariant。根据经验:任何可以修改其状态的类都不能是协变的。原因是此类类通常具有具有T作为其参数之一类型的方法,例如void Add(T element). 并且输入位置中不允许使用协变类型参数。

除其他原因外,还添加了泛型以提供类型安全性。例如,您不能将一个添加ElephantApple. 如果ICollection<T>要扩展ICollection,那么您可以调用((ICollection)myApples).Add(someElephant)而不会出现编译时错误,就像ICollection方法一样void Add(object obj),它似乎允许您将任何对象添加到列表中,而实际上您只能添加T. 因此,不ICollection<T>延伸也不延伸。ICollectionIList<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>许多列表和集合类并没有实现或扩展它们。坦率地说,我发现它们不是很有用,因为它们只定义Countand this[int index]


如果我可以从头开始重新设计 .Net 4.5,我会将列表接口拆分为一个只读协变接口IList<out T>,其中包括ContainsandIndexOf和一个可变不变接口IMutableList<T>。然后你可以投到IList<Apple>. IList<object>我在这里实现了这个:

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

于 2014-01-18T11:34:54.103 回答
3

请注意,自 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>.

于 2014-01-18T11:12:58.603 回答
1

创建一个接口MyIList<T>并让它继承自IList<T>and 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 在问题的评论中已经提到了Brad 的文章。

于 2013-01-28T09:45:17.137 回答
0

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

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

这有点误导,因为在泛型方面,我们有IList<T>可变大小,并且IReadOnlyList<T>自 4.5 以来是只读的,但是 AFAIK,没有固定大小的泛型列表。

于 2016-11-09T09:07:29.473 回答