Подтвердить что ты не робот

Почему С# ограничивает набор типов, которые могут быть объявлены как const?

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

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

const string Foo = "foo";

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

4b9b3361

Ответ 1

Из спецификация С#, глава 10.4 - Константы:
(10.4 в спецификации С# 3.0, 10.3 в онлайн-версии для 2.0)

Константа - это член класса, который представляет собой постоянное значение: значение, которое может быть вычислено во время компиляции.

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

IntPtr, как пример, хотя и является типом значения, по-прежнему является структурой, а не одним из встроенных литералов. Таким образом, любое выражение, использующее IntPtr, должно будет вызвать код в структуре IntPtr, и это то, что не является законным для объявления константы.

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

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

Таким образом, вы имеете следующий эффект:

  • Ссылка на исходное имя константы, класс, в котором она была объявлена, или пространство имен не скомпилировано в код в этом месте
  • Если вы декомпилируете код, в нем будут магические числа, просто потому, что исходная "ссылка" на константу, как упоминалось выше, отсутствует, только значение константы
  • Компилятор может использовать это для оптимизации или даже удаления ненужного кода. Например, if (SomeClass.Version == 1), когда SomeClass.Version имеет значение 1, фактически удалит if-statement и сохранит исполняемый блок кода. Если значение константы не равно 1, то весь оператор if и его блок будут удалены.
  • Поскольку значение константы компилируется в код, а не ссылка на константу, использование констант из других сборок не будет автоматически обновлять скомпилированный код каким-либо образом, если значение константы должно измениться (что должно нет!)

Другими словами, со следующим сценарием:

  • Ассемблер A содержит константу с именем "Версия", имеющую значение 1
  • В сборке B содержится выражение, которое анализирует номер версии сборки A из этой константы и сравнивает ее с 1, чтобы убедиться, что она может работать с сборкой
  • Кто-то модифицирует сборку A, увеличивая значение константы до 2 и перестраивая A (но не B)

В этом случае сборка B в ее скомпилированной форме будет по-прежнему сравнивать значение 1 с 1, потому что когда B был скомпилирован, константа имела значение 1.

Фактически, если это единственное использование чего-либо из сборки A в сборке B, сборка B будет скомпилирована без зависимости от сборки A. Выполнение кода, содержащего это выражение в сборке B, не будет загружать сборку A.

Таким образом, константы должны использоваться только для вещей, которые никогда не изменятся. Если это значение, которое может или изменится некоторое время в будущем, и вы не можете гарантировать, что все остальные сборки будут восстановлены одновременно, поле readonly более подходит, чем константа.

Итак, это нормально:

  • public const Int32 NumberOfDaysInAWeekInGregorianCalendar = 7;
  • public const Int32 NumberOfHoursInADayOnEarth = 24;

пока это не так:

  • public const Int32 AgeOfProgrammer = 25;
  • public const Строка NameOfLastProgrammerThatModifiedAssembly = "Joe Programmer";

Редактировать 27 мая 2016 года

Хорошо, только что получил upvote, поэтому я перечитаю свой ответ здесь, и это на самом деле немного неправильно.

Теперь, цель спецификации языка С# - это все, что я написал выше. Вы не должны использовать то, что не может быть представлено буквальным как 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

Правильно, a const decimal на самом деле a readonly decimal.

Реальная сделка заключается в том, что компилятор будет использовать этот DecimalConstantAttribute для работы своей магией.

Теперь это единственная такая магия, которую я знаю с помощью компилятора С#, но я подумал, что стоит упомянуть.

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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

string1.Equals(string2)

Ответ 5

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

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

Ответ 6

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