32

IEnumerable<T> implements IEnumerable.
But ICollection<T> does not implement ICollection.

What was the rationale for this and/or was it just an oversight?

BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
  • 2
    I don't think questions about design decisions people who most probably aren't SO users made are really appropriate. That is, you can't get the actual reason here, rather than a bunch of guesses. – millimoose Jun 04 '13 at 01:33
  • Please add the language tag. – Paul Bellora Jun 04 '13 at 01:34
  • 3
    @PaulBellora The language is irrelevant here. This is a question about design of the class libraries. – millimoose Jun 04 '13 at 01:34
  • 1
    `ICollection` is not the "generic equivalent" to `ICollection`, they only share two members that are the "same". – CodeNaked Jun 04 '13 at 01:37
  • @millimoose As a .NET expat, the syntax wasn't instantly recognizable to me, though I can guess what it means. I'll leave it up to the OP. – Paul Bellora Jun 04 '13 at 01:38
  • 2
    @CodeNaked If `ICollection(T)` is not the "generic equivalent" to `ICollection` then it should not have been named as though it is the generic equivalent. That's my part of my point in asking my question. – Bart Sipes Jun 04 '13 at 01:39
  • 3
    @PaulBellora It's the syntax used in the left nav-menu in the MSDN documentation, which for some reason is neither C# (`ICollection`) or (`ICollection(Of T)`). It's as good a "language-agnostic" choice as any. – millimoose Jun 04 '13 at 01:40
  • @millimoose Oh, good to know. Thanks and +1 to your comment. – Paul Bellora Jun 04 '13 at 01:41
  • 1
    http://blogs.msdn.com/b/ericlippert/archive/2011/04/04/so-many-interfaces.aspx <-- Related – Billy ONeal Jun 04 '13 at 01:41
  • 1
    @BartSipes The idea is that "collection" is a better name for the API that `ICollection` represents, than for the API exposed by `ICollection`. Obviously you can't ever rename the old interface. So the choice was (well, might have been, we're all guessing here) to either just get the naming "right" for the new API in order to make a clean break, or to make the naming consistent and have to think up a new, possibly less apt name for `ICollection`. They went with #1. – millimoose Jun 04 '13 at 01:44
  • 2
    How is this a duplicate? – nawfal Jun 08 '14 at 08:19
  • @nawfal It is a duplicate in that the accepted answer for the 'original' also answers the question posed here. The questions themselves are not duplicates (unless I misunderstand English [sarcasm]). – Ethan Reesor Aug 17 '15 at 22:38
  • 2
    @FireLizzard I wasn't being sarcastic, I really think the "intent" of the questions should match to be considered duplicate. IEnumerable and ICollection are two different interfaces and the design choices behind both are different. Voting to re-open – nawfal Aug 18 '15 at 09:45
  • @millimoose Do you have anything to back up that claim of questions about design decisions not being appropriate here? There have been many questions that have been successful here about design decisions in .NET (I can provide links if you are interested). A few members of the .NET design team are even active users here, and in this question somebody even managed to find an [official blog](https://blogs.msdn.microsoft.com/ericlippert/2011/04/04/so-many-interfaces/) about it. Personally I like reading these kind of questions and IMO it would be unfortunate if they were prohibited. – jrh Jan 29 '18 at 15:17
  • 1
    I don't feel like the dupe target explaining `IEnumerable` is a good fit, and marking it as a duplicate might even be confusing for readers trying to research this topic. It is my opinion that the case of `IEnumerable` and `IEnumerable` is considerably easier to understand than `ICollection` and `ICollection`, and that `ICollection` deserves a distinct question. – jrh Jan 29 '18 at 15:42
  • @jrh - experience from previous questions of the sort that I had at the time, probably. And I’m not the one that made the call on this question, since it got resolved as a dupe, not off-topic. My understanding of community moderation is that these things are decided organically - if I’d made the wrong call and got enough people to jump the gun, raising an objection on meta and getting five reopen votes would’ve reversed my call with everyone who’d already voted locked out of voting again on the same question. (And get educated on the current consensus in the process.) – millimoose Jan 29 '18 at 18:58
  • @millimoose thanks for the reply, I ask this kind of thing in comments sometimes to see if there is a meta post I can read to understand people's reasoning on this site better, I don't recall a post on that topic so I figured I'd give it a shot. – jrh Jan 29 '18 at 19:02
  • @jrh I can’t really trace back my exact reasoning more than two years after the fact, but the main criterion that fits is answerability. The problem with design intent questions is they might attract answers of the form “this is why I’d have done things the way they appear to have been done”, as opposed to there being a more objective test for correctness of an answer, or an authoritative source being available. I was unaware of a precedent of the .NET team fielding such questions here, if it exists, it should be pretty easy to get an off-topic close reversed citing them. – millimoose Jan 29 '18 at 19:03
  • 1
    @jrh if you feel a question was closed for the wrong reasons, I think the thing to do is just raise this on meta and make your case for it. The way things are set up is so how the people who originally closed it have no input on the further process. I gave my best reasoning for my vote when I did so, and with that ends my influence over the fate of the question. Lacking mod status nothing that I say is to be understood as a prohibition on a class of questions - you or anyone else is free to agree or disagree with my position and act accordingly. There’s no predetermined “groupthink” to accept. – millimoose Jan 29 '18 at 19:08
  • @millimoose Thanks for the suggestion, however I don't raise meta questions on principle because despite my best efforts to research it, in general I lack the evidence to make a strong affirmative case that this question, or any other, meets the requirements of this site, due to contradictory guidance on meta topics. I have asked for cleanup or clarification to various controversial meta topics, but got none. This probably isn't something that you or I can do something about, but I do feel the need to limit my statements of what is or isn't good for this site to the most cut and dry cases. – jrh Jan 30 '18 at 15:03

4 Answers4

11

As Nick said, ICollection is pretty much useless.

These interfaces are similar only by their name, CopyTo and Count are the only properties in common. Add, Remove, Clear, Contains and IsReadOnly have been added while IsSychronized and SyncRoot have been removed.

In essence, ICollection<T> is mutable, ICollection is not.

Krzysztof Cwalina has more on this topic

ICollection<T> seems like ICollection, but it’s actually a very different abstraction. We found that ICollection was not very useful. At the same time, we did not have an abstraction that represented an read/write non-indexed collection. ICollection<T> is such abstraction and you could say that ICollection does not have an exact corresponding peer in the generic world; IEnumerable<T> is the closest.

Marc L.
  • 3,296
  • 1
  • 32
  • 42
Romhein
  • 2,126
  • 2
  • 17
  • 17
  • 3
    The mutability argument doesn't hold because `ICollection` (mutable) implements `IEnumerable` (not mutable). `ICollection` generally adds to the interface members of `ICollection`, indicating it could be derived from it. The properties of `ICollection` which are not present in `ICollection` could be trivially implemented by any collection, as is the case for all generic collections in the Framework except `HashSet`. – Sam Harwell Mar 01 '10 at 03:46
  • 1
    `ICollection` is essentially `IEnumerable` plus `Count()`. If `ICollection` inherited `ICollection`, a routine that was expecting an `IEnumerable` but was given an `IList` could easily determine the number of items therein by trying to cast to `ICollection`. Note that a cast to `IList` would fail, and the routine would have to use awkward and ugly Reflection to discover that a cast to `IList` would be possible. – supercat Mar 19 '12 at 22:14
  • @supercat You should be using `IReadOnlyCollection` instead of casting `IEnumerable` to `ICollection`. – binki Mar 12 '15 at 15:24
  • 2
    @binki: Types which implemented `IList` prior to .NET 4.0 may implement `ICollection` and `IEnumerable`, but won't implement `IReadOnlyList` [which should, IMHO, have been named `IReadableList`; IMHO, the contract for an `IReadOnlyFoo` interface should specify that instances may safely be exposed to outside code which should not be allowed to modify them--a condition which `IReadOnlyList does not impose on its implementations]. Having `ICollection` inherit from `ICollection` would have ensured, *since .NET 2.0*, that code receiving... – supercat Mar 12 '15 at 15:36
  • 1
    ...any kind of `IList` could ascertain the number of items in it without having to know `T`. If an implementation of `IList` was created after .NET 4.0, it implements `IReadOnlyList`, and is being queried by code written for .NET 4.0, using that type may be more elegant than using `ICollection`, but `ICollection` can be implemented in old or new code and queried by old or new code. – supercat Mar 12 '15 at 15:39
  • @supercat You've got very good points on this topic; I'd advise adding a new answer but a group of voters seem to have decided that a question for `IEnumerable` is a dupe for this? -- I wish that .NET had an `ICountable` interface that all of the generic and non-generic collection classes implemented (so I can get `Count` regardless of what the collection contains), but I guess I'm stuck with `ToList()` which is less than ideal (conceptually and performance wise) and a bit ironic considering the responses that claim that `ICollection` and `ICollection` have nothing in common. – jrh Jan 29 '18 at 15:51
  • I know this is old, but would just would like to add, Queue implements ICollection because it doesn't implement the add/remove methods, where ICollection does. So ICollection isn't entirely useless. It has its purpose. – eaglei22 Jun 25 '20 at 16:26
7

ICollection<T> and ICollection are actually very different interfaces that unfortunately share a name and not much else.

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

ICollection<T> seems like ICollection, but it’s actually a very different abstraction. We found that ICollection was not very useful. At the same time, we did not have an abstraction that represented an read/write non-indexed collection. ICollection<T> is such abstraction and you could say that ICollection does not have an exact corresponding peer in the generic world; IEnumerable<T> is the closest.

Marc L.
  • 3,296
  • 1
  • 32
  • 42
Samuel Neff
  • 73,278
  • 17
  • 138
  • 182
  • 1
    Thanks, that seems to corroborate what @millimoose thought in his comments on the question, which is that ICollection was poorly named. – Bart Sipes Jun 04 '13 at 01:53
  • 1
    Hmm. For those tempted to downvote this answer for being a near duplicate of [Romhein's answer](https://stackoverflow.com/a/2353524/4975230) note that Samuel was a rather unlucky victim of the way that Shog9 merged [this question](https://stackoverflow.com/questions/16908455/why-does-icollectiont-not-implement-icollection) into this one. I think this answer doesn't really add much, but it's not really Samuel's fault. Maybe the question this was merged from should have just been deleted. Alternatively maybe this answer could be edited to add more information? – jrh Jan 29 '18 at 16:01
4

First, IList<T> does not implement IList either, probably for the same reasons. IList<T> implements: ICollection<T>, IEnumerable<T>, IEnumerable

Some parts of ICollection just aren't necessary, but changing an interface after it's out in the wild is breaking at best.

Look at ICollection:

public interface ICollection : IEnumerable
{
    void CopyTo(Array array, int index);

    int Count { get; }
    bool IsSynchronized { get; }
    object SyncRoot { get; }
}

It's just not properties you need in most cases, when I want a Collection I've never once needed this, nor would want to implement it. It got old would be the reasoning I suppose, but you'd have to ask the .Net team for the affirmative answer.

Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
  • 3
    This question refers to the original .NET 2 design - before the generic interfaces were ever released (so breakage isn't an issue). *Almost* all collection types that implement `ICollection` also implement `ICollection` (with the unfortunate exception of `HashSet`), and just use explicit implementation of the "awkward" members of `ICollection`. – Sam Harwell Mar 01 '10 at 01:16
  • @280Z28 - If they were to change `ICollection` to not require those properties, it'd be breaking is what I meant...they simply chose `ICollection` to not require them, simpler and non-breaking. – Nick Craver Mar 01 '10 at 01:19
0

Both ICollection and ICollection<T> contain at least one method that returns the collection type (GetEnumerator) and another that takes it as an argument (CopyTo).

ICollection<T>.GetEnumerator is covariant with ICollection.GetEnumerator, because T is a more specific type than System.Object. This part is OK. This is why IEnumerable<T> is able to subclass (and thus be substitutable) for IEnumerable.

But if we want ICollection<T> to be substitutable for ICollection, we also need ICollection<T>.CopyTo to be contravariant with ICollection.CopyTo, which it is not. The ICollection<T>.CopyTo method cannot accept a parameter of a less-specific type than T (the generics grammar constrains it to be T or smaller). And if ICollection<T>.CopyTo method is not contravariant in its argument, then that means the ICollection<T> interface as a whole is not substitutable for ICollection.

That might be a little hard to make sense of; it's more complicated because it deals with arrays. It's blindingly obvious in IList<T>, where implementing both interfaces could potentially break the type safety that's supposed to be guaranteed by generics (by simply invoking the non-generic IList.Add method). But that's actually just another way of saying that the IList<T>.Add method is not contravariant in its arguments with IList.Add - the generic method cannot accept the less-specific type System.Object as an argument.

Long story short: ICollection<T> simply can't be made substitutable for ICollection under all circumstances, and therefore cannot inherit from it.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • 1
    The argument about variance doesn't hold up because `ICollection` is not a strongly-typed container. You could use it to say that `ICollection` can't derive from `ICollection`, but you can always put a weak wrapper around a strongly typed collection, where the underlying implementation is expected to throw an `ArgumentException` when improperly used. A collection implementing `ICollection` does have a `Count` property, and it can be copied element by element to an array of type `object[]` (boxing as necessary), which means it could safely implement `ICollection`. – Sam Harwell Mar 01 '10 at 03:41
  • @280Z28: Your "weak wrapper around a strongly typed collection" isn't relevant here because generics are about **compile-time type safety**, whereas the weak wrapper has to rely on **runtime type checking**. In order for `ICollection` to derive from `ICollection`, it has to be usable any place that `ICollection` is, but without contravariance, this would be invalid at compile time. Try it yourself; declare an `ICollection` and use its `CopyTo` method on an `object[]` argument - it won't compile. – Aaronaught Mar 01 '10 at 14:16
  • 2
    The semantics of `ICollection.CopyTo()` are that it will accept any type of array, and throw an exception if any item in the collection is not of a type suitable for the particular array passed in. The concepts of covariance, contravariance, and invariance are only meaningful in the context of some particular type parameter. Since `ICollection.CopyTo` has none, there should be no particular problem. – supercat Mar 19 '12 at 22:27
  • 2
    @Aaronaught: In what way is `ICollection.CopyTo()` really more type-safe than `ICollection.CopyTo()`? Both methods would accept at compile time a parameter of type `SiameseCat[]`, but would fail at run-time unless all the items to be put in the array happen to be instances of `SiameseCat` or a subtype thereof (a condition which should not generally be relied upon in an `ICollection`)? – supercat Jun 07 '13 at 21:05