List< T>是“better"然后ICollection< T> ?为什么要实现冗余接口

本文关键字:为什么 接口 ICollection 冗余 实现 quot better List 然后 | 更新日期: 2023-09-27 18:18:24

Q1: List<T>ICollection<T>"更好",更强大。为什么在代码第一教程中他们使用ICollection<T> ?这说不通啊。List<T>可以做ICollection<T>所做的一切,甚至更多。是的,我明白,它是一个接口,而List是一个类。但本我并没有回答这个问题。为什么不使用IList而不使用iccollection ?这也与以下问题有关……

Q2: List<T>定义为:

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>

其中IReadOnlyCollection<T>(例如)定义为

public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable

它已经有IEnumerable<T>, IEnumerable

为什么在List<T>定义中再次使用 IEnumerable<T>, IEnumerable ?

没有任何意义。

Q3: IList<T>的定义如下:

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable

我不明白,如果List<T>实现IList<T>为什么在List<T>定义中再次使用ICollection<T>, IEnumerable<T>, IEnumerable ?

将List定义为:

不是更短吗?
public class List<T> : IList<T>

其中IList<T>已经实现了所有接口?

为什么要一遍又一遍地重复这些接口?

谢谢。

List< T>是“better"然后ICollection< T> ?为什么要实现冗余接口

Q1:

List<T>可能比ICollection<T>更"强大",但它不那么抽象。如果ICollection<T>暴露了您所需要的一切,那么为什么要指定他必须使用List<T>,从而限制您的接口/方法/其他用户,并约束他必须使用List<T> ?

你的类/接口的用户可能不使用List<T>,而是使用HashedSet<T>,这也是ICollection<T>,但不是List<T>
如果他想将他的hashhedset传递给你的方法,那就意味着他必须将他的集合转换为列表,如果你定义你的方法应该接受list而不是ICollection<T>

ICollection没有List那么具体,这意味着一旦一个类型实现了ICollection,该类型就可以被你的方法使用。当然,只有当ICollection指定了方法中需要的所有功能时,你才能这样做。
如果需要使用索引器访问集合中的某个元素,那么ICollection<T>不是接口的参数类型的好选择,因为ICollection<T>没有定义这样的索引器。

Q2,问题3:

这都是关于抽象和Liskov替换原理的。

当你有一个接受IEnumerable的方法时,你可以给它分配一个IReadonlyCollection,因为它在IEnumerable<T>旁边实现了IEnumerable。如果它只实现IEnumerable<T>,那么你就不能将IReadOnlyCollection<T>作为参数赋值给一个接受IEnumerable类型参数的方法

使你的代码不那么具体,使它更强大。假设你有一个这样的方法:

void MyMethod(IList<int> items, int count, int start)
{
    while(items.Count < count)
    {
       items.Add(start++);
    }
}

该方法请求一个IList<int>,做一些List-y的事情,然后返回。但是,该方法不能做任何您不能用ICollection<int>:

做的事情。
void MyMethod(ICollection<int> items, int count, int start)
{
    while(items.Count < count)
    {
       items.Add(start++);
    }
}

这两个方法哪个更强大?

这可能会让你感到惊讶,但答案是第二个。IList<int>实现了ICollection<int>,因此您仍然可以将任何IList<int>传递给第二个方法。但是,您不能将任何ICollection<int>传递给第一个方法。以这种方式使接口相互依赖使我们能够编写出所需的特定方法,并且通过允许它处理更多类型的对象,从而使我们的代码更加强大。

当您遇到需要更具体的类型而只需要不太具体的操作的情况时,只有在使用由尚未理解这一点的团队编写的库时。

至于为什么文档显示了所有不同的接口实现,您必须询问语言设计者,但我可以猜测他们只是想明确这一点,并使我们这些需要阅读文档并验证IList确实实现了iccollection的人更容易。

首先,您应该更好地定义的含义IList<T>ICollection<T>有更多的功能。但是拥有更多的功能并不总是更好的选择。大多数情况下,最好使用功能最少的类型。

