Static readonly vs const в C#: в чем разница и что выбрать?
Основное различие: время компиляции vs время выполнения
Главное отличие между const и static readonly в том, когда значение фиксируется:
const— значение определяется на этапе компиляции. Компилятор подставляет значение напрямую в код (как литерал).static readonly— значение определяется на этапе выполнения (при инициализации класса или статического конструктора).
Практические последствия
1. Типы данных
const поддерживает только примитивные типы (int, double, string, bool, enum, null). static readonly может быть любого типа, включая ссылочные:
public const string AppName = "MyApp"; // ✅ OK
public const DateTime StartDate = ...; // ❌ Ошибка! DateTime — не константный тип
public static readonly DateTime StartDate = new DateTime(2023, 1, 1); // ✅ OK
2. Производительность
const быстрее, так как компилятор подставляет значение на этапе компиляции. Но разница заметна только в высоконагруженных циклах или при миллионах вызовов.
3. Подводный камень с DLL (самое важное!)
Если вы используете const в публичной библиотеке (DLL), то значение "вшивается" в код на стороне клиента при компиляции. Если вы потом измените значение в библиотеке и пересоберете только её (без перекомпиляции клиента), клиент продолжит использовать старое значение.
// Библиотека (version 1.0)
public class Config
{
public const int MaxUsers = 100; // Клиент скомпилировал с value = 100
}
// Клиент
int limit = Config.MaxUsers; // В IL-коде будет literal 100
// Библиотека (version 1.1) — изменили на 200
// Клиент НЕ перекомпилирован → limit всё ещё = 100
static readonly лишён этого недостатка — значение читается из памяти во время выполнения, поэтому клиент всегда получит актуальное значение из новой DLL (если она подгружена).
Когда что использовать?
- Используйте
constдля:- Значений, которые гарантированно не изменятся (число Пи, количество месяцев в году).
- Локальных констант внутри одного класса/метода.
- Атрибутов и параметров, требующих констант (
[DefaultValue(5)]).
- Используйте
static readonlyдля:- Значений, которые могут измениться в будущих версиях библиотеки.
- Сложных объектов (строки с вычислениями, DateTime, массивы).
- Значений, получаемых из конфигурации или вычисляемых во время выполнения.
Пример правильного выбора
public class AppSettings
{
// const — для абсолютно неизменных величин
public const int MaxRetryAttempts = 3;
// static readonly — для значений, которые могут измениться
public static readonly string ConnectionString =
ConfigurationManager.AppSettings["DbConnection"];
// static readonly — для объектов
public static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(30);
}
Запомните правило: если значение может измениться в будущем (даже теоретически) — используйте static readonly.
Дополнение: разница в IL-коде и памяти
Если заглянуть под капот, то const и static readonly по-разному хранятся в памяти:
const— не создаёт поля в памяти. Компилятор заменяет все обращения на литерал. Например,const int X = 5;везде будет заменено на5. Это значит, что при изменении константы нужно перекомпилировать все сборки, которые её используют.static readonly— создаёт статическое поле в памяти. Значение хранится один раз в области статических данных (High-Frequency Heap). При обращении генерируется IL-кодldsfld(загрузка статического поля). Это чуть медленнее, но гарантирует актуальность значения.
Ещё один нюанс: const не может быть изменён даже через рефлексию (потому что его физически нет в памяти). static readonly можно изменить через рефлексию (хотя это плохая практика):
var field = typeof(MyClass).GetField("MyField", BindingFlags.Static | BindingFlags.Public);
field.SetValue(null, newValue); // Работает для static readonly, но не для const
Итог: если ваша константа — часть публичного API или может измениться — выбирайте static readonly. Для внутренних, абсолютно неизменных значений — const. Никогда не используйте const для строк, которые могут быть локализованы или изменены.