в объявлении поля модификатор readonly указывает на то что
Немного о модификаторе readonly
Ключевое слово readonly — это модификатор, который можно использовать для полей. Если объявление поля содержит модификатор readonly, присвоение значений таким полям может происходить только как часть объявления или в конструкторе в том же классе.
Там же приводится пример использования данного модификатора. Все как бы хорошо, однако, данный пример приведен для типов значений, для ссылочных типов никаких пояснений не приводится.
В одной из книг приводился пример:
Данный кусок кода вызвал у меня определенное замешательство. Объект documentQueue объявлен как readonly, так каким же образом мы можем добавлять в него элементы? Я предположил, что, возможно, элементы данного объекта становятся readonly. Я написал аналогичный класс, но с использованием List. К моему удивлению, элементы изменялись без каких-либо трудностей.
Тогда я создал еще один объект Queue documentQueue2 и попробовал присвоить значение documentQueue( documentQueue=documentQueue2). И только в этом случае IntelieSense показала ошибку, что нельзя присвоить значение полю доступному только для чтения.
Таким образом, для ссылочных типов модификатор доступа readonly применяется к ссылке — ее нельзя будет изменить в будущем. Публичные же свойства, объекта ссылочного типа, остаются доступными для изменения.
Надеюсь, данная статья поможет таким же новичкам как и я быстрее освоить изучаемый язык.
О песочнице
Это «Песочница» — раздел, в который попадают дебютные посты пользователей, желающих стать полноправными участниками сообщества.
Если у вас есть приглашение, отправьте его автору понравившейся публикации — тогда её смогут прочитать и обсудить все остальные пользователи Хабра.
Чтобы исключить предвзятость при оценке, все публикации анонимны, псевдонимы показываются случайным образом.
О модерации
Не надо пропускать:
readonly (Справочник по C#)
Ключевое слово readonly — это модификатор, который может использоваться в четырех контекстах:
В объявлении поля readonly указывает на то, что присвоение значения полю может происходить только при объявлении или в конструкторе этого класса. Полю только для чтения можно несколько раз назначить значения в объявлении поля и в конструкторе.
Поле readonly нельзя изменять после выхода из конструктора. Это правило влечет за собой разные последствия для типов значений и ссылочных типов:
Видимый извне тип, который содержит видимое извне и доступное только для чтения поле с изменяемым ссылочным типом, может представлять уязвимость и приводить к предупреждению CA2104: Не объявляйте изменяющиеся ссылочные типы только для чтения.
В определении типа readonly struct объект readonly указывает на то, что тип структуры является неизменяемым. Дополнительные сведения см. в описании структуры readonly в статье Типы структур.
В объявлении члена экземпляра в типе структуры readonly указывает на то, что член экземпляра не изменяет состояние структуры. Дополнительные сведения см. в разделе о членах экземпляров readonly в статье Типы структур.
В возврате метода ref readonly модификатор readonly указывает, что метод возвращает ссылку, и записи для этой ссылки не допускаются.
Контексты readonly struct и ref readonly были добавлены в C# 7.2. Члены структуры readonly добавлены в C# 8.0
Пример поля только для чтения
Можно присвоить значение полю readonly только в следующих контекстах:
Когда переменная инициализируется в объявлении, например:
В конструкторе экземпляра класса, содержащего объявление поля экземпляра.
В статическом конструкторе класса, содержащего объявление статического поля.
Эти контексты конструктора являются единственными, в которых можно передавать поле readonly в качестве параметра out или ref.
Ключевое слово readonly отличается от ключевого слова const. Поле const может быть инициализировано только при объявлении поля. Поле readonly может быть назначено несколько раз в объявлении поля и в любом конструкторе. Таким образом, поля readonly могут иметь разные значения в зависимости от использованного конструктора. К тому же, поскольку поле const является константой времени компиляции, поле readonly можно использовать для констант времени выполнения, как в следующем примере:
В предыдущем примере при использовании такого оператора:
будет отображено сообщение об ошибке компилятора:
Присваивание значений доступному только для чтения полю допускается только в конструкторе и в инициализаторе переменных.
Пример возвращаемой ссылки только для чтения
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.
Вы также можете ознакомиться с предложениями языковых спецификаций:
ReadOnly (Visual Basic)
Указывает, что переменная или свойство может быть прочитано, но не записано.
Комментарии
Правила
Контекст объявления. ReadOnly можно использовать только на уровне модуля. Это означает, что контекст объявления для ReadOnly элемента должен быть классом, структурой или модулем и не может быть исходным файлом, пространством имен или процедурой.
Комбинированные модификаторы. Нельзя указывать ReadOnly вместе с Static в одном объявлении.
Присвоение значения. Код, использующий ReadOnly свойство, не может задать его значение. Но код, имеющий доступ к базовому хранилищу, может назначить или изменить значение в любое время.
Значение переменной можно присвоить ReadOnly только в ее объявлении или в конструкторе класса или структуры, в которой он определен.
Когда следует использовать переменную только для чтения
Существуют ситуации, в которых нельзя использовать оператор Const для объявления и присваивания постоянного значения. Например, Const инструкция может не принять тип данных, который необходимо назначить, или не удастся вычислить значение во время компиляции с помощью константного выражения. Возможно, вы даже не узнаете значение во время компиляции. В таких случаях можно использовать ReadOnly переменную для хранения постоянного значения.
Обратите внимание, что это похоже на объявление параметра процедуры как ByVal, что не позволяет процедуре изменять сам аргумент вызова, но позволяет ему изменять его члены.
Пример
Модификатор ReadOnly можно использовать в следующих контекстах:
Struct и readonly: как избежать падения производительности
Использование типа Struct и модификатора readonly иногда может порождать падения производительности. Сегодня мы расскажем о том, как этого избегать, используя один Open Source анализатор кода — ErrorProne.NET.
Как вы, вероятно, знаете из моих предыдущих публикаций «The ‘in’-modifier and the readonly structs in C#» («Модификатор in и структуры readonly в C#») и «Performance traps of ref locals and ref returns in C#» («Ловушки производительности при использовании локальных переменных и возвращаемых значений с модификатором ref»), работать со структурами сложнее, чем может показаться. Оставив в стороне вопрос изменяемости, замечу, что поведение структур с модификатором readonly (только для чтения) и без него в контекстах readonly сильно различается.
Предполагается, что структуры используются при программировании сценариев, требующих высокой производительности, и для эффективной работы с ними вы должны кое-что знать о различных скрытых операциях, порождаемых компилятором для обеспечения неизменности структуры.
Вот краткий перечень предостережений, которые вы должны помнить:
Вот несколько правил, которые следует запомнить. И, что наиболее важно, код, который опирается на эти правила, очень хрупкий (т. е. изменения, вносимые в код, немедленно порождают существенные изменения в других частях кода или документации — прим. перев.). Сколько человек заметят, что замена public readonly int X ; на public int X < get; >в часто используемой структуре без модификатора readonly существенно влияет на производительность? Или насколько легко увидеть, что передача параметра с помощью модификатора in вместо передачи по значению может снизить производительность? Это действительно возможно при использовании свойства in параметра в цикле, когда защитная копия создается при каждой итерации.
Такие свойства структур буквально взывают к разработке анализаторов. И зов был услышан. Встречайте ErrorProne.NET — набор анализаторов, который информирует вас о возможности изменения программного кода для улучшения его дизайна и производительности при работе со структурами.
Анализ кода с выводом сообщений «Сделайте структуру X readonly»
Лучший способ избежать трудноуловимых ошибок и негативного влияния на производительность при использовании структур — по возможности сделать их readonly. Модификатор readonly в объявлении структуры четко выражает намерение разработчика (подчеркивая, что структура неизменяема) и помогает компилятору избежать порождения защитных копий во многих контекстах, упомянутых выше.
Объявление структуры readonly не нарушает целостности кода. Вы можете без опасений запустить фиксер (процесс исправления кода) в пакетном режиме и объявить все структуры всего программного решения доступными только для чтения.
Дружественность по отношению к модификатору ref readonly
Следующий шаг — оценка безопасности использования новых возможностей (модификатора in, локальных переменных ref readonly и т. п.). Это означает, что компилятор не будет создавать скрытые защитные копии, способные снизить производительность.
Можно рассмотреть три категории типов:
Вторая категория — это структуры без модификатора readonly, не содержащие открытых полей. В этом случае любой доступ к открытому члену в контексте readonly вызовет создание защитной копии.
Последняя категория — это структуры с полями public или internal и свойствами или методами public либо internal. В этом случае компилятор создает защитные копии в зависимости от используемого члена.
Такое разделение помогает мгновенно выводить предупреждения, если «недружественная» структура передается с модификатором in, сохраняется в локальной переменной ref readonly и т. д.
Анализатор не выводит предупреждения, если «недружественная» структура используется как поле readonly, поскольку альтернатива в этом случае отсутствует. Модификаторы in и ref readonly разработаны с целью оптимизации, специально, чтобы избежать создания избыточных копий. Если структура «недружелюбна» по отношению к этим модификаторам, у вас есть другие возможности: передать аргумент по значению или сохранить копию в локальной переменной. В этом отношении поля readonly ведут себя иначе: если вы хотите сделать тип неизменяемым, то должны использовать эти поля. Помните: код должен быть ясным и элегантным, и только во вторую очередь — быстрым.
Анализ скрытых копий
Компилятор выполняет много действий, скрытых от пользователя. Как было показано в предыдущей публикации, довольно сложно увидеть, когда происходит создание защитной копии.
Анализатор выявляет следующие скрытые копии:
Обратите внимание, что анализаторы выводят диагностические сообщения, только если размер структуры ≥16 байт.
Использование анализаторов в реальных проектах
Передача больших структур по значению и, как результат, создание компилятором защитных копий существенно влияют на производительность. По крайней мере, это показывают результаты тестов производительности. Но как эти явления повлияют на реальные приложения в терминах времени сквозного прохождения?
Чтобы протестировать анализаторы с помощью реального кода, я использовал их для двух проектов: проекта Roslyn и внутреннего проекта, над которым я сейчас работаю в компании Microsoft (проект представляет собой самостоятельное компьютерное приложение с жесткими требованиями к производительности); назовем его для ясности «Проект D».
Означает ли это, что описанные выше возможности бесполезны? Вовсе нет.
Работа над проектом с высокими требованиями к производительности (например, над Roslyn или «Проектом D») подразумевает, что большое количество людей тратят массу времени на различные виды оптимизации. В самом деле, в ряде случаев структуры в нашем коде передавались с модификатором ref, и некоторые поля были объявлены без модификатора readonly, чтобы исключить порождение защитных копий. Отсутствие роста производительности при передаче структур с модификатором in может означать, что код был хорошо оптимизирован и на критических путях его прохождения отсутствует избыточное копирование структур.
Что я должен делать с этими возможностями?
Я считаю, что вопрос использования модификатора readonly для структур не требует долгих размышлений. Если структура неизменяема, то модификатор readonly просто явно принуждает компилятор к такому проектному решению. И отсутствие защитных копий для подобных структур — просто бонус.
Сегодня мои рекомендации таковы: если структуру можно сделать readonly, то непременно сделайте ее таковой.
Использование других рассмотренных возможностей имеет нюансы.
Предварительная оптимизация против предварительной пессимизации?
Герб Саттер (Herb Sutter) в своей удивительной книге «Стандарты кодирования на C++: 101 правило, рекомендации и передовой опыт» вводит понятие «предварительной пессимизации».
«При прочих равных условиях, сложность кода и его удобочитаемость, некоторые эффективные шаблоны проектирования и идиомы кодирования должны естественным образом стекать с кончиков ваших пальцев. Такой код не сложнее в написании, чем его пессимизированные альтернативы. Вы не занимаетесь предварительной оптимизацией, а избегаете добровольной пессимизации».
С моей точки зрения, параметр с модификатором in — как раз тот самый случай. Если вы знаете, что структура относительно велика (40 байт и более), то можете всегда передавать ее с модификатором in. Цена использования модификатора in сравнительно невелика, поскольку при этом не нужно корректировать вызовы, а выгоду можно получить реальную.
Напротив, для локальных переменных и возвращаемых значений с модификатором readonly ref дело обстоит иначе. Я бы сказал, что эти возможности следует использовать при кодировании библиотек, а в коде приложения от них лучше отказаться (только если профилирование кода не выявит, что операция копирования реально является проблемой). Использование этих возможностей требует от вас дополнительных усилий, а читателю кода становится сложнее его понять.
Написание безопасного и эффективного кода C#
C# позволяет создавать проверяемый безопасный код с более высокой производительностью. Если вы будете внимательно применять эти методы, у вас будет меньше сценариев, требующих небезопасного кода. Эти функции упрощают использование ссылок на типы значений в качестве аргументов метода и возвращаемых значений метода. При безопасном выполнении эти методики сводят к минимуму копирование типов значений. Используя типы значений, можно свести к минимуму число распределений и сборок мусора.
В большей части примера кода в этой статье демонстрируются функции, добавленные в C# 7.2. Чтобы использовать эти функции, убедитесь, что проект не настроен на использование более ранней версии. Дополнительные сведения см. в разделе Настройка языковой версии.
Преимущество использования типов значений заключается в том, что они часто позволяют избежать выделения памяти в кучах. Недостаток состоит в том, что они копируются по значению. Этот компромисс усложняет оптимизацию алгоритмов, работающих с большими объемами данных. Языковые функции, описываемые в этой статье, предоставляют механизмы, которые обеспечивают безопасный эффективный код с использованием ссылок на типы значений. При рациональном использовании этих функций можно свести к минимуму число операций выделения и копирования.
В статье также объясняются некоторые низкоуровневые оптимизации, которые рекомендуется использовать при запуске профилировщика и выявлении узких мест.
Эти методы поддерживают компромисс между двумя целями:
Сокращение количества выделений в куче.
Переменные, являющиеся ссылочными типами, содержат ссылку на расположение в памяти и выделяются в управляемой куче. Ссылка копируется только когда ссылочный тип передается в качестве аргумента в метод или возвращается из метода. Каждый новый объект требует новое распределение и впоследствии должен быть освобожден. Сборка мусора занимает некоторое время.
Сокращение числа копирований значений.
Переменные, которые являются типами значений, непосредственно содержат их значения, и значение обычно копируется при передаче в метод или возвращается из метода. Это поведение включает в себя копирование значения this при вызове членов типа значения. Операция копирования занимает некоторое время, в зависимости от размера типа.
В этой статье используется следующий пример концепции трехмерной структуры для объяснения этих рекомендаций:
Различные примеры использования других реализаций этой концепции.
Объявите неизменяемые структуры как readonly
Этих двух правил достаточно, чтобы убедиться, что ни один из элементов readonly struct не изменяет состояние этой структуры. Объект struct является неизменяемым. Структура Point3D может быть определена как неизменяемая, как показано в следующем примере:
Следуйте этим рекомендациям, когда планируете создать неизменяемый тип значения. Улучшения производительности являются дополнительным преимуществом. Ключевые слова readonly struct четко выражают намерение проекта.
Объявите элементы readonly для изменяемых структур.
Рассмотрим другое приложение, для которого требуется структура объемных точек, однако оно должно поддерживать изменяемость. Следующая версия структуры объемных точек добавляет модификатор readonly только к тем элементам, которые не изменяют структуру. Используйте этот пример, если проект должен поддерживать модификации структуры некоторыми членами, однако вам все равно нужны преимущества реализации через readonly для некоторых членов:
Использование инструкций ref readonly return
Тем не менее, следующее определение свойства может возвращаться по ссылке, так как возвращаемое значение является статическим элементом:
Вы не хотите, чтобы вызывающие объекты изменяли источник, поэтому следует возвращать значение через ref readonly :
Возвращение ref readonly позволяет сохранить копирование больших структур и неизменность внутренних элементов данных.
Во время вызова вызывающие объекты выбирают использовать свойство Origin как ref readonly или как значение:
С добавлением ключевого слова in C# предоставляет полный словарь для выражения намерения проекта. Если в сигнатуре метода не указан ни один из следующих модификаторов, типы значений копируются при передаче в вызываемый метод. Каждый из этих модификаторов указывает, что переменная передается по ссылке, предотвращая копирование. Каждый модификатор выражает конкретное намерение.
При добавлении модификатора in для передачи аргумента по ссылке вы объявляете о своем намерении передавать аргументы по ссылке, чтобы избежать ненужных операций копирования. Вы не собираетесь изменять объект, используемый в качестве этого аргумента.
Модификатор in также можно использовать со ссылочными типами или числовыми значениями. Однако преимущества в этих случаях минимальны (если они вообще есть).
Использование параметров in для больших структур
Ниже приведен пример метода, который вычисляет расстояние между двумя точками в трехмерном пространстве.
Необязательное использование in на сайте вызова
Пропуск модификатора in в месте вызова сообщает компилятору, что он может сделать копию аргумента по следующим причинам:
Чтобы велеть компилятору передавать аргументы, доступные только для чтения, по ссылке, укажите модификатор in для аргументов в месте вызова, как показано в следующем коде:
Избегание защитных копий
Вы видите пример программы, который демонстрирует разницу в производительности с помощью BenchmarkDotNet в наших репозиториях примеров на сайте GitHub. Он сравнивает передачу изменяемых структур по значению и по ссылке с передачей неизменяемых структур по значению и по ссылке. Быстрее всего использовать неизменяемую структуру и передачу по ссылке.
Использование типов ref struct
Использование типов nint и nuint
Целочисленные типы собственного размера — это 32-разрядные целые числа в 32-разрядном процессе или 64-разрядные целые числа в 64-разрядном процессе. Используйте их для сценариев взаимодействия, с низкоуровневыми библиотеками и для оптимизации производительности в сценариях, где часто выполняются математические операции с целыми числами.
Выводы
Использование типов значений сводит к минимуму число операций распределения:
Сравните это со ссылочными типами в таких же ситуациях:
Чтобы свести распределения к минимуму, придется пойти на компромисс. Вы копируете больше памяти, если размер struct больше, чем размер ссылки. Ссылка обычно является 64- или 32-разрядной и зависит от ЦП целевого компьютера.
Эти компромиссы обычно имеют минимальное влияние на производительность. Однако для больших структур или больших коллекций влияние на производительность возрастает. Влияние может быть большим в плотных циклах и часто используемых путях для программ.