C#JuniorTechnical
Объясните разницу между const и readonly в C#.
const встраивается как литерал при компиляции (только примитивы/строки), readonly вычисляется в runtime и может хранить любой тип. Ключевая ловушка const — бинарная несовместимость: при изменении значения потребители видят старое до перекомпиляции.
const vs readonly в C#
Оба ключевых слова создают «неизменяемые» поля, но с принципиально разной семантикой:
const— константа времени компиляции. Значение должно быть известно при компиляции, встраивается (inlined) в вызывающий код как литерал. Допустимые типы: числа, строки, bool, enum,null.readonly— поле, которое можно присвоить только в объявлении или в конструкторе. Вычисляется в runtime. Допустимые типы: любые, включая сложные объекты и структуры.
public class Config
{
// const: значение 3.14159 встраивается в каждый вызывающий файл
public const double Pi = 3.14159265358979;
// const string — интернируется, встраивается как литерал
public const string AppName = "Talento";
// readonly: вычисляется однажды в runtime, не встраивается
public static readonly DateTime LaunchDate = new DateTime(2024, 1, 15);
// readonly с вычислением из внешних данных
public readonly string ConnectionString;
public Config(IConfiguration configuration)
{
// Присвоение возможно только здесь
ConnectionString = configuration["Db:ConnectionString"]
?? throw new InvalidOperationException("DB connection string not set");
}
}
// Ошибка: const не может быть присвоен в конструкторе
// public const string Value;
// Value = "x"; // CS0133
// Ошибка: const не может хранить объект
// public const List<int> Items = new(); // CS0134
// Правильно для коллекций:
public static readonly IReadOnlyList<string> AllowedRoles =
new[] { "Admin", "Recruiter", "Candidate" };
Критическая проблема версионирования с const
Если сборка A определяет public const int MaxRetries = 3, а сборка B её использует, то при перекомпиляции A со значением 5 — сборка B всё равно использует старое значение 3, пока B не будет перекомпилирована. Это классическая бинарная несовместимость. С static readonly такой проблемы нет — значение читается в runtime.
// Библиотека v1
public const int MaxRetries = 3;
// После деплоя v2 с MaxRetries = 5 — старые потребители всё равно видят 3!
// Безопаснее:
public static readonly int MaxRetries = 5; // читается в runtime
readonly struct и in-параметры
// readonly struct — все поля неизменяемы, нет defensive copy
readonly struct Point
{
public double X { get; }
public double Y { get; }
public Point(double x, double y) { X = x; Y = y; }
public double Distance => Math.Sqrt(X * X + Y * Y);
}
// 'in' параметр + readonly struct = нет копии при передаче
void PrintDistance(in Point p) => Console.WriteLine(p.Distance);
Подводные камни
- Версионирование const. Публичные
constв библиотеках — ловушка: изменение значения требует перекомпиляции всех потребителей. Для публичных API предпочитайтеstatic readonly. - const и enums — то же самое.
constзначения enum также встраиваются — изменение enum-значения ломает ABI. - readonly не делает объект иммутабельным.
readonly List<int> Items = new();— ссылка не изменится, но сам список можно модифицировать. Для иммутабельности используйтеIReadOnlyList<T>илиImmutableArray<T>. - Struct без readonly — defensive copy. Если struct-поле не помечено
readonly, каждый вызов метода на нём создаёт копию (defensive copy). Добавьтеreadonlyк struct или к полю. - Потокобезопасность readonly.
readonlyгарантирует видимость значения после конструктора, но не атомарность изменений вложенных объектов. Для многопоточного доступа нуженlockилиInterlocked.
Common mistakes
- Путать константы времени компиляции и readonly-поля с похожим механизмом из другой версии или платформы.
- Игнорировать runtime-границы C#: lifecycle, DI scope, SQL translation, UI thread или platform API.
- Не обсуждать null/empty/error cases и поведение под нагрузкой.
What the interviewer is testing
- Кандидат объясняет константы времени компиляции и readonly-поля на конкретном примере, а не только определением.
- Указывает последствия для производительности, тестируемости и поддержки.
- Различает документированное поведение текущего стека и устаревшие практики.