在这种情况下,使用最小的功能来满足需求是更好的。例如,您可能会提到ConcurrentList这样的集合类型是更好的选择。虽然它有更多的功能(非常适合并发操作),但是它也有一些性能缺陷。

Q2,Q3:对于重复的接口声明,我认为它们是不必要的,他们写它们只是为了清晰。

这只是对Frederik的回答的一个快速扩展。为什么总是要使用最通用的接口

public void Generalized(ICollection<string> collection)
{
    //blah
}
public void Specialized(List<string> collection)
{
    //blah
}
public void SomeOtherMethod()
{
    ICollection<string> coll = new List<string> { "a", "b" };
    List<string> list = new List<string> { "a", "b" };
    Generalized(coll);
    Generalized(list);
    Specialized(list);
    Specialized(coll); //doesn't compile
}

关于接口定义,它们的存在是为了让用户更清楚地了解List<T>的所有内容。

编辑

为了使它完整,这里需要注意,当从方法返回一个对象时,返回最专门化的对象总是明智的。

这一切归结为赋值兼容性可重用性。把最一般化的一个作为输入,这样其他的都可以被接受。返回时,返回最专门化的一个,以便它适用于大多数调用方法。

public ICollection<string> ReturnGeneralized()
{
    return new List<string>();
}
public List<string> ReturnSpecialized()
{
    return new List<string>();
}
static void SomeOtherMethod()
{
    ICollection<string> coll1 = ReturnGeneralized();
    List<string> list1 = ReturnGeneralized(); //doesn't compile
    ICollection<string> coll2 = ReturnSpecialized();
    List<string> list2 = ReturnSpecialized();
}

理想的方法应该是

public List<string> MostUsable(IEnumerable<string> collection)
{
    return new List<string>();
}

List<T>可以做ICollection<T>的一切,甚至更多。

没有。ICollection<T>IList<T>的主要区别在于前者是无序的,或者换句话说,元素的顺序不是由接口定义的。虽然后者提供了额外的功能(索引访问),但它同时对实现它的容器施加了约束(由于需要列表契约不变量)。这意味着什么-添加/删除操作的列表是非常低效的(除了在列表的末尾,特别是在列表的开始)。另一方面,ICollection<T>没有这样的约束,因此容器可以自由地以最有效的方式实现操作。例如,另一个流行的类Dictionary<TKey, TValue>实现了ICollection<KeyValuePair<TKey, TValue>>,但没有实现IList<KeyValuePair<TKey, TValue>>。它比List<T>更强大吗?答案是否定的。到目前为止,还没有一个容器可以有效地执行所有操作。另一个例子:搜索IList<T> (Contains, IndexOf)效率非常低。与ICollection<T>HashSet<T>类实现相比,ContainsAddRemove的速度都非常快。如果它将被迫实现IList<T>' (to make it more powerful by your logic) then the last 2 operations would suffer like in列表'。

为什么在代码第一教程他们使用ICollection<T> ?

我猜你已经想到了实体框架。我们认为。实体的主要目的是有效地支持称为CRUD的4种主要数据库操作(创建、读取、更新和删除)。正如您可能看到的,没有索引访问(通常,db表不像列表),因此为这些操作的实现者设置IList<T>约束是没有意义的。从所有标准接口来看,ICollection<T>更接近于客户端和实现者都需要的最小功能。请注意,当您填充实体时,没有什么可以阻止您 List<T>分配给这些EF导航属性,但仍然允许EF在为您填充数据库中的数据时使用不同的容器。

综上所述,IList<T>并不适用于所有场景,这就是为什么存在其他更适合特定场景的接口。List<T>是一个好的类,但绝对不是最好的,因为这样的东西不存在。

注:以上所有内容当然都适用于Q1。那么Q2Q3呢?这只是个人喜好的问题。就像有些人使用可选功能,如显式 private用于类字段,internal用于类,this.用于访问类成员等,而其他人则不使用。结果是一样的