Я понимаю, почему ты так думаешь. Есть функция, которая ожидает коллекцию и ожидает, что ее можно изменить. Передача массива приведет к сбою, поэтому очевидно, что вы не можете заменить интерфейс этой конкретной реализацией, верно?
Это проблема? Может быть. Это зависит от того, как часто вы ожидаете, что идеалы сохранятся. Собираетесь ли вы случайно использовать массив вместо коллекции, а затем через десять лет удивитесь, что она сломается? Не совсем. Система типов, используемая приложениями .NET, не идеальна - она не говорит вам, что это конкретное ICollection<T>
использование требует, чтобы коллекция была модифицируемой.
Было бы лучше для .NET, если бы массивы не претендовали на реализацию ICollection<T>
(или IEnumerable<T>
, которые они также «на самом деле» не реализуют)? Я так не думаю. Есть ли способ сохранить удобство того, что массивы «будут» ICollection<T>
, а также избежать того же нарушения LSP? Неа. Базовый массив по-прежнему будет иметь фиксированную длину - в лучшем случае вместо этого вы нарушите более полезные принципы (например, тот факт, что ссылочные типы не должны иметь ссылочную прозрачность).
Но ждать! Давайте посмотрим на настоящий контракт ICollection<T>.Add
. Позволяет ли это NotSupportedException
быть брошенным? Ах да, цитируя MSDN:
[NotSupportedException возникает, если...] Коллекция ICollection доступна только для чтения.
И массивы действительно возвращают true, когда вы запрашиваете IsReadOnly
. Контракт сохраняется.
Если вы считаете, что Stream
не нарушает LSP из-за CanWrite
, вы должны считать массивы допустимыми коллекциями, поскольку они имеют IsReadOnly
, а это true
. Если функция принимает доступную только для чтения коллекцию и пытается добавить в нее, это ошибка функции. Невозможно указать это явно в C#/.NET, поэтому вам нужно полагаться на другие части контракта, а не только на типы, например. в документации к функции должно быть указано, что NotSupportedException
(или ArgumentException
, или что-то еще) выдается для коллекции, доступной только для чтения. Хорошая реализация сделает этот тест прямо в начале функции.
Важно отметить, что типы в C# не так ограничены, как в теории типов, где определена LSP. Например, вы можете написать такую функцию на C#:
bool IsFrob(object bobicator)
{
return ((Bob)bobicator).IsFrob;
}
Можно ли заменить bobicator
любым супертипом object
? Явно нет. Но точно так же это не проблема бедного типа Frobinate
— это ошибка в функции IsFrob
. На практике большая часть кода на C# (и большинстве других языков) работает только с объектами, гораздо более ограниченными, чем это может быть указано типом в сигнатуре метода.
Объект нарушает LSP только в том случае, если он нарушает контракт своего супертипа. Он не может нести ответственность за другие нарушения кодаg LSP. И часто вы обнаружите, что довольно прагматично создавать код, который идеально не работает в LSP — инженерия всегда была связана с компромиссами. Тщательно взвесьте расходы.
person
Luaan
schedule
18.05.2017
ICollection<T>.Add
уродлив, но документирует, чтоNotSupportedException
будет выброшено, еслиIsReadOnly
ложно. - person Lee   schedule 18.05.2017