Почему общий 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.