2013-07-24 1 views
12

Я сомневаюсь в моем понимании семантики и сомневаюсь в том, как разрабатывать, используя концепции, такие как только для чтения и неизменные. Опишу два естества я сомневающихся между помощью documentation, в котором говоритсяСемантика неизменности/только для чтения (в частности, C# IReadOnlyCollection <T>)

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

В зависимости от стресса ли я слово «означает» или «только для чтения» (при произнесении в моей голове, или вслух, если это ваш стиль) Я чувствую, что предложение меняется означающий:

  1. Когда я подчеркиваю «только для чтения», документация определяет, на мой взгляд, неизменяемость наблюдений (термин, используемый в Eric Lippert's article), что означает, что реализация интерфейса может делать то, что ему нравится, пока мутации не видны публично †.
  2. Когда я подчеркиваю «Представляет», документация определяет (на мой взгляд, снова) неизменный фасад (снова описанный в статье Эрика Липперта), который является более слабой формой, где мутации могут быть возможны, но просто не могут быть сделаны Пользователь. Например, свойство типа IReadOnlyCollection<T> дает понять пользователю (то есть кому-то, кто кодов против типа объявления), что он не может изменять эту коллекцию. Однако неоднозначно, может ли сам тип объявления изменить коллекцию.
  3. Для полноты: интерфейс не несет никакой семантики, отличной от той, которая несет подписи ее членов. В этом случае неизменяемость наблюдения или фасанта зависит от реализации (а не только от реализации зависимого от интерфейса, но зависит от экземпляра).

Первый вариант на самом деле является моей предпочтительной интерпретацией, хотя этот контракт можно легко сломать, например. путем построения ReadOnlyCollection<T> из массива T и затем установки значения в массив обертки.

BCL имеет отличные интерфейсы для фасадов неизменности, такие как IReadOnlyCollection<T>, IReadOnlyList<T> и, возможно, даже IEnumerable<T>, и т.д. Тем не менее, я считаю наблюдательную неизменность также полезно и насколько я знаю, нет никаких интерфейсов в BCL Carring это значение (просьба указать мне, если я ошибаюсь). Имеет смысл, что этого не существует, потому что эта форма неизменности не может быть реализована декларацией интерфейса, только разработчиками (интерфейс может нести семантику, хотя, как я покажу ниже). Кроме того: я хотел бы иметь эту способность в будущей версии C#!


Пример: (может быть пропущена) Я часто должны реализовать метод, который получает в качестве аргумента коллекцию, которая используется другим потоком, а также, но этот метод требует сбора, чтобы не быть изменены в ходе ее поэтому я объявляю параметр типа IReadOnlyCollection<T> и даю себе похлопывание по спине, думая, что я выполнил требования. Неверно ... Для вызывающего абонента такая подпись выглядит так, как будто метод обещает не менять коллекцию, ничего другого, и если вызывающий принимает вторую интерпретацию документации (фасад), он может просто подумать, что мутация разрешена, а метод в вопрос устойчив к этому. Хотя для этого примера есть и другие более традиционные решения, надеюсь, вы видите, что эта проблема может быть практической проблемой, особенно когда другие используют ваш код (или будущее - вы, если на то пошло).


Так что теперь к моим реальным проблемам (которые срабатывающий ставить под сомнение существующих интерфейсов семантики):

Я хотел бы использовать наблюдательную неизменность и фасад неизменность и различие между ними. Возможны два варианта:

  1. Используйте интерфейсы и документы BCL каждый раз, независимо от того, является ли это наблюдательным или просто неизменным. Недостаток: пользователи, использующие такой код, будут обращаться к документации только в том случае, если уже слишком поздно, а именно, когда обнаружена ошибка. Я хочу привести их к победе; документация не может этого сделать). Кроме того, я нахожу такую ​​семантику достаточно важной, чтобы быть видимой в системе типов, а не только в документации.
  2. Определите интерфейсы, которые несут семантику неизменяемости наблюдений явно, например IImmutableCollection<T> : IReadOnlyCollection<T> { } и IImmutableList<T> : IReadOnlyList<T> { }. Обратите внимание, что интерфейсы не имеют каких-либо членов, кроме унаследованных. Цель этих интерфейсов состояла бы в том, чтобы просто сказать: «Даже декларирующий тип меня не изменит!». Я специально сказал «не буду» здесь, а не «не могу». В этом заключается недостаток: злой (или ошибочный, чтобы оставаться вежливым) исполнителем не помешал нарушить этот контракт компилятором или чем-то действительно. Преимущество состоит в том, что программист, который решил реализовать этот интерфейс, а не тот, который он непосредственно наследует, скорее всего, знает о дополнительном сообщении, отправленном этим интерфейсом, поскольку программист знает о существовании этого интерфейса и тем самым вероятно, для его реализации.

Я думал идти с вторым вариантом, но я боюсь, что есть проблемы дизайна, сравнимые с типами делегатов (которые были изобретены нести смысловую информацию по их semanticless коллегами Func и Action) и как-то, что не удались, см. например, here.

Я хотел бы знать, если вы столкнулись/обсуждали эту проблему, или я слишком много болтаю о семантике и должен просто принять существующие интерфейсы и не знаю ли я о существующих решениях в BCL. Любые проблемы с дизайном, такие как упомянутые выше, были бы полезными. Но меня особенно интересуют другие решения, которые вы могли бы (иметь) решить мою проблему (что в двух словах отличает наблюдательность и неизменность фасада как в декларации, так и в использовании).
Спасибо заранее.


† Я игнорирую мутации полей и т. Д. На элементах коллекции.
‡ Это действительно для примера, который я дал ранее, но утверждение действительно более широкое. Например, любой метод объявления не изменит его, или параметр такого типа передает, что метод может ожидать, что коллекция не будет изменяться во время ее выполнения (что отличается от того, что метод не может изменить коллекцию, которая является единственной оператор, который можно сделать с существующими интерфейсами), и, возможно, многие другие.

+1

Возможно, это актуально: http: //blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx –

+6

Я одобряю использование термина «Неизменяемый» как часть имени класса для наблюдательно неизменяемых коллекций , и «Readonly» как часть имени класса для неизменяемых фасадов для изменяемых коллекций. Мне также нравятся не-коллекции классов со словом «Неизменяемый» в их имени, чтобы быть «глубоко неизменными», то есть поля являются частными типами значений readonly или являются частными ссылочными типами readonly, которые сами по себе являются неизменными. ('readonly' является ключевым словом C#) (Это, по-видимому, также является соглашением, принятым Microsoft, так счастливые дни.) –

+0

Я не на самом деле берет то, что вы просите, но IImmutablexxx означает, что он не изменится (он может измениться, но изменение создаст новую коллекцию), IReadOnlyxxx запрещает получателю изменять коллекцию, которую поставщик может изменить. –

ответ

1

Интерфейс не может обеспечить неизменность. Слово в названии не предотвратит изменчивость, это просто еще один намек, подобный документации.

Если вы хотите непреложный объект, вам нужен конкретный тип, который является неизменным. В C# неизменяемость зависит от реализации и не отображается в интерфейсе.

Как сказал Крис, вы можете найти existing implementations of immutable collections.

+0

Помимо нескольких «маркерных» интерфейсов, единственное, что * любой * интерфейс может гарантировать, что любой класс, который его реализует, будет либо соблюдать контракт интерфейса, либо считаться «сломанной» реализацией. Если в контракте для 'ImmutableFloatMatrix' указано, что каждый вызов GetValue (X, Y)' должен всегда возвращать тот же результат для любой данной (X, Y) пары, потребители не менее надежно предполагают, что реализация 'ImmutableFloatMatrix 'было неизменным, чем предполагать, что передача« Fred »' List .Add' не добавит «Barney». – supercat