Why doesn't generic ICollection implement IRea

2019-01-11 08:45发布

问题:

In .NET 4.5 / C# 5, IReadOnlyCollection<T> is declared with a Count property:

public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
{
    int Count { get; }
}

I am wondering, wouldn't it have made sense for ICollection<T> to implement the IReadOnlyCollection<T> interface as well:

public interface ICollection<T> : IEnumerable<T>, IEnumerable, *IReadOnlyCollection<T>*

This would've meant that classes implementing ICollection<T> would've automatically implemented IReadOnlyCollection<T>. This sounds reasonable to me.

The ICollection<T> abstraction can be viewed as an extension of the IReadOnlyCollection<T> abstraction. Note that List<T>, for example, implements both ICollection<T> and IReadOnlyCollection<T>.

However it has not been designed that way.

What am I missing here? Why would the current implementation have been chosen instead?


UPDATE

I'm looking for an answer that uses Object Oriented design reasoning to explain why:

  • A concrete class such as List<T> implementing both IReadOnlyCollection<T> and ICollection<T>

is a better design than:

  • ICollection<T> implementing IReadOnlyCollection<T> directly

Also please note that this is essentially the same question as:

  1. Why doesn't IList<T> implement IReadOnlyList<T>?
  2. Why doesn't IDictionary<T> implement IReadOnlyDictionary<T>?

回答1:

Jon was right here https://stackoverflow.com/a/12622784/395144 , you should mark his reply as the answer:

int ICollection<Foo>.Count { ... } // compiler error!

Since interfaces can have explicit implementations, extracting base interfaces is not backward compatible (with base classes you don't have this problem).

That's why...

Collection<T> : IReadOnlyCollection<T>
List<T> : IReadOnlyList<T>
Dictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>

... but not their interfaces.

IMHO, they did a design error initially, quite unresolvable now (without breaking things).

EDIT: hiding doesn't help, old (explicit) implementations won't still build (without modifying the code):

interface INew<out T> { T Get(); }

interface IOld<T> : INew<T>
{
    void Set(T value);
    new T Get();
}

class Old<T> : IOld<T>
{
    T IOld<T>.Get() { return default(T); }
    void IOld<T>.Set(T value) { }
}

'Sample.Old' does not implement interface member 'Sample.INew.Get()'



回答2:

There are probably several reasons. Here are two off the top:

  1. Huge backwards compatibility problems

    How would you write the definition of ICollection<T>? This looks natural:

    interface ICollection<T> : IReadOnlyCollection<T>
    {
        int Count { get; }
    }
    

    But it has a problem, because IReadOnlyCollection<T> also declares a Count property (the compiler will issue a warning here). Apart from the warning, leaving it as-is (which is equivalent to writing new int Count) allows implementors to have different implementations for the two Count properties by implementing at least one explicitly. This might be "amusing" if the two implementations decided to return different values. Allowing people to shoot themselves in the foot is rather not C#'s style.

    OK, so what about:

    interface ICollection<T> : IReadOnlyCollection<T>
    {
        // Count is "inherited" from IReadOnlyCollection<T>
    }
    

    Well, this breaks all existing code that decided to implement Count explicitly:

    class UnluckyClass : ICollection<Foo>
    {
         int ICollection<Foo>.Count { ... } // compiler error!
    }
    

    Therefore it seems to me that there's no good solution to this problem: either you break existing code, or you force an error-prone implementation on everyone. So the only winning move is not to play.

  2. Semantic runtime type checks

    It would be meaningless to write code like

    if (collection is IReadOnlyCollection<Foo>)
    

    (or the equivalent with reflection) unless IReadOnlyCollection<T> is "opt-in": if ICollection<T> were a superset of IReadOnlyCollection<Foo> then pretty much every collection class under the sun would pass this test, making it not useful in practice.



回答3:

It would be semantically wrong, because obviously, not every ICollection is read-only.

That said, they could have called the interface IReadableCollection, while an implementation could be called ReadOnlyCollection.

However, they didn't go that route. Why? I saw a BCL team member write that they didn't want the collections API to become too convoluted. (Although it already is, frankly.)



回答4:

When I first read your question I thought "Hey, he's right! That would make the most sense." But the point of an interface is to describe a contract - A description of behavior. If your class implements IReadOnlyCollection, it should be read-only. Collection<T> isn't. So for a Collection<T> to implement an interface that implied read-only could lead to some pretty nasty problems. Consumers of an IReadOnlyCollection are going to make certain assumptions about that underlying collection - like iterating over it is safe becuase no one can be modifying it, etc. etc. If it was structured as you suggest that might not be true!



回答5:

The Object Oriented design reasoning would stem from the "Is A" relationship between a class and any interfaces that the class implements.

In .NET 4.5/ c# 5.0 List<T> is both an ICollection<T> and IReadOnlyCollection<T>. You can instantiate a List<T> as either type depending on how you want your collection to be used.

While having ICollection<T> implement IReadOnlyCollection<T> would give you the ability to have List<T> be either an ICollection<T> or IReadOnlyCollection<T>, doing so would be saying that ICollection<T> "Is A" IReadOnlyCollection<T> which it is not.

Updated

If every class that inherited from ICollection<T> implemented IReadOnlyCollection<T> then I would say it makes more sense to have ICollection<T> implement the interface. Since that is not the case, think about what it would look like if ICollection<T> were to implement IReadOnlyCollection<T>:

public interface IReadOnlyCollection<T> : IEnumerable<T>, IEnumerable{}
public interface ICollection<T> : IReadOnlyCollection<T> {}

If you were to look at the signature for ICollection<T>, it looks like it IS-A read-only collection. Wait, let's look at IReadOnlyCollection and ahh...it implements IEnumerable<T> and IEnumerable so it doesn't necessarily have to be a read-only collection. Functionally the implementations proposed by the original question are the same, but semantically I believe it is more correct to push an interface implementation as high as it can go.



回答6:

This interface is more a description interface then really functional thing.

In suggested approach every collection must be understood as something that you can onlly read ad is always the same. So you could not add or remove a thing after creation.

We could ask our self why the interface is called IReadOnlyCollection, not 'IReadOnlyEnumerable' as it use IEnumerable<T>, IEnumerable. The answer is easy this type of interface would not have any senece, because Enumerable is already 'read only'.

So lets look in doc IReadOnlyCollection(T)

The first (and last) senetece in description says:

Represents a strongly-typed, read-only collection of elements.

And I think that this explain everything if you know what strong-typing is for.