为什么ICollection<T>没有实现ICollection接口?

32

IEnumerable<T> 实现了 IEnumerable 接口。
但是,ICollection<T> 并没有实现 ICollection 接口。

这样做的原因是什么?或者这只是一种疏忽吗?


2
我认为关于那些很可能不是SO用户做出的设计决策的问题并不合适。也就是说,你在这里得到的只是一堆猜测,而不是真正的原因。 - millimoose
3
@PaulBellora,语言并不重要。这是一个关于类库设计的问题。 - millimoose
2
@CodeNaked 如果 ICollection(T) 不是与 ICollection 等效的“泛型等效物”,那么它就不应该被命名为泛型等效物。这是我在提问时表达观点的一部分。 - Bart Sipes
3
这是在MSDN文档的左侧导航菜单中使用的语法,不知为何既不是C#的 ICollection<T> 也不是VB.NET的 ICollection(Of T)。这是一个和任何语言都兼容的选择。 - millimoose
2
@FireLizzard 我并不是在讽刺,我真的认为问题的“意图”应该匹配才能被视为重复。IEnumerable和ICollection是两个不同的接口,它们背后的设计选择也不同。投票重新开放。 - nawfal
显示剩余15条评论
4个回答

11

正如Nick所说的,ICollection几乎是无用的。

这些接口仅在名称上相似,只有CopyToCount是共同存在的属性。而AddRemoveClearContainsIsReadOnly则已被添加,而IsSychronizedSyncRoot则已被删除。

本质上,ICollection<T>是可变的,ICollection则不是。

Krzysztof Cwalina对此有更多的说明

ICollection<T>看起来像是ICollection,但实际上是一种非常不同的抽象。我们发现ICollection并不是很有用。同时,我们也没有一个表示读写非索引集合的抽象概念。ICollection<T>就是这样的一个抽象概念,可以说ICollection在泛型世界中没有确切的对应体;而IEnumerable<T>则是最接近的。


3
ICollection<T>(可变)实现了IEnumerable<T>(不可变),因此“可变性讨论”无效。 ICollection<T> 通常添加了 ICollection 的接口成员,这表明它可以从中派生出来。ICollection 中的属性在ICollection<T>中不存在,但是任何集合都可以轻松地实现它们,就像框架中除了 HashSet<T> 之外的所有泛型集合一样。 - Sam Harwell
1
ICollection 本质上是 IEnumerable 加上 Count()。如果 ICollection<T> 继承了 ICollection,那么一个期望接收 IEnumerable<Animal> 的程序,但实际传入的是 IList<Cat>,可以通过尝试转换为 ICollection 来轻松确定其中的项目数量。请注意,转换为 IList<Animal> 将失败,该程序将不得不使用笨拙且丑陋的反射来发现转换为 IList<Animal> 是可能的。 - supercat
@supercat,你应该使用IReadOnlyCollection<T>而不是将IEnumerable<T>强制转换为ICollection - binki
2
@binki:在.NET 4.0之前实现了IList<Cat>的类型可能会实现ICollectionIEnumerable<Animal>,但不会实现IReadOnlyList<Animal>[我认为应该被命名为IReadableList<Animal>;在我看来,IReadOnlyFoo接口的契约应该指定实例可以安全地暴露给不允许修改它们的外部代码--这是IReadOnlyList<T>没有强制其实现遵守的条件]。自.NET 2.0以来,使ICollection<T>继承自ICollection将确保接收到代码... - supercat
1
任何类型的 IList<T> 都可以确定其中的项数,而无需知道 T。如果在 .NET 4.0 之后创建了 IList<T> 的实现,则它会实现 IReadOnlyList<T>,并且正在由为 .NET 4.0 编写的代码查询,使用该类型可能比使用 ICollection 更加优雅,但是 ICollection 可以在旧代码或新代码中实现并由旧代码或新代码查询。 - supercat
显示剩余2条评论

