IEnumerable<T>
实现了 IEnumerable
接口。
但是,ICollection<T>
并没有实现 ICollection
接口。
这样做的原因是什么?或者这只是一种疏忽吗?
正如Nick所说的,ICollection
几乎是无用的。
这些接口仅在名称上相似,只有CopyTo
和Count
是共同存在的属性。而Add
、Remove
、Clear
、Contains
和IsReadOnly
则已被添加,而IsSychronized
和SyncRoot
则已被删除。
本质上,ICollection<T>
是可变的,ICollection
则不是。
Krzysztof Cwalina对此有更多的说明
ICollection<T>
看起来像是ICollection
,但实际上是一种非常不同的抽象。我们发现ICollection
并不是很有用。同时,我们也没有一个表示读写非索引集合的抽象概念。ICollection<T>
就是这样的一个抽象概念,可以说ICollection
在泛型世界中没有确切的对应体;而IEnumerable<T>
则是最接近的。
ICollection<T>
(可变)实现了IEnumerable<T>
(不可变),因此“可变性讨论”无效。 ICollection<T>
通常添加了 ICollection
的接口成员,这表明它可以从中派生出来。ICollection
中的属性在ICollection<T>
中不存在,但是任何集合都可以轻松地实现它们,就像框架中除了 HashSet<T>
之外的所有泛型集合一样。 - Sam HarwellICollection
本质上是 IEnumerable
加上 Count()
。如果 ICollection<T>
继承了 ICollection
,那么一个期望接收 IEnumerable<Animal>
的程序,但实际传入的是 IList<Cat>
,可以通过尝试转换为 ICollection
来轻松确定其中的项目数量。请注意,转换为 IList<Animal>
将失败,该程序将不得不使用笨拙且丑陋的反射来发现转换为 IList<Animal>
是可能的。 - supercatIReadOnlyCollection<T>
而不是将IEnumerable<T>
强制转换为ICollection
。 - binkiIList<Cat>
的类型可能会实现ICollection
和IEnumerable<Animal>
,但不会实现IReadOnlyList<Animal>
[我认为应该被命名为IReadableList<Animal>
;在我看来,IReadOnlyFoo
接口的契约应该指定实例可以安全地暴露给不允许修改它们的外部代码--这是IReadOnlyList<T>
没有强制其实现遵守的条件]。自.NET 2.0以来,使ICollection<T>
继承自ICollection
将确保接收到代码... - supercatIList<T>
都可以确定其中的项数,而无需知道 T
。如果在 .NET 4.0 之后创建了 IList<T>
的实现,则它会实现 IReadOnlyList<T>
,并且正在由为 .NET 4.0 编写的代码查询,使用该类型可能比使用 ICollection
更加优雅,但是 ICollection
可以在旧代码或新代码中实现并由旧代码或新代码查询。 - supercatICollection<T>
和 ICollection
实际上是两个非常不同的接口,但不幸的是它们共享一个名称,除此之外没有太多相似之处。
ICollection<T>
看起来像ICollection
,但实际上它是一个非常不同的抽象。我们发现ICollection
不是很有用。同时,我们没有一个抽象来表示一个读写非索引集合。ICollection<T>
是这样的抽象,你可以说在泛型世界中没有与ICollection
相对应的准确对等物;IEnumerable<T>
是最接近的。
首先,IList<T>
并没有实现IList
,原因可能是相同的。 IList<T>
实现了:ICollection<T>, IEnumerable<T>, IEnumerable
ICollection的一些部分并不是必需的,但在接口发布后更改它是最好避免的。
看看ICollection:
public interface ICollection : IEnumerable
{
void CopyTo(Array array, int index);
int Count { get; }
bool IsSynchronized { get; }
object SyncRoot { get; }
}
在大多数情况下,你不需要属性,当我需要一个集合时,我从来没有用过这个属性,也不想去实现它。我认为这可能是因为它已经老旧了,但你必须向 .Net 团队询问肯定的答案。
ICollection<T>
的集合类型也实现了ICollection
(不幸的例外是HashSet<T>
),并且只是使用显式实现“笨拙”的ICollection
成员。 - Sam HarwellICollection
以不需要这些属性,那就是破坏性的,这就是我的意思...他们只是选择让ICollection<T>
不需要它们,更简单和不会破坏现有代码。 - Nick CraverICollection
和ICollection<T>
都包含至少一个方法,返回集合类型(GetEnumerator
),以及另一个将其作为参数的方法(CopyTo
)。
ICollection<T>.GetEnumerator
与ICollection.GetEnumerator
具有协变性,因为T
是比System.Object
更具体的类型。这部分没有问题。这就是为什么IEnumerable<T>
能够子类化(从而可以替代)IEnumerable
的原因。
但是,如果我们希望ICollection<T>
可以替代ICollection
,我们还需要ICollection<T>.CopyTo
与ICollection.CopyTo
相反变。然而,它并不是这样的。 ICollection<T>.CopyTo
方法不能接受比T
(泛型语法将其限制为T
或更小)更少具体类型的参数。如果ICollection<T>.CopyTo
方法在其参数上不是逆变的,那么这意味着整个ICollection<T>
接口都不能替代ICollection
。
这可能有点难以理解;它更复杂,因为涉及到数组。在 IList<T>
中显然是不明显的,因为实现两个接口可能会破坏泛型应该保证的类型安全性(通过简单地调用非泛型方法 IList.Add
)。但这实际上只是另一种说法,即 IList<T>.Add
方法在参数上与 IList.Add
不是逆变的 - 泛型方法无法接受不那么具体的类型 System.Object
作为参数。
长话短说:在所有情况下,ICollection<T>
简单地不能替代 ICollection
,因此不能从它继承。
ICollection
不是强类型容器。您可以使用它来说ICollection
不能从ICollection<T>
派生,但您始终可以在强类型集合周围放置一个弱包装器,其中底层实现预期在不正确使用时抛出ArgumentException
。实现ICollection<T>
的集合确实具有Count
属性,并且可以逐个元素地复制到类型为object[]
(必要时Boxing)的数组中,这意味着它可以安全地实现ICollection
。 - Sam HarwellICollection<T>
从ICollection
派生出来,它必须可用于任何ICollection
的地方,但是如果没有逆变性,在编译时这将是无效的。试试吧;声明一个ICollection<T>
并在object[]
参数上使用它的CopyTo
方法——它不会编译。 - AaronaughtICollection.CopyTo()
的语义是它将接受任何类型的数组,并在集合中的任何项不适合传递的特定数组类型时抛出异常。协变性、逆变性和不变性的概念只有在某个特定类型参数的上下文中才有意义。由于 ICollection.CopyTo
没有这些参数,因此不应该有任何特殊问题。 - supercatICollection<Cat>.CopyTo()
真的比ICollection.CopyTo()
更加类型安全?这两种方法都可以在编译时接受类型为SiameseCat[]
的参数,但是如果要放入数组中的所有项恰好是SiameseCat
或其子类型的实例(在ICollection<Cat>
中通常不应该依赖于此条件),则会在运行时失败。 - supercat
ICollection(T)
不是与ICollection
等效的“泛型等效物”,那么它就不应该被命名为泛型等效物。这是我在提问时表达观点的一部分。 - Bart SipesICollection<T>
也不是VB.NET的ICollection(Of T)
。这是一个和任何语言都兼容的选择。 - millimoose