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

时间:2009-01-14 00:13:01

标签: c# constants compiler-errors

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

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

const string Foo = "foo";

这会排除任何可变类型,所以他们可能选择了这个限制而不必在编译时确定给定类型是否可变?

6 个答案:

答案 0 :(得分:29)

来自C# specification, chapter 10.4 - Constants
(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)
  4. 在这种情况下,程序集B在其编译形式中仍将比较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#编译器知道的唯一这样的魔法,但我认为值得一提。

答案 1 :(得分:1)

  

我认为const的概念实际上是C#中的语法糖,它只是用文字值取代了名称的任何用法

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

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

答案 2 :(得分:1)

  

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

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

答案 3 :(得分:0)

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

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

实际上被视为

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

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

string1.Equals(string2)

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

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