7

ICollection<T>ICollection 实际上是两个非常不同的接口,但不幸的是它们共享一个名称,除此之外没有太多相似之处。

引自https://learn.microsoft.com/en-us/archive/blogs/kcwalina/system-collections-vs-system-collection-generic-and-system-collections-objectmodel

ICollection<T> 看起来像 ICollection,但实际上它是一个非常不同的抽象。我们发现 ICollection 不是很有用。同时,我们没有一个抽象来表示一个读写非索引集合。 ICollection<T> 是这样的抽象,你可以说在泛型世界中没有与 ICollection 相对应的准确对等物;IEnumerable<T> 是最接近的。


1
谢谢,这似乎证实了@millimoose在问题评论中的想法,即ICollection命名不当。 - Bart Sipes
1
嗯。对于那些因为这个答案与Romhein的答案几乎相同而想要投反对票的人,请注意Samuel是Shog9将这个问题合并到这个问题中的一个不幸的受害者。我认为这个答案并没有真正添加太多内容,但这并不是Samuel的错。也许这个被合并的问题应该被删除,或者也许可以编辑这个答案以添加更多信息? - jrh

4

首先,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 团队询问肯定的答案。


3
这个问题涉及到原始的.NET 2设计-在泛型接口被发布之前(因此不会出现破坏性的问题)。 几乎所有实现ICollection<T>的集合类型也实现了ICollection(不幸的例外是HashSet<T>),并且只是使用显式实现“笨拙”的ICollection成员。 - Sam Harwell
如果他们更改ICollection以不需要这些属性,那就是破坏性的,这就是我的意思...他们只是选择让ICollection<T>不需要它们,更简单和不会破坏现有代码。 - Nick Craver

0

ICollectionICollection<T>都包含至少一个方法,返回集合类型(GetEnumerator),以及另一个将其作为参数的方法(CopyTo)。

ICollection<T>.GetEnumeratorICollection.GetEnumerator具有协变性,因为T是比System.Object更具体的类型。这部分没有问题。这就是为什么IEnumerable<T>能够子类化(从而可以替代)IEnumerable的原因。

但是,如果我们希望ICollection<T>可以替代ICollection,我们还需要ICollection<T>.CopyToICollection.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,因此不能从它继承。


1
关于差异的争论不成立,因为ICollection不是强类型容器。您可以使用它来说ICollection不能从ICollection<T>派生,但您始终可以在强类型集合周围放置一个弱包装器,其中底层实现预期在不正确使用时抛出ArgumentException。实现ICollection<T>的集合确实具有Count属性,并且可以逐个元素地复制到类型为object[](必要时Boxing)的数组中,这意味着它可以安全地实现ICollection - Sam Harwell
@280Z28:你的“弱包装强类型集合”在这里不相关,因为泛型是关于编译时类型安全,而弱包装必须依赖于运行时类型检查。为了使ICollection<T>ICollection派生出来,它必须可用于任何ICollection的地方,但是如果没有逆变性,在编译时这将是无效的。试试吧;声明一个ICollection<T>并在object[]参数上使用它的CopyTo方法——它不会编译。 - Aaronaught
2
ICollection.CopyTo() 的语义是它将接受任何类型的数组,并在集合中的任何项不适合传递的特定数组类型时抛出异常。协变性、逆变性和不变性的概念只有在某个特定类型参数的上下文中才有意义。由于 ICollection.CopyTo 没有这些参数,因此不应该有任何特殊问题。 - supercat
2
@Aaronaught:在什么情况下,ICollection<Cat>.CopyTo()真的比ICollection.CopyTo()更加类型安全?这两种方法都可以在编译时接受类型为SiameseCat[]的参数,但是如果要放入数组中的所有项恰好是SiameseCat或其子类型的实例(在ICollection<Cat>中通常不应该依赖于此条件),则会在运行时失败。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接