Const vs Readonly в C#: что выбрать и в чём разница?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Const vs Readonly в C#: в чём разница и когда что применять?

Каждый разработчик на C# рано или поздно сталкивается с дилеммой: объявить значение как const или как readonly? На первый взгляд, оба ключевых слова делают одно и то же — запрещают изменение переменной после инициализации. Но копни глубже — и откроется целая пропасть различий, влияющих на архитектуру, производительность и даже безопасность кода.

В этой статье мы не просто перечислим сухие факты из документации. Мы разберём реальные сценарии, покажем подводные камни и поможем вам сделать осознанный выбор. Поехали!

Что такое const в C#?

Const — это константа времени компиляции. Её значение должно быть известно на момент компиляции и не может меняться в рантайме.

  • Может быть только примитивных типов: int, double, string, bool, enum и null.
  • Неявно static — принадлежит типу, а не экземпляру.
  • Значение подставляется прямо в код на этапе компиляции (как макрос в C++).
  • Нельзя использовать с new или ссылочными типами (кроме string).
public class MathConstants
{
    public const double Pi = 3.14159;
    public const int MaxRetries = 5;
    public const string AppName = "MyApp";
    
    // Ошибка! Ссылочные типы (кроме string) нельзя
    // public const List<int> Numbers = new List<int>();
}

Когда компилятор встречает const, он буквально встраивает значение в IL-код. Это значит, что если вы используете константу из другой сборки, и потом измените её значение, старые сборки всё ещё будут использовать старое значение — пока не будут перекомпилированы.

Что такое readonly в C#?

Readonly — это поле, которое можно инициализировать только в момент объявления или в конструкторе класса. После этого изменить его нельзя.

  • Может быть любого типа: как значимого, так и ссылочного.
  • Может быть как статическим, так и экземплярным.
  • Значение вычисляется в рантайме (например, можно использовать new или вызов метода).
  • Не подставляется в код — это обычное поле с защитой от записи.
public class Config
{
    public readonly DateTime CreatedAt = DateTime.Now;
    public readonly List<string> Tags;
    public static readonly Guid InstanceId = Guid.NewGuid();
    
    public Config()
    {
        Tags = new List<string> { "csharp", "tutorial" };
        // Ошибка! Нельзя изменить после конструктора
        // Tags = new List<string>();
    }
}

Важный нюанс: readonly защищает только ссылку, а не содержимое объекта. Если у вас readonly List<int>, вы не сможете присвоить новый список, но спокойно можете добавлять или удалять элементы.

Сравнение const и readonly: таблица различий

Чтобы закрепить материал, вот наглядное сравнение:

Критерий const readonly
Время вычисления Компиляция Выполнение (рантайм)
Типы данных Только примитивы + string Любые типы
Статичность Неявно static Может быть static или instance
Изменяемость Никогда Только в конструкторе
Доступ Как static поле Как обычное поле
Подстановка Да (встраивается в код) Нет (обычное поле)
Производительность Максимальная (нет поля) Немного медленнее (обращение к полю)

Когда использовать const?

Используйте const для значений, которые:

  • Абсолютно неизменны и никогда не поменяются: математические константы (Math.PI), коды ошибок, названия конфигураций.
  • Примитивны и не требуют вычислений в рантайме.
  • Локальны для одной сборки, либо вы готовы перекомпилировать все зависимые проекты при изменении.
// Хороший пример для const
public class HttpStatusCodes
{
    public const int Ok = 200;
    public const int NotFound = 404;
    public const int InternalServerError = 500;
}

Когда использовать readonly?

Выбирайте readonly, когда:

  • Значение зависит от рантайма: дата создания, GUID, результат вычисления.
  • Тип — ссылочный: списки, словари, пользовательские классы.
  • Нужны разные значения для разных экземпляров: например, идентификатор заказа.
  • Вы пишете библиотеку и не хотите, чтобы изменение константы сломало клиентов без перекомпиляции.
// Хороший пример для readonly
public class Order
{
    public readonly Guid OrderId;
    public readonly DateTime CreatedAt;
    public readonly List<OrderItem> Items;
    
    public Order()
    {
        OrderId = Guid.NewGuid();
        CreatedAt = DateTime.UtcNow;
        Items = new List<OrderItem>();
    }
}

Подводные камни и частые ошибки

1. Проблема с встраиванием const

Представьте: у вас есть библиотека MyLib.dll с константой public const int Version = 1;. Вы компилируете приложение App.exe, которое использует эту константу. Компилятор встраивает значение 1 в App.exe. Если вы обновите MyLib.dll до версии 2, но не перекомпилируете App.exe, он всё равно будет использовать 1. Баг гарантирован.

Решение: Для публичных API используйте static readonly вместо const.

2. Readonly и изменяемые объекты

Помните: readonly

Блоги

Рекомендаций по книгам