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>
已经实现了所有接口?
为什么要一遍又一遍地重复这些接口?
谢谢。
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>
类实现相比,Contains
、Add
和Remove
的速度都非常快。如果它将被迫实现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。那么Q2和Q3呢?这只是个人喜好的问题。就像有些人使用可选功能,如显式 private
用于类字段,internal
用于类,this.
用于访问类成员等,而其他人则不使用。结果是一样的