Avatar ·

В чем разница между const и readonly в C#? Когда что использовать?

Всем привет! Я начал изучать C# и наткнулся на два модификатора, которые кажутся мне очень похожими: `const` и `readonly`. Я понимаю, что оба они делают переменную неизменяемой, но чувствую, что между ними должна быть какая-то фундаментальная разница, которую я упускаю. Вот простой пример, который я написал: ```csharp public class MyClass { public const int MyConst = 10; public readonly int MyReadonly = 20; public MyClass() { MyReadonly = 30; // Это работает // MyConst = 40; // Ошибка: константа не может быть изменена } public void SomeMethod() { // MyReadonly = 50; // Ошибка: поле readonly можно изменить только в конструкторе } } ``` Я вижу, что `readonly` поле можно изменить в конструкторе, а `const` — нет. Но есть ли другие отличия? Например, я слышал, что `const` — это статическая величина по своей природе, а `readonly` может быть разной для разных экземпляров. Как это работает на уровне компиляции? И когда какой модификатор лучше использовать в реальных проектах? Буду благодарен за подробное объяснение с примерами кода, чтобы я точно запомнил разницу. Спасибо! <<>>

Основные отличия между const и readonly

На первый взгляд const и readonly действительно похожи — оба делают переменную неизменяемой после инициализации. Однако они работают на совершенно разных уровнях и предназначены для разных сценариев. Давайте разберём ключевые различия.

1. Время инициализации

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

public class Constants
{
    // const — обязательно значение известно на этапе компиляции
    public const int DaysInWeek = 7;
    public const double Pi = 3.14159;
    // public const DateTime Now = DateTime.Now; // ОШИБКА! Нельзя
    
    // readonly — можно вычислить в конструкторе
    public readonly DateTime CreationTime;
    public readonly int RandomValue;
    
    public Constants()
    {
        CreationTime = DateTime.Now; // OK
        RandomValue = new Random().Next(100); // OK
    }
}

2. Статическая природа

const по своей сути является статическим. Вы не можете использовать модификатор static вместе с const — это приведёт к ошибке компиляции. readonly может быть как статическим, так и экземплярным.

public class Example
{
    // const всегда static, даже без ключевого слова
    public const int A = 1;  // Эквивалентно public static const int A = 1;
    
    // readonly может быть разным для разных экземпляров
    public readonly int InstanceValue;
    public static readonly int StaticValue = 100;
    
    public Example(int val)
    {
        InstanceValue = val; // Каждый объект может иметь своё значение
    }
}

3. Поведение при компиляции

Это самое важное различие. const встраивается прямо в код на этапе компиляции. Если вы используете константу из другой сборки, её значение копируется в вашу сборку. readonly — это ссылка на поле, которая разрешается во время выполнения.

// Библиотека Version1.dll
public class Library
{
    public const int Version = 1;
    public static readonly int ReadonlyVersion = 1;
}

// Ваше приложение
Console.WriteLine(Library.Version);          // Будет встроено как Console.WriteLine(1)
Console.WriteLine(Library.ReadonlyVersion);  // Будет обращение к полю

Если вы обновите библиотеку до версии 2, изменив Version = 2 и ReadonlyVersion = 2, то:

  • Library.Version в вашем приложении всё равно покажет 1, пока вы не перекомпилируете его.
  • Library.ReadonlyVersion покажет 2 сразу после замены DLL, без перекомпиляции.

4. Типы данных

const может быть только для примитивных типов: bool, int, double, string, enum и null для ссылочных типов. readonly может быть любого типа, включая пользовательские классы, массивы и коллекции.

public class TypesDemo
{
    // const — только примитивные типы
    public const string AppName = "MyApp";
    public const int MaxUsers = 100;
    // public const List<string> Names = new List<string>(); // ОШИБКА!
    
    // readonly — любые типы
    public readonly List<string> Names = new List<string>(); // OK
    public readonly StringBuilder Builder = new StringBuilder(); // OK
}

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

  • const — для значений, которые абсолютно неизменны и известны на этапе компиляции: математические константы (Pi, e), строковые литералы для путей, ключи, которые не будут меняться.
  • readonly — для значений, которые устанавливаются один раз, но могут зависеть от внешних данных: время создания объекта, настройки из конфигурации, значения, полученные из базы данных.
  • static readonly — для глобальных неизменяемых данных, которые могут измениться в новой версии библиотеки (чтобы не перекомпилировать клиентов).

Итоговая таблица

Характеристикаconstreadonly
Время вычисленияКомпиляцияВыполнение
СтатическийВсегдаОпционально
Изменение в конструктореНетДа
Поддерживаемые типыПримитивные + stringЛюбые
Встраивание в кодДаНет (ссылка)
<<>>

Дополнение: readonly для структур и массивов

Хочу добавить важный нюанс, который часто путает новичков. Модификатор readonly гарантирует, что ссылку на объект нельзя изменить, но сам объект может быть изменяемым. Это особенно заметно с массивами и коллекциями.

public class ReadonlyDemo
{
    public readonly int[] Numbers = { 1, 2, 3 };
    
    public void TryModify()
    {
        // Numbers = new int[] { 4, 5, 6 }; // ОШИБКА! Нельзя переназначить
        Numbers[0] = 100; // Это РАБОТАЕТ! Массив изменяется
    }
}

То же самое касается полей readonly структур. Если у вас структура с изменяемыми полями, readonly защищает только от замены всей структуры целиком, но не от изменения её внутренних полей. Поэтому для настоящей неизменяемости рекомендуется использовать readonly struct (доступно с C# 7.2) или неизменяемые коллекции (ImmutableArray, ImmutableList).

Ещё один совет: если вы используете const для публичных членов библиотеки, помните о проблеме встраивания. Для публичного API лучше использовать static readonly, чтобы клиенты автоматически получали новые значения при обновлении библиотеки, без необходимости перекомпиляции.