【发布时间】:2010-09-28 18:33:33
【问题描述】:
我已经看到它们以许多相同的方式使用,我担心如果我不更好地理解这一点,我将走上一条不可逆转的设计道路。另外,我正在使用 .NET。
【问题讨论】:
标签: .net list collections
我已经看到它们以许多相同的方式使用,我担心如果我不更好地理解这一点,我将走上一条不可逆转的设计道路。另外,我正在使用 .NET。
【问题讨论】:
标签: .net list collections
在 C# 中,存在三个表示对象包的概念。按功能递增的顺序,它们是:
可枚举没有顺序。您不能在集合中添加或删除项目。您甚至无法计算集合中的项目数。它严格地允许您一个接一个地访问集合中的每个项目。
Collection 是一个可修改的集合。您可以从集合中添加和删除对象,还可以获取集合中项目的计数。但是仍然没有顺序,因为没有顺序:无法通过索引访问项目,也无法排序。
List 是一组有序的对象。您可以对列表进行排序、按索引访问项目、按索引删除项目。
事实上,在查看这些接口时,它们是相互构建的:
interface IEnumerable<T>
GetEnumeration<T>interface ICollection<T> : IEnumerable<T>
AddRemoveClearCountinterface IList<T> : ICollection<T>
InsertIndexOfRemoveAt在声明变量,或者方法参数的时候,应该选择使用
基于您在概念上需要对一组对象执行的操作。
如果你只需要能够对列表中的每个对象做某事,那么你只需要IEnumerable:
void SaveEveryUser(IEnumerable<User> users)
{
for User u in users
...
}
您不关心用户是否保存在List<T>、Collection<T>、Array<T> 或其他任何地方。您只需要IEnumerable<T> 接口。
如果您需要能够添加、删除或计算集合中的项目,请使用集合:
ICollection<User> users = new Collection<User>();
users.Add(new User());
如果您关心排序顺序,并且需要正确的顺序,请使用 List:
IList<User> users = FetchUsers(db);
图表形式:
| Feature | IEnumerable<T> | ICollection<T> | IList<T> |
|------------------------|----------------|----------------|----------|
| Enumerating items | X | X | X |
| | | | |
| Adding items | | X | X |
| Removing items | | X | X |
| Count of items | | X | X |
| | | | |
| Accessing by index | | | X |
| Removing by index | | | X |
| Getting index of item | | | X |
System.Collections.Generic 中的List<T> 和Collection<T> 是实现这些接口的两个类;但它们不是唯一的类:
ConcurrentBag<T> 是一个有序的对象包 (IEnumerable<T>)LinkedList<T> 是一个不允许您按索引访问项目的包(ICollection);但您可以在集合中任意添加和删除项目SynchronizedCollection<T> 是一个有序集合,您可以在其中按索引添加/删除项目因此您可以轻松更改:
IEnumerable<User> users = new SynchronizedCollection<User>();
SaveEveryUser(users);
选择你需要的概念,然后使用匹配的类。
【讨论】:
ICollection<T> 和IList<T>。不同的具体实现可能表现不同。例如,如果您通过其IEnumerable<T> 接口访问List<T>,那么您将无法添加、删除、排序或计算列表中的项目。
除了其他答案之外,我还对通用列表和集合功能进行了快速概述。集合是列表的有限子集:
* = 存在 o = 部分存在 属性/方法集合
Add() * *
AddRange() *
AsReadOnly() *
BinarySearch() *
Capacity *
Clear() * *
Contains() * *
ConvertAll() *
CopyTo() o *
Count * *
Equals() * *
Exists() *
Find() *
FindAll() *
FindIndex() *
FindLast() *
FindLastIndex() *
ForEach() *
GetEnumerator() * *
GetHashCode() * *
GetRange() *
GetType() * *
IndexOf() o *
Insert() * *
InsertRange() *
Item() * *
LastIndexOf() *
New() o *
ReferenceEquals() * *
Remove() * *
RemoveAll() *
RemoveAt() * *
RemoveRange() *
Reverse() *
Sort() *
ToArray() *
ToString() * *
TrimExcess() *
TrueForAll() *
【讨论】:
根据 MSDN,List(Of T).Add 是“一个 O(n) 操作”(当超出“容量”时),而 Collection(Of T).Add 是 always“一个O(1) 操作”。如果 List 是使用 Array 实现的,而 Collection 是 Linked List,那将是可以理解的。但是,如果是这种情况,人们会期望 Collection(Of T).Item 是“一个 O(n) 操作”。但是 - 它是 - 不是!?! Collection(Of T).Item 是“O(1) 操作”,就像 List(Of T).Item。
最重要的是,“tuinstoel”的“2008 年 12 月 29 日 22:31”帖子声称速度测试显示 List(Of T).Add 比 Collection(Of T).Add 快已经用 Long 和 String 复制了。虽然我只比他声称的 80% 快了 33%,但根据 MSDN,它应该是相反的“n”倍!?!
【讨论】:
所有这些接口都继承自IEnumerable,您应该确保自己理解。该接口基本上允许您在 foreach 语句中使用该类(在 C# 中)。
ICollection 是您列出的最基本的接口。这是一个支持Count 的可枚举接口,仅此而已。 IList 是 ICollection 的一切,但它也支持添加和删除项目,按索引检索项目等。它是“对象列表”最常用的接口,我知道这是模糊的。 IQueryable 是一个支持 LINQ 的可枚举接口。您始终可以从 IList 创建 IQueryable 并使用 LINQ to Objects,但您也发现 IQueryable 用于延迟执行 LINQ to SQL 和 LINQ to Entities 中的 SQL 语句。 IDictionary 是一种不同的动物,因为它是唯一键到值的映射。它也是可枚举的,因为您可以枚举键/值对,但除此之外,它的用途与您列出的其他用途不同【讨论】:
列表速度更快。
举个例子
private void button1_Click(object sender, EventArgs e)
{
Collection<long> c = new Collection<long>();
Stopwatch s = new Stopwatch();
s.Start();
for (long i = 0; i <= 10000000; i++)
{
c.Add(i);
}
s.Stop();
MessageBox.Show("collect " + s.ElapsedMilliseconds.ToString());
List<long> l = new List<long>();
Stopwatch s2 = new Stopwatch();
s2.Start();
for (long i = 0; i <= 10000000; i++)
{
l.Add(i);
}
s2.Stop();
MessageBox.Show("lis " + s2.ElapsedMilliseconds.ToString());
}
在我的机器上List<> 几乎快两倍。
编辑
我不明白为什么人们不赞成这一点。在我的工作机器和家用机器上,List 代码都快了 80%。
【讨论】:
Collection<T> 是围绕IList<T> 的可定制包装器。虽然IList<T> 没有密封,但它不提供任何自定义点。 Collection<T> 的方法默认委托给标准的IList<T> 方法,但可以很容易地重写以执行您想要的操作。也可以在 Collection<T> 中连接事件,我认为 IList 无法完成。
简而言之,事后扩展它要容易得多,这可能意味着更少的重构。
【讨论】:
IList、IList<T>、List<T> 等的代码。简而言之,您不知道它是否会被调用。多态性解决了这个问题。
ObservableCollection<T> 的示例,其中方法被覆盖以通知更改。
List<T> 是一个非常常见的容器,因为它非常通用(有很多方便的方法,如 Sort、Find 等) - 但如果你想覆盖任何行为(例如,在插入时检查项目)。
Collection<T> 是任何IList<T> 的包装器(默认为List<T>) - 它具有扩展点(virtual 方法),但没有像Find 这样的支持方法那么多。由于是间接的,它比List<T> 稍慢,但也不慢。
使用 LINQ,List<T> 中的额外方法变得不那么重要了,因为 LINQ-to-Objects 倾向于提供它们...例如First(pred)、OrderBy(...) 等。
【讨论】:
Hanselman Speaks: "Collection<T> 看起来像一个列表,它内部甚至还有一个 List<T>。每个方法都委托给内部 List<T>。它包括一个受保护的属性,该属性公开了 List<T>。"
编辑:Collection<T> 在 System.Generic.Collections .NET 3.5 中不存在。如果您从 .NET 2.0 迁移到 3.5,如果您使用大量 Collection<T> 对象,则需要更改一些代码,除非我遗漏了一些明显的东西......
编辑 2:Collection<T> 现在位于 .NET 3.5 的 System.Collections.ObjectModel 命名空间中。帮助文件是这样写的:
“System.Collections.ObjectModel 命名空间包含可用作可重用库的对象模型中的集合的类。当属性或方法返回集合时使用这些类。”
【讨论】:
List<T> 供应用程序代码内部使用。您应该避免编写接受或返回 List<T> 的公共 API(考虑改用超类或集合接口)。
Collection<T> 为自定义集合提供基类(尽管可以直接使用)。
考虑在您的代码中使用Collection<T>,除非您需要List<T> 的特定功能。
以上只是建议。
[改编自:框架设计指南,第二版]
【讨论】:
Dictionary<string, List<string>> 来返回List<string> 就可以了,因为字典的状态只封装了列表的identities,而不是它们的内容。
这是研究生院的问题之一。 T 的集合有点抽象;可能有默认实现(我不是 .net/c# 人),但集合将具有基本操作,如添加、删除、迭代等。
T 的列表暗示了这些操作的一些细节:add 应该花费恒定时间,remove 应该花费与元素数量成比例的时间,getfirst 应该是恒定时间。一般来说,List是一种Collection,但Collection不一定是List。
【讨论】:
List 表示一个集合,其中项目的顺序很重要。它还支持方法 s.a.排序和搜索。集合是一种更通用的数据结构,它对数据的假设更少,也支持更少的操作方法。如果您想公开自定义数据结构,您可能应该扩展集合。如果您需要在不暴露数据结构的情况下操作数据,列表可能是更方便的方式。
【讨论】:
两者都实现相同的接口,因此它们的行为方式相同。也许它们在内部的实现方式不同,但这必须进行测试。
我看到的唯一真正的区别是命名空间以及Collection<T> 标记为ComVisibleAttribute(false),因此 COM 代码无法使用它。
【讨论】: