Почему общий ICollection не реализует IReadOnlyCollection в .NET 4.5?

В .NET 4.5/С# 5, IReadOnlyCollection<T> объявлен с помощью свойства Count:

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

Мне интересно, не имеет смысла для ICollection<T> реализовать интерфейс IReadOnlyCollection<T>:

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

Это означало бы, что классы, реализующие ICollection<T>, автоматически выполнили бы IReadOnlyCollection<T>. Это звучит разумно для меня.

Абстракция ICollection<T> может рассматриваться как расширение абстракции IReadOnlyCollection<T>. Обратите внимание, что List<T>, например, реализует как ICollection<T>, так и IReadOnlyCollection<T>.

Однако он не был разработан таким образом.

Что мне здесь не хватает? Почему вместо этого была выбрана текущая реализация?


UPDATE

Я ищу ответ, который использует аргументы Объектно-ориентированное проектирование, чтобы объяснить, почему:

  • Конкретный класс, такой как List<T>, реализующий как IReadOnlyCollection<T>, так и ICollection<T>

- лучший дизайн, чем:

  • ICollection<T> прямое выполнение IReadOnlyCollection<T>

Также обратите внимание, что это по существу тот же вопрос, что и:

  • Почему IList<T> не реализует IReadOnlyList<T>?
  • Почему IDictionary<T> не реализует IReadOnlyDictionary<T>?

Ответы

Ответ 1

Джон был здесь fooobar.com/questions/12104/..., вы должны отметить его ответ как ответ:

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

Так как интерфейсы могут иметь явные реализации, извлечение базовых интерфейсов не обратно совместимо (с базовыми классами у вас нет этой проблемы).

Вот почему...

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

... но не их интерфейсы.

IMHO, они сначала сделали ошибку дизайна, совершенно неразрешимую (без нарушения).

РЕДАКТИРОВАНИЕ: скрытие не помогает, старые (явные) реализации еще не будут строить (без изменения кода):

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' не реализует элемент интерфейса 'Sample.INew.Get()'

Ответ 2

Вероятно, есть несколько причин. Вот некоторые:

  • Огромные проблемы с обратной совместимостью

    Как бы вы написали определение ICollection<T>? Это выглядит естественно:

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

    Но у него есть проблема, потому что IReadOnlyCollection<T> также объявляет свойство Count (здесь компилятор выдаст предупреждение). Помимо предупреждения, оставив все как есть (что эквивалентно написанию new int Count), разработчики могут иметь разные реализации для двух свойств Count, реализуя по крайней мере одно явное. Это может быть "забавно", если две реализации решили вернуть разные значения. Позволять людям стрелять себе в ногу - это не стиль С#.

    ОК, а как насчет:

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

    Ну, это нарушает весь существующий код, который решил реализовать Count явно:

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

    Поэтому мне кажется, что нет хорошего решения этой проблемы: либо вы нарушаете существующий код, либо вы навязываете подверженную ошибкам реализацию для всех. Так что единственный выигрышный ход - не играть.

Ответ 3

Это было бы семантически неправильно, потому что, очевидно, не каждый ICollection доступен только для чтения.

Тем не менее, они могли бы назвать интерфейс IReadableCollection, в то время как реализацию можно было бы назвать ReadOnlyCollection.

Однако они не пошли по этому маршруту. Зачем? Я видел, что член команды BCL пишет, что они не хотели, чтобы API-интерфейс коллекции стал слишком запутанным. (Хотя это уже, откровенно говоря.)

Ответ 4

Когда я впервые прочитал ваш вопрос, я подумал: "Эй, он прав! Это имело бы наибольший смысл". Но точка интерфейса - это описание контракта - описание поведения. Если ваш класс реализует IReadOnlyCollection, он должен быть доступен только для чтения. Collection<T> нет. Таким образом, для Collection<T> для реализации интерфейса, который подразумевается только для чтения, может привести к некоторым довольно неприятным проблемам. Потребители IReadOnlyCollection собираются сделать определенные предположения относительно этой базовой коллекции - например, итерации по ней безопасны, потому что никто не может ее модифицировать и т.д. И т.д. Если она была структурирована так, как вы предполагаете, это может быть неверно!

Ответ 5

Обоснование Объектно-ориентированное проектирование будет связано с отношением "Is A" между классом и любыми интерфейсами, которые реализует класс.

В .NET 4.5/С# 5.0 List<T> есть как ICollection<T>, так и IReadOnlyCollection<T>. Вы можете создать экземпляр List<T> в качестве типа в зависимости от того, как вы хотите использовать свою коллекцию.

Пока ICollection<T> реализация IReadOnlyCollection<T> даст вам возможность иметь List<T> либо ICollection<T>, либо IReadOnlyCollection<T>, то это говорит о том, что ICollection<T> "Is A" IReadOnlyCollection<T>, которого нет.

Обновлено

Если каждый класс, унаследованный от ICollection<T> реализован IReadOnlyCollection<T>, то я бы сказал, что имеет смысл использовать ICollection<T> интерфейс. Поскольку это не так, подумайте о том, как бы это выглядело, если ICollection<T> должен был реализовать IReadOnlyCollection<T>:

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

Если вы хотите посмотреть подпись на ICollection<T>, она выглядит как IS-A только для чтения. Подождите, посмотрите на IReadOnlyCollection и ahh... он реализует IEnumerable<T> и IEnumerable, поэтому он не обязательно должен быть коллекцией только для чтения. Функционально реализации, предложенные исходным вопросом, одинаковы, но семантически я считаю, что правильнее выдвигать реализацию интерфейса так высоко, насколько это возможно.

Ответ 6

Этот интерфейс является скорее интерфейсом описания, а затем действительно функциональным.

В предлагаемом подходе каждая коллекция должна пониматься как то, что вы можете только прочитать, всегда одно и то же. Таким образом, вы не могли добавить или удалить вещь после создания.

Мы могли бы спросить себя, почему интерфейс называется IReadOnlyCollection, а не "IReadOnlyEnumerable", поскольку он использует IEnumerable<T>, IEnumerable. Ответ прост, этот интерфейс не будет иметь никакого смысла, потому что Enumerable уже "только для чтения".

Итак, давайте посмотрим в doc IReadOnlyCollection (T)

В первом (и последнем) сенете в описании говорится:

Представляет собой типизированный, только для чтения набор элементов.

И я думаю, что это все объясняет, если вы знаете, что для strong-typing.