В каком месте можно инициализировать readonly поля в C#?
Что проверяют
Знаете ли вы точные правила присваивания readonly и понимаете ли, что именно этот модификатор защищает. Почти всегда следом спрашивают про разницу с const и про readonly поле ссылочного типа.
Как отвечать
Экземплярное readonly поле можно присвоить в трёх местах:
- инициализатор при объявлении:
private readonly int _limit = 10;. Выполняется раньше тела конструктора; - конструктор экземпляра этого же класса: любой из них, присваивать можно сколько угодно раз, передавать поле как
refилиoutтоже можно. После выхода из конструктора значение зафиксировано; init-аксессор свойства (C# 9): он тоже может записатьreadonlyполе своего типа, поэтому такое поле заполняется через инициализатор объектаnew Order { Id = 5 }.
Для static readonly мест два: инициализатор статического поля и статический конструктор.
Дальше ограничения. Конструктор наследника не может присвоить readonly поле базового класса, для этого у базы делают параметр конструктора. Параметры первичного конструктора (C# 12) сами не readonly, их явно копируют в поле: private readonly ILogger _log = log;.
Разница с const: const вычисляется при компиляции и подставляется в код вызывающих сборок, поэтому смена публичной константы требует их перекомпиляции. static readonly читается во время выполнения и может быть любого типа, например TimeSpan или объект.
Где спотыкаются
Считают, что readonly List<T> делает список неизменяемым. Зафиксирована только ссылка, Add и Clear работают. Для настоящей неизменяемости берут ImmutableArray<T>, а IReadOnlyList<T> лишь прячет методы изменения от потребителя.
Забывают про защитные копии: вызов метода у readonly поля изменяемой структуры идёт на копии, и изменение молча теряется. Выход: объявить структуру как readonly struct или не мутировать её через методы.
Путают readonly с const и объявляют публичной константой значение, которое может поменяться между версиями библиотеки.