为什么C#限制可以声明为const的类型集?

编译器错误CS0283表示只能将基本POD类型(以及字符串,枚举和空引用)声明为const 。 有没有人有关于这种限制的理由的理论? 例如,能够声明其他类型的const值(例如IntPtr)会很好。

我相信const的概念实际上是C#中的语法糖,它只是用文字值替换了名称的任何用法。 例如,给定以下声明,在编译时对Foo的任何引用都将替换为“foo”。

 const string Foo = "foo"; 

这将排除任何可变类型,因此他们可能选择此限制而不必在编译时确定给定类型是否可变?

从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语句及其块。
  • 由于常量的值被编译到代码中,而不是对常量的引用,因此如果常量的值应该改变,那么使用来自其他程序集的常量将不会以任何方式自动更新已编译的代码(它不应该!)

换句话说,使用以下方案:

  1. 程序集A包含一个名为“Version”的常量,其值为1
  2. 程序集B,包含一个表达式,该表达式从该常量分析程序集A的版本号并将其与1进行比较,以确保它可以与程序集一起使用
  3. 有人修改程序集A,将常量的值增加到2,并重建A(但不是B)

在这种情况下,程序集B在其编译forms中仍将比较1到1的值,因为在编译B时,常量的值为1。

实际上,如果这是程序集B中程序集A中任何内容的唯一用法,则程序集B将在不依赖于程序集A的情况下进行编译。在程序集B中执行包含该表达式的代码将不会加载程序集A.

因此,常量只应用于永不改变的事物。 如果它是一个可能或将来会改变某个时间的值,并且您无法保证所有其他程序集同时重建,则只读字段比常量更合适。

所以这没关系:

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

虽然这不是:

  • public const Int32 AgeOfProgrammer = 25;
  • public const String NameOfLastProgrammerThatModifiedAssembly =“Joe Programmer”;

编辑2016年5月27日

好的,刚刚得到一个upvote,所以我在这里重新阅读我的答案,这实际上有点错误。

现在,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#编译器知道的唯一这样的魔法,但我认为值得一提。

有没有人有关于这种限制的理由的理论?

如果允许它只是一个理论,我的理论是原始类型的const值可以用MSIL中的文字操作码参数表示……但是其他非原始类型的值不能,因为MSIL没有语法将用户定义类型的值表示为文字。

我相信const的概念实际上是C#中的语法糖,它只是用文字值替换了名称的任何用法

编译器在其他语言中对const对象做了什么?

您可以将readonly用于我在运行时评估的可变类型。 有关差异,请参阅此文章 。

consts仅限于C#中的数字和字符串,因为编译器将该变量替换为MSIL中的文字值。 换句话说,当你写:

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

实际上被视为

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

是的,C#编译器非常聪明,可以将字符串上的相等运算符(==)视为

 string1.Equals(string2) 

在我看来,只有值类型可以表示为一个常量(除了字符串,它位于值和对象类型之间)。

对我来说没问题:必须在堆上分配对象(引用),但根本不分配常量(因为它们在编译时被替换)。

简而言之,所有简单类型,枚举和字符串都是不可变的,但例如Struct不是。 您可以拥有一个具有可变状态的Struct(字段,属性,甚至是对引用类型的引用)。 因此编译器无法确保(在编译时)不能更改Struct变量的内部状态。 因此编译器需要确保类型在定义中是不可变的,以便在常量表达式中使用。