来自C# 规范,第 10.4 章 - 常量:
(C# 3.0 规范中的 10.4,2.0 在线版本中的 10.3)
常量是表示常量值的类成员:可以在编译时计算的值。
这基本上是说您只能使用仅由文字组成的表达式。不能使用对任何方法、构造函数(不能表示为纯 IL 文字)的任何调用,因为编译器无法在编译时执行该执行,从而计算结果。此外,由于无法将方法标记为不变(即输入和输出之间存在一对一映射),编译器执行此操作的唯一方法是分析 IL 以查看是否它取决于输入参数、特殊情况处理某些类型(如 IntPtr)或只是禁止对任何代码的每次调用。
例如,IntPtr 虽然是一种值类型,但仍然是一种结构,而不是内置文字之一。因此,任何使用 IntPtr 的表达式都需要调用 IntPtr 结构中的代码,这对于常量声明是不合法的。
我能想到的唯一合法的常量值类型示例是仅通过声明它就用零初始化的示例,这几乎没有用。
至于编译器如何处理/使用常量,它将使用计算值代替代码中的常量名称。
因此,您有以下效果:
- 没有对原始常量名称、声明它的类或命名空间的引用被编译到此位置的代码中
- 如果你反编译代码,它会在其中包含幻数,只是因为如上所述,对常量的原始“引用”不存在,只有常量的值
- 编译器可以使用它来优化甚至删除不必要的代码。例如
if (SomeClass.Version == 1)
,当 SomeClass.Version 的值为 1 时,实际上会删除 if 语句,并保留正在执行的代码块。如果常量的值不是 1,则整个 if 语句及其块将被删除。
- 由于常量的值被编译到代码中,而不是对常量的引用,因此如果常量的值发生变化(它不应该发生变化!),使用来自其他程序集的常量将不会以任何方式自动更新编译的代码
换句话说,在以下情况下:
- 程序集 A,包含一个名为“Version”的常量,值为 1
- 程序集 B,包含一个表达式,该表达式从该常量分析程序集 A 的版本号并将其与 1 进行比较,以确保它可以与程序集一起使用
- 有人修改程序集 A,将常量的值增加到 2,并重建 A(但不是 B)
在这种情况下,程序集 B 的编译形式仍会将 1 的值与 1 进行比较,因为在编译 B 时,常量的值是 1。
事实上,如果这是在程序集 B 中唯一使用程序集 A 的任何内容,则程序集 B 将在不依赖程序集 A 的情况下编译。在程序集 B 中执行包含该表达式的代码将不会加载程序集 A。
因此,常量应该只用于永远不会改变的事物。如果它是一个可能或将在未来某个时间更改的值,并且您不能保证同时重建所有其他程序集,则只读字段比常量更合适。
所以这没关系:
- 公共常量 Int32 NumberOfDaysInAWeekInGregorianCalendar = 7;
- 公共常量 Int32 NumberOfHoursInADayOnEarth = 24;
虽然这不是:
- 公共常量 Int32 AgeOfProgrammer = 25;
- 公共常量字符串 NameOfLastProgrammerThatModifiedAssembly = "乔程序员";
编辑 2016 年 5 月 27 日
好的,刚刚获得了赞成票,所以我在这里重新阅读了我的答案,这实际上有点错误。
现在,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
没错, aconst decimal
实际上是 a readonly decimal
。
这里真正的事情是编译器将使用它DecimalConstantAttribute
来发挥它的魔力。
现在,这是我所知道的唯一一个使用 C# 编译器的魔法,但我认为值得一提。