Почему C# ограничивает набор типов, которые могут быть объявлены как const?
Ошибка компилятора CS0283 указывает на то, что только базовые типы POD (а также строки, перечисления и нулевые ссылки) могут быть объявлены как const
, У кого-нибудь есть теория об обосновании этого ограничения? Например, было бы неплохо иметь возможность объявлять постоянные значения других типов, таких как IntPtr.
Я считаю, что концепция const
на самом деле является синтаксическим сахаром в C# и просто заменяет любое использование имени буквальным значением. Например, учитывая следующее объявление, любая ссылка на Foo будет заменена на "foo" во время компиляции.
const string Foo = "foo";
Это исключило бы любые изменяемые типы, поэтому, возможно, они выбрали это ограничение вместо того, чтобы определять во время компиляции, является ли данный тип изменяемым?
6 ответов
Из спецификации C#, глава 10.4 - Константы:
(10.4 в спецификации C# 3.0, 10.3 в онлайн-версии для 2.0)
Константа - это член класса, представляющий постоянное значение: значение, которое можно вычислить во время компиляции.
В основном это говорит о том, что вы можете использовать только выражения, которые состоят исключительно из литералов. Любые вызовы любых методов, конструкторов (которые не могут быть представлены как чистые литералы IL) не могут быть использованы, так как компилятор не может выполнить это выполнение и, таким образом, вычислить результаты во время компиляции. Кроме того, поскольку нет способа пометить метод как инвариантный (т. Е. Существует взаимно-однозначное сопоставление между входом и выходом), единственный способ для компилятора сделать это - проанализировать IL, чтобы увидеть, это зависит от других вещей, кроме входных параметров, особого случая, обрабатывающего некоторые типы (например, IntPtr), или просто запрещающего каждый вызов любого кода.
Например, IntPtr, хотя и является типом значения, по-прежнему является структурой, а не одним из встроенных литералов. Таким образом, любое выражение, использующее IntPtr, должно вызывать код в структуре IntPtr, и это является недопустимым для объявления констант.
Единственный допустимый пример типа значения константы, о котором я могу подумать, это тот, который инициализируется нулями, просто объявляя его, и это вряд ли полезно.
Что касается того, как компилятор обрабатывает / использует константы, он будет использовать вычисленное значение вместо имени константы в коде.
Таким образом, вы получаете следующий эффект:
- Никакая ссылка на исходное имя константы, класс, в котором она была объявлена, или пространство имен, не скомпилировано в код в этом месте
- Если вы декомпилируете код, он будет содержать магические числа просто потому, что первоначальная "ссылка" на константу, как упоминалось выше, отсутствует, только значение константы
- Компилятор может использовать это для оптимизации или даже удаления ненужного кода. Например,
if (SomeClass.Version == 1)
когда SomeClass.Version имеет значение 1, фактически удалит оператор if и сохранит выполняемый блок кода. Если значение константы не равно 1, весь оператор if и его блок будут удалены. - Поскольку значение константы компилируется в код, а не является ссылкой на константу, использование констант из других сборок не приведет к автоматическому обновлению скомпилированного кода в любом случае, если значение константы должно измениться (чего не должно быть!)
Другими словами, со следующим сценарием:
- Сборка А, содержит константу с именем "Версия", имеющую значение 1
- Сборка B содержит выражение, которое анализирует номер версии сборки A по этой константе и сравнивает ее с 1, чтобы убедиться, что она может работать со сборкой.
- Кто-то изменяет сборку A, увеличивая значение константы до 2, и восстанавливает A (но не B)
В этом случае сборка B в скомпилированном виде все равно будет сравнивать значение от 1 до 1, поскольку при компиляции B константа имела значение 1.
Фактически, если это единственное использование чего-либо из сборки A в сборке B, сборка B будет скомпилирована без зависимости от сборки A. Выполнение кода, содержащего это выражение в сборке B, не будет загружать сборку A.
Таким образом, константы должны использоваться только для вещей, которые никогда не изменятся. Если это значение может измениться или когда-нибудь изменится в будущем, и вы не можете гарантировать, что все другие сборки будут перестроены одновременно, поле только для чтения более подходит, чем константа.
Так что это нормально:
- public const Int32 NumberOfDaysInAWeekInGregorianCalendar = 7;
- public const Int32 NumberOfHoursInADayOnEarth = 24;
пока это не
- public const Int32 AgeOfProgrammer = 25;
- public const String NameOfLastProgrammerThatModifiedAssembly = "Джо Программист";
Редактировать 27 мая 2016
Хорошо, только что получил голосование, поэтому я перечитал свой ответ здесь, и это на самом деле немного неправильно.
Теперь намерение спецификации языка C# - это все, что я написал выше. Вы не должны использовать то, что не может быть представлено литералом как const
,
А ты можешь? Ну да....
Давайте посмотрим на decimal
тип.
public class Test
{
public const decimal Value = 10.123M;
}
Давайте посмотрим, как на самом деле выглядит этот класс, если взглянуть на него с помощью ildasm:
.field public static initonly valuetype [mscorlib]System.Decimal X
.custom instance void [mscorlib]System.Runtime.CompilerServices.DecimalConstantAttribute::.ctor(int8, uint8, uint32, uint32, uint32) = ( 01 00 01 00 00 00 00 00 00 00 00 00 64 00 00 00 00 00 )
Позвольте мне разобрать это для вас:
.field public static initonly
соответствует:
public static readonly
Это верно, const decimal
на самом деле readonly decimal
,
Здесь дело в том, что компилятор будет использовать это DecimalConstantAttribute
творить свою магию.
Теперь, это единственная магия, которую я знаю с компилятором C#, но я подумал, что стоит упомянуть.
У кого-нибудь есть теория об обосновании этого ограничения?
Если это разрешено быть просто теорией, моя теория состоит в том, что значения констант примитивных типов могут быть выражены в параметрах буквального кода операции в MSIL ... но значения других, не примитивных типов не могут, потому что MSIL не имеет синтаксис для выражения значения пользовательского типа в виде литерала.
Я считаю, что понятие const на самом деле является синтаксическим сахаром в C#, и оно просто заменяет любое использование имени на буквальное значение
Что делает компилятор с const-объектами на других языках?
Вы можете использовать readonly для изменчивых типов, которые могут быть оценены во время выполнения. Смотрите эту статью для различий.
Короче говоря, все простые типы, перечисления и строки являются неизменяемыми, но, например, Struct - нет. Вы можете иметь структуру с изменяемым состоянием (поля, свойства, даже ссылки на ссылочные типы). Поэтому компилятор не может убедиться (во время компиляции), что внутреннее состояние переменной Struct не может быть изменено. Поэтому компилятор должен быть уверен, что тип по определению неизменен для использования в константном выражении.
Мне кажется, что только типы значений могут быть выражены как константа (за исключением строк, которые находятся где-то между значением и типом объекта).
Это нормально для меня: объекты (ссылки) должны быть размещены в куче, но константы не выделяются вообще (так как они заменяются во время компиляции).
Констант ограничен числами и строками в C#, потому что компилятор заменяет переменную литеральным значением в MSIL. Другими словами, когда вы пишете:
const string myName = "Bruce Wayne";
if (someVar == myName)
{
...
}
на самом деле рассматривается как
if (someVar == "Bruce Wayne")
{
...
}
и да, компилятор C# достаточно умен, чтобы рассматривать оператор равенства (==) в строках как
string1.Equals(string2)