Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't generic ICollection implement IReadOnlyCollection in .NET 4.5?

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>?
like image 365
Zaid Masud Avatar asked Sep 27 '12 13:09

Zaid Masud


2 Answers

There are probably several reasons. Here are some:

  • 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.

like image 165
Jon Avatar answered Nov 14 '22 08:11

Jon


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()'

like image 17
Notoriousxl Avatar answered Nov 14 '22 07:11

Notoriousxl