Я сомневаюсь в моем понимании семантики System.Collection.Generic.IReadOnlyCollection<T>
и сомневается, как разрабатывать, используя понятия, такие как чтение и неизменяемость. Позвольте мне описать две природы, с которыми я сомневаюсь, используя документацию, в которой говорится
Представляет собой типизированный, только для чтения набор элементов.
В зависимости от того, подчеркиваю ли я слова "Представляет" или "только для чтения" (при произнесении в голове или вслух, если это ваш стиль), я чувствую, что изменение предложения означает:
- Когда я подчеркиваю "только для чтения", документация определяет, на мой взгляд, неизменность наблюдения (термин, используемый в статья Эрика Липперта) это означает, что реализация интерфейса может делать то, что ему нравится, пока мутации не видны публично.
- Когда я подчеркиваю "Представляет", документация определяет (на мой взгляд, снова) неизменный фасад (снова описанный в статье Эрика Липперта), который является более слабой формой, где мутации могут быть возможны, но просто не могут быть сделаны Пользователь. Например, свойство типа
IReadOnlyCollection<T>
разъясняет пользователю (то есть кому-то, который кодирует тип объявления), что он не может изменять эту коллекцию. Однако неоднозначно, может ли сам тип объявления модифицировать коллекцию. - Для полноты: интерфейс не несет никакой семантики, отличной от той, которая несет подписи ее членов. В этом случае неизменяемость наблюдения или фасанта зависит от реализации (а не только от реализации зависимого от интерфейса, но зависит от экземпляра).
Первый вариант - это действительно моя предпочтительная интерпретация, хотя этот контракт можно легко сломать, например. построив ReadOnlyCollection<T>
из массива T
, а затем установив значение в массив обертки.
BCL имеет превосходные интерфейсы для неизменности фасада, такие как IReadOnlyCollection<T>
, IReadOnlyList<T>
и, возможно, даже IEnumerable<T>
и т.д. Однако я считаю, что неизменяемость наблюдения также полезна и, насколько я знаю, интерфейсы в BCL, занимающиеся этим значением (просьба указать мне, если я ошибаюсь). Имеет смысл, что этого не существует, потому что эта форма неизменности не может быть реализована декларацией интерфейса, только разработчиками (интерфейс может нести семантику, хотя, как я покажу ниже). Кроме того: я бы хотел иметь эту возможность в будущей версии С#!
Пример: (может быть пропущен) Мне часто приходится внедрять метод, который получает в качестве аргумента коллекцию, которая используется другим потоком, но метод требует, чтобы сбор не изменялся во время его выполнение, и поэтому я объявляю, что параметр имеет тип IReadOnlyCollection<T>
и дает мне похлопывание по спине, думая, что я выполнил требования. Неверно... Для вызывающего, что подпись выглядит так, как если бы метод promises не менял коллекцию, ничего больше, и если вызывающий принимает вторую интерпретацию документации (фасад), он может просто подумать, что мутация разрешена, а метод, о котором идет речь, устойчив к этому. Хотя для этого примера есть и другие более традиционные решения, надеюсь, вы видите, что эта проблема может быть практической проблемой, особенно когда другие используют ваш код (или будущее - вы, если на то пошло).
Итак, теперь к моей фактической проблеме (которая вызвала сомнения в семантике существующих интерфейсов):
Я хотел бы использовать неизменяемость наблюдений и неизменность фасада и различать их. Два варианта, о которых я думал:
- Используйте интерфейсы BCL и документируйте каждый раз, независимо от того, является ли это наблюдательным или просто неизменным. Недостаток: пользователи, использующие такой код, будут обращаться к документации только в том случае, если уже слишком поздно, а именно, когда обнаружена ошибка. Я хочу привести их к победе; документация не может этого сделать). Кроме того, я нахожу такую семантику достаточно важной, чтобы быть видимой в системе типов, а не только в документации.
- Определите интерфейсы, которые явно несут семантику неизменяемости наблюдений, например
IImmutableCollection<T> : IReadOnlyCollection<T> { }
иIImmutableList<T> : IReadOnlyList<T> { }
. Обратите внимание, что интерфейсы не имеют каких-либо членов, кроме унаследованных. Цель этих интерфейсов состояла бы в том, чтобы просто сказать: "Даже декларирующий тип меня не изменит!". Я специально сказал "не буду" здесь, а не "не могу". В этом заключается недостаток: злой (или ошибочный, чтобы оставаться вежливым) исполнителем не помешал нарушить этот контракт компилятором или чем-то действительно. Преимущество состоит в том, что программист, который решил реализовать этот интерфейс, а не тот, который он непосредственно наследует, скорее всего, знает о дополнительном сообщении, отправленном этим интерфейсом, поскольку программист знает о существовании этого интерфейса и тем самым вероятно, для его реализации.
Я собираюсь пойти со вторым вариантом, но боюсь, что у него есть проблемы с дизайном, сопоставимые с типами делегатов (которые были изобретены для переноса семантической информации по их семантичным аналогам Func
и Action
) и как-то это не удалось, см., например, здесь.
Я хотел бы знать, если вы столкнулись/обсуждали эту проблему, или я слишком много болтаю о семантике и должен просто принять существующие интерфейсы и не знаю ли я о существующих решениях в BCL. Любые проблемы с дизайном, такие как упомянутые выше, были бы полезными. Но меня особенно интересуют другие решения, которые вы могли бы (иметь) решить мою проблему (что в двух словах отличает наблюдательность и неизменность фасада как в декларации, так и в использовании). Заранее благодарю вас.
† Я игнорирую мутации полей и т.д. на элементах коллекции.
‡ Это действительно для примера, который я дал ранее, но утверждение действительно более широкое. Например, любой метод объявления не изменит его, или параметр такого типа передает, что метод может ожидать, что коллекция не будет изменяться во время ее выполнения (что отличается от того, что метод не может изменить коллекцию, которая является единственной оператор, который можно сделать с существующими интерфейсами) и, возможно, многие другие.