TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #461 1K
День триста восемьдесят шестой. #MoreEffectiveCSharp
2. По возможности делайте значимые типы неизменяемыми
Неизменяемые типы просты: после того, как они созданы, они являются постоянными. Если вы проверяете параметры в конструкторе, вы знаете, что объект находится в согласованном состоянии с момента создания. Вы не можете изменить внутреннее состояние объекта и нарушить согласованность. Тем самым вы избавляете себя от множества необходимых проверок. Кроме того, неизменяемые типы потокобезопасны, могут быть безопасно экспортированы и лучше работают в коллекциях на основе хешей.

На практике очень трудно сделать каждый тип неизменяемым. Вот почему эта рекомендация предназначена для атомарных неизменяемых значимых типов. Выделите среди ваших типов структуры, которые естественным образом образуют единую сущность. Например, Адрес - это единая сущность, состоящая из нескольких связанных полей. Изменение одного поля скорее всего означает изменения в других полях. А Клиент не является атомарным типом. Он, скорее всего, будет содержать много независимой друг от друга информации: адрес, имя и один или несколько телефонных номеров. Любая из этих частей информации может измениться независимо от других.
В атомарных типах скорее всего изменится всё содержимое, а изменение одного из полей - исключение.

Внутренние изменения состояния означают, что возможно нарушить инварианты объекта, по крайней мере, временно. Например, при изменении города скорее всего изменится индекс или область. Изменение их двумя последовательными присваиваниями выглядит безопасно, но предположим, что этот фрагмент является частью многопоточной программы. Любое переключение контекста после изменения города и до изменения индекса может привести к тому, что другой поток увидит несогласованное представление данных.

У вас однопоточная программа? Тогда представьте, что после изменения города возникло исключение. Внесена лишь часть изменений, и объект остался в недопустимом состоянии. В итоге как для потоковой безопасности, так и для защиты от ошибок, пришлось бы добавлять массивный код проверки целостности объекта, который бы только увеличивался по мере эволюции приложения.

Чтобы создать неизменяемый тип, помимо запрета на изменение состояния в интерфейсе типа, необходимо убедиться, что в нем нет дыр, которые позволили бы клиентам изменять его внутреннее состояние. Структуры не поддерживают наследования, поэтому можно не защищаться от изменяемых полей в производных типах. Но нужно следить за любыми полями ссылочного типам в неизменяемом типе. Например, массив – ссылочный тип. Если массив разместить в поле readonly, то само поле (ссылка на массив) будет неизменяемо, но отдельные элементы массива всё равно можно изменить.
Чтобы исключить эту возможность, нужно делать защищённую копию массива, либо можно использовать класс ImmutableArray из пространства имен System.Collections.Immutable.

Сложность типа определяет, какую из трех стратегий использовать для инициализации неизменяемого типа:
1. Определить разумный ограниченный набор конструкторов. Это часто является самым простым подходом.
2. Создать фабричные методы для инициализации. Фабрики облегчают создание часто используемых значений. Например, тип Color следует этой стратегии для инициализации системных цветов. Статические методы Color.FromKnownColor() и Color.FromName() создают объект типа из значения предопределённого системного цвета.
3. Создать изменяемый вспомогательный класс для случаев, когда необходимы многоэтапные операции для полного создания неизменяемого типа. Примером такого вспомогательного класса является System.Text.StringBuilder для создания строк.

Неизменяемые типы проще как при написании кода, так и при его обслуживании. Не спешите слепо лепить аксессоры get и set для каждого свойства вашего типа. Первоначальным выбором для типов, хранящих данные, должны быть неизменяемые, атомарные значимые типы. Затем вы легко сможете построить более сложные структуры из этих объектов.

Источник: Bill Wagner “More Effective C#”. – 2nd ed. Глава 3.
More from @netdeveloperdiary
  1. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  2. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  3. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  4. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  5. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →