Ответ 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
для работы своей магией.
Теперь это единственная такая магия, которую я знаю с помощью компилятора С#, но я подумал, что стоит упомянуть.