.NET 中有很多集合类型,它们都有一些共同的行为,例如:
- 您可以使用
foreach 枚举它们
- 他们有一个
Count 属性
- 您可以使用
Add 方法添加项目
- 等等……
这种行为是集合类型的预期行为,您猜对了:它都在ICollection<T> 接口中。我们来看看接口层次结构:
-
IEnumerable<T> 允许使用foreach 枚举您的类
-
ICollection<T> 是一个代表集合的 IEnumerable<T>:
- 它允许检索项目
Count
- 你可能可以
Add/Remove/Clear收藏中的项目
- 一个集合可以是只读的,在这种情况下
IsReadOnly应该返回true
- 还有其他几个辅助方法:
Contains/CopyTo。
-
IList<T> 是一个 ICollection<T>,它允许通过索引访问项目。
- 它添加了一个索引器
- 一些索引相关的函数:
Insert/RemoveAt
IndexOf
您应该实现哪个接口是语义的问题:
IEnumerable<T> 只是一个可枚举的序列。它应该只通过使用代码枚举一次,因为你永远不知道它在多个枚举中的行为。如果您多次枚举 IEnumerable<T>,像 ReSharper 这样的工具甚至会发出警告。
当然,大多数时候您可以安全地多次枚举它,但有时您不应该这样做。例如,枚举可以执行 SQL 查询(例如 Linq-to-SQL)。
您通过定义一个函数来实现IEnumerable<T>:GetEnumerator,它返回 en IEnumerator<T>。枚举器是一个对象,它是一种指向序列中当前元素的指针。它可以返回这个Current 值,并且可以移动到带有MoveNext 的下一个元素。它也是一次性的(在枚举结束时由foreach 处理)。
让我们分解一个foreach 循环:
IEnumerable<T> sequence = ... // Whatever
foreach (T item in sequence)
DoSomething(item);
这等价于:
IEnumerator<T> enumerator = null;
try
{
enumerator = sequence.GetEnumerator();
while (enumerator.MoveNext())
{
T item = enumerator.Current;
DoSomething(item);
}
}
finally
{
if (enumerator != null)
enumerator.Dispose();
}
为了记录,实现IEnumerable 并不是严格要求使类可以与foreach 一起使用。鸭子打字在这里就足够了,但我离题太多了。
当然,您可以使用 yield 关键字轻松实现该模式:
public static IEnumerable<int> GetAnswer()
{
yield return 42;
}
这将创建一个私有类,它将为您实现IEnumerable<int>,因此您不必这样做。
ICollection<T> 表示一个集合,可以安全地多次枚举。但是你真的不知道它是什么样的收藏品。它可以是一个集合、一个列表、一个字典等等。
这是集合语义。
一些例子:
-
T[] - 它实现了ICollection<T>,即使你不能Add/Remove
List<T>
-
HashSet<T> - 集合但不是列表的一个很好的例子
-
Dictionary<TKey, TValue> - 是的,那是ICollection<KeyValuePair<TKey, TValue>>
LinkedList<T>
ObservableCollection<T>
IList<T> 让您知道该集合是可以让您通过索引轻松访问元素的类型(即 O(1) 时间)。 p>
循环链表不是这种情况,因为它不仅需要 O(n) 时间,而且首先没有有意义的索引。
一些例子:
T[]
List<T>
ObservableCollection<T>
请注意 HashSet<T> 和 Dictionary<TKey, TValue> 不再在列表中。这些不是列表。 LinkedList<T> 在语义上是一个列表,但它不提供 O(1) 时间内的索引访问(它需要 O(n))。
我应该提一下 .NET 4.5 中的只读等效项:IReadOnlyCollection<out T>、IReadOnlyList<out T>。它们提供的协方差很好。