Почему универсальная ICollection не реализует IReadOnlyCollection в.NET 4.5?
В.NET 4.5 / C# 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>
,
Однако это не было разработано таким образом.
Что мне здесь не хватает? Почему вместо этого была выбрана текущая реализация?
ОБНОВИТЬ
Я ищу ответ, который использует аргументы в пользу объектно-ориентированного проектирования, чтобы объяснить, почему:
- Конкретный класс, такой как
List<T>
реализуя обаIReadOnlyCollection<T>
а такжеICollection<T>
это лучший дизайн, чем:
ICollection<T>
реализацииIReadOnlyCollection<T>
непосредственно
Также обратите внимание, что это по сути тот же вопрос, что и:
- Почему не
IList<T>
воплощать в жизньIReadOnlyList<T>
? - Почему не
IDictionary<T>
воплощать в жизньIReadOnlyDictionary<T>
?
6 ответов
Джон был прямо здесь /questions/17237838/pochemu-universalnaya-icollection-ne-realizuet-ireadonlycollection-vnet-45/17237853#17237853, вы должны пометить его ответ как ответ:
int ICollection<Foo>.Count { ... } // compiler error!
Поскольку интерфейсы могут иметь явные реализации, извлечение базовых интерфейсов не имеет обратной совместимости (с базовыми классами у вас нет этой проблемы).
Вот почему...
Collection<T> : IReadOnlyCollection<T>
List<T> : IReadOnlyList<T>
Dictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
... но не их интерфейсы.
ИМХО, они изначально ошиблись в дизайне, теперь совершенно неразрешимы (не ломая вещи).
РЕДАКТИРОВАТЬ: скрытие не помогает, старые (явные) реализации по-прежнему не будут собираться (без изменения кода):
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()"
Вероятно, есть несколько причин. Вот два из вершины:
Огромные проблемы с обратной совместимостью
Как бы вы написали определение
ICollection<T>
? Это выглядит естественно:interface ICollection<T> : IReadOnlyCollection<T> { int Count { get; } }
Но это проблема, потому что
IReadOnlyCollection<T>
также объявляетCount
свойство (здесь компилятор выдаст предупреждение). Помимо предупреждения, оставить его как есть (что эквивалентно написаниюnew int Count
) позволяет разработчикам иметь разные реализации для двухCount
свойства путем реализации по крайней мере одного явно. Это может быть "забавно", если две реализации решили вернуть разные значения. Позволять людям стрелять себе в ногу - это не стиль C#.Хорошо, так что насчет:
interface ICollection<T> : IReadOnlyCollection<T> { // Count is "inherited" from IReadOnlyCollection<T> }
Ну, это нарушает весь существующий код, который решили реализовать
Count
в явном виде:class UnluckyClass : ICollection<Foo> { int ICollection<Foo>.Count { ... } // compiler error! }
Поэтому мне кажется, что нет хорошего решения этой проблемы: либо вы нарушаете существующий код, либо вы навязываете подверженную ошибкам реализацию для всех. Таким образом, единственный выигрышный ход - не играть.
Семантические проверки типов во время выполнения
Было бы бессмысленно писать код
if (collection is IReadOnlyCollection<Foo>)
(или эквивалент с отражением), если
IReadOnlyCollection<T>
"согласен": еслиICollection<T>
были надмножествомIReadOnlyCollection<Foo>
тогда почти каждый класс коллекции под солнцем прошел бы этот тест, делая его бесполезным на практике.
Это было бы семантически неправильно, потому что, очевидно, не каждый ICollection
только для чтения.
Тем не менее, они могли бы назвать интерфейс IReadableCollection
в то время как реализация может быть вызвана ReadOnlyCollection
,
Однако они не пошли по этому пути. Зачем? Я видел, как члены команды BCL написали, что они не хотят, чтобы API коллекций становился слишком сложным. (Хотя это уже есть, честно говоря.)
Когда я впервые прочитал ваш вопрос, я подумал: "Эй, он прав! В этом есть смысл". Но смысл интерфейса - описать контракт - описание поведения. Если ваш класс реализует IReadOnlyCollection
, это должно быть только для чтения. Collection<T>
нет. Так что для Collection<T>
реализация интерфейса, подразумевающего только чтение, может привести к довольно неприятным проблемам. Потребители IReadOnlyCollection
мы собираемся сделать определенные предположения об этой базовой коллекции - например, итерации по ней безопасны, потому что никто не может изменять ее, и т. д. и т. д. Если он структурирован так, как вы предполагаете, это может быть не так!
Рассуждения об объектно-ориентированном проектировании будут основываться на отношении"Is A" между классом и любыми интерфейсами, которые реализует класс.
В.NET 4.5/ C# 5.0 List<T>
это одновременно ICollection<T>
а такжеIReadOnlyCollection<T>
, Вы можете создать экземплярList<T>
в зависимости от того, как вы хотите использовать свою коллекцию.
Имея ICollection<T>
воплощать в жизнь IReadOnlyCollection<T>
даст вам возможность иметь List<T>
быть либо ICollection<T>
или жеIReadOnlyCollection<T>
Делая это будет говорить, чтоICollection<T>
"Это"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>
Похоже, что это коллекция только для чтения. Подождите, давайте посмотрим на IReadOnlyCollection и ааа... это реализует IEnumerable<T>
а также IEnumerable
так что это не обязательно должна быть коллекция только для чтения. Функционально реализации, предложенные в исходном вопросе, одинаковы, но я считаю, что с точки зрения семантики более правильно выдвинуть реализацию интерфейса настолько высоко, насколько это возможно.
Этот интерфейс скорее интерфейс описания, чем действительно функциональная вещь.
В предлагаемом подходе каждая коллекция должна пониматься как нечто, что вы можете только прочитать, объявление всегда одинаково. Таким образом, вы не можете добавить или удалить вещь после создания.
Мы могли бы спросить себя, почему интерфейс называется IReadOnlyCollection
, а не 'IReadOnlyEnumerable', как он использует IEnumerable<T>, IEnumerable
, Ответ прост, этот тип интерфейса не будет иметь никакого смысла, потому что Enumerable уже "только для чтения".
Итак, давайте посмотрим в документ IReadOnlyCollection(T)
Первый (и последний) в описании senetece гласит:
Представляет строго типизированную коллекцию элементов только для чтения.
И я думаю, что это все объясняет, если вы знаете, для чего нужна строгая типизация.