Почему C # ограничивает набор типов, которые могут быть объявлены как const? - PullRequest
28 голосов
/ 14 января 2009

Ошибка компилятора CS0283 указывает, что только базовые типы POD (а также строки, перечисления и нулевые ссылки) могут быть объявлены как const. У кого-нибудь есть теория об обосновании этого ограничения? Например, было бы неплохо иметь возможность объявлять константные значения других типов, таких как IntPtr.

Я считаю, что концепция const на самом деле является синтаксическим сахаром в C # и просто заменяет любое использование имени буквальным значением. Например, учитывая следующее объявление, любая ссылка на Foo будет заменена на «foo» во время компиляции.

const string Foo = "foo";

Это исключило бы любые изменяемые типы, поэтому, возможно, они выбрали это ограничение вместо того, чтобы определять во время компиляции, является ли данный тип изменяемым?

Ответы [ 6 ]

29 голосов
/ 14 января 2009

Из спецификации 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. Сборка A, содержит константу с именем «Version», имеющую значение 1
  2. Сборка B, содержит выражение, которое анализирует номер версии сборки A по этой константе и сравнивает ее с 1, чтобы убедиться, что она может работать со сборкой
  3. Кто-то изменяет сборку 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 #, но я подумал, что стоит упомянуть.

1 голос
/ 14 января 2009

Есть ли у кого-нибудь теория об обосновании этого ограничения?

Если это разрешено быть просто теорией, моя теория состоит в том, что значения констант примитивных типов могут быть выражены в параметрах буквального кода операции в MSIL ... но значения других, не примитивных типов не могут, потому что MSIL не ' t имеет синтаксис для выражения значения пользовательского типа в виде литерала.

1 голос
/ 14 января 2009

Я считаю, что понятие const на самом деле является синтаксическим сахаром в C # и просто заменяет любое использование имени на буквальное значение

Что делает компилятор с const-объектами в других языках?

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

0 голосов
/ 02 мая 2017

Короче говоря, все простые типы, перечисления и строки являются неизменяемыми, но, например, Struct - нет. Вы можете иметь структуру с изменяемым состоянием (поля, свойства, даже ссылки на ссылочные типы). Поэтому компилятор не может убедиться (во время компиляции), что внутреннее состояние переменной Struct не может быть изменено. Поэтому компилятор должен быть уверен, что тип по определению неизменен для использования в константном выражении.

0 голосов
/ 14 января 2009

Мне кажется, что только типы значений могут быть выражены как константа (за исключением строк, которые находятся где-то между значением и типом объекта).

Это нормально для меня: объекты (ссылки) должны быть размещены в куче, но константы не выделяются вообще (так как они заменяются во время компиляции).

0 голосов
/ 14 января 2009

не ограничены числами и строками в C #, потому что компилятор заменяет переменную литеральным значением в MSIL Другими словами, когда вы пишете:

const string myName = "Bruce Wayne";
if (someVar == myName)
{
   ...
}

фактически рассматривается как

if (someVar == "Bruce Wayne")
{
   ...
}

и да, компилятор C # достаточно умен, чтобы рассматривать оператор равенства (==) для строк как

string1.Equals(string2)
...