Avatar ·

В чем разница между const и readonly в C#?

Всем привет! Я новичок в C# и постоянно путаюсь между ключевыми словами `const` и `readonly`. Вроде бы оба делают значение неизменяемым, но я чувствую, что это не совсем так. У меня есть такой код: ```csharp public class MyClass { public const int MaxAttempts = 5; public readonly string ConnectionString = "localhost"; public void Test() { // MaxAttempts = 10; // Ошибка - const нельзя изменить // ConnectionString = "new"; // Ошибка - readonly нельзя изменить } } ``` Оба поля нельзя изменить после инициализации. Но в чем тогда разница? Я слышал, что `const` работает на этапе компиляции, а `readonly` — на этапе выполнения. Что это значит на практике? Когда мне использовать `const`, а когда `readonly`? И почему я могу инициализировать `readonly` в конструкторе, а `const` — нет? Объясните, пожалуйста, на простых примерах. Заранее спасибо! <<>>

Ключевое различие: время вычисления и область применения

Главное отличие между const и readonly в C# — это когда значение фиксируется и как оно обрабатывается компилятором.

const — константа времени компиляции

const означает, что значение должно быть известно на этапе компиляции. Оно подставляется непосредственно в код (как литерал).

public class Calculator
{
    public const double Pi = 3.14159;
    public const int DaysInWeek = 7;
    // public const DateTime Now = DateTime.Now; // Ошибка! Нельзя использовать неконстантное выражение
}

Ограничения const:

  • Можно использовать только с примитивными типами (int, double, bool, string, enum).
  • Значение должно быть вычислено на этапе компиляции.
  • Нельзя инициализировать в конструкторе — только при объявлении.
  • По умолчанию static (нельзя использовать static const — это избыточно).

readonly — константа времени выполнения

readonly фиксирует значение один раз во время выполнения — либо при объявлении, либо в конструкторе класса.

public class DatabaseConfig
{
    public readonly string ConnectionString;
    public readonly int MaxRetries;
    
    public DatabaseConfig(string connectionString, int maxRetries)
    {
        ConnectionString = connectionString; // Можно задать в конструкторе
        MaxRetries = maxRetries;
    }
    
    // public void Update() { ConnectionString = "new"; } // Ошибка! Нельзя изменить после конструктора
}

Особенности readonly:

  • Работает с любыми типами данных (включая ссылочные).
  • Может быть инициализирован в конструкторе (это дает гибкость).
  • Может быть статическим или экземплярным.
  • Не обязан быть константой компиляции — можно использовать DateTime.Now, результат метода и т.д.

Практические примеры

Рассмотрим сценарий, где разница становится критичной:

// Библиотека ClassLibrary.dll
public class Constants
{
    public const int Version = 1;
    public static readonly int BuildNumber = 100;
}

// Приложение, использующее библиотеку
Console.WriteLine(Constants.Version);      // Всегда 1, даже если библиотека обновлена
Console.WriteLine(Constants.BuildNumber);  // Обновится, если перекомпилировать библиотеку

Если вы измените const в библиотеке и не перекомпилируете приложение, приложение продолжит использовать старое значение (потому что оно было "вклеено" в IL-код). readonly же читается из сборки каждый раз.

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

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

Важное замечание про ссылочные типы

readonly защищает только ссылку, но не содержимое объекта:

public class Config
{
    public readonly List<string> AllowedUsers = new List<string>();
}

// Можно делать:
config.AllowedUsers.Add("Admin"); // OK! Ссылка не меняется
// Нельзя:
config.AllowedUsers = new List<string>(); // Ошибка! Нельзя переназначить

Итог: const — это жесткая подстановка на этапе компиляции, readonly — защита от записи после инициализации в рантайме.

<<>>

Дополнение: производительность и метаданные

Коллега выше отлично объяснил основную разницу. Я добавлю несколько технических нюансов, которые помогут глубже понять тему.

Производительность

const немного быстрее, потому что значение подставляется непосредственно в код на этапе компиляции — нет необходимости в чтении из памяти. Однако разница обычно микроскопическая и не стоит того, чтобы жертвовать гибкостью.

Атрибуты и метаданные

const не создает поле в классе — оно существует только в метаданных как константа. readonly же создает полноценное поле, которое можно увидеть через рефлексию:

public class Test
{
    public const int A = 1;
    public readonly int B = 2;
}

// Через рефлексию:
var fields = typeof(Test).GetFields();
// fields содержит B, но НЕ содержит A!

Инициализация в конструкторе

Это ключевое отличие: readonly можно задать в конструкторе, что позволяет создавать разные экземпляры с разными "константами":

public class User
{
    public readonly DateTime CreatedAt;
    public User() => CreatedAt = DateTime.UtcNow; // Уникальное время для каждого объекта
}

С const такое невозможно — значение будет одинаковым для всех экземпляров и всех сборок.

Практический совет

Если сомневаетесь — используйте static readonly. Это безопаснее и гибче, чем const. const стоит применять только для действительно неизменных величин, определенных в стандартах (например, Math.PI).