关于常量:为什么C#限制可以声明为const的类型集?

Why does C# limit the set of types that can be declared as const?

编译器错误CS0283表示只有基本的pod类型(以及字符串、枚举和空引用)才能声明为const。有人对这种限制的理由有理论吗?例如,能够声明其他类型(如intptr)的常量值就更好了。

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

1
const string Foo ="foo";

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


根据C规范,第10.4章-常数:(C 3.0规范中的10.4,2.0的在线版本中的10.3)好的。

A constant is a class member that represents a constant value: a value that can be computed at compile time.

Ok.

这基本上意味着您只能使用仅由文本组成的表达式。不能使用对任何方法、构造函数(不能表示为纯IL文本)的任何调用,因为编译器无法在编译时执行该操作,因此无法计算结果。此外,由于无法将方法标记为不变量(即,在输入和输出之间有一对一的映射),编译器执行此操作的唯一方法是分析IL以查看它是否依赖于输入参数以外的东西,特殊情况下处理某些类型(如intptr),或者只是不允许对任何代码的每个调用。好的。

作为一个例子,虽然intptr是一个值类型,但它仍然是一个结构,而不是一个内置的文本。因此,任何使用intptr的表达式都需要调用intptr结构中的代码,这对于常量声明来说是不合法的。好的。

我能想到的唯一合法的常量值类型示例是一个通过声明它来初始化为零的类型,这几乎没用。好的。

至于编译器如何处理/使用常量,它将使用计算值代替代码中的常量名。好的。

因此,您具有以下效果:好的。

  • 没有对原始常量名、在其中声明的类或命名空间的引用编译到此位置的代码中。
  • 如果您对代码进行反编译,代码中会有一些神奇的数字,这仅仅是因为对常量的原始"引用",如上所述,不存在,只有常量的值
  • 编译器可以使用它来优化甚至删除不必要的代码。例如,当someclass.version的值为1时,if (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:每周日历日数=7;
    • 公共常数Int32小时数IndayOnearth=24;

    虽然这不是:好的。

    • public const int32 ageofprogrammer=25;
    • public const string name of lastprogrammer that modifiedassembly="joe programmer";

    编辑日期:2016年5月27日好的。

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

    现在,C语言规范的意图就是我上面写的所有内容。你不应该使用不能用文字表示为const的东西。好的。

    但是你能吗?嗯,是的…好的。

    让我们来看看decimal类型。好的。

    1
    2
    3
    4
    public class Test
    {
        public const decimal Value = 10.123M;
    }

    让我们看看用ildasm看这个类的实际情况:好的。

    1
    2
    .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 )

    我来给你分一下:好的。

    1
    .field public static initonly

    对应于:好的。

    1
    public static readonly

    没错,一个const decimal实际上是一个readonly decimal。好的。

    真正的问题是编译器将使用该DecimalConstantAttribute来发挥其魔力。好的。

    现在,这是我所知道的C编译器唯一的魔力,但我认为值得一提。好的。好啊。


    Does anyone have a theory on the rationale for this limitation?

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


    I believe that the concept of const is actually syntactic sugar in C#, and that it just replaces any uses of the name with the literal value

    编译器如何处理其他语言中的常量对象?

    对于运行时要计算的可变类型,可以使用readonly。有关差异,请参阅本文。


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


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

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


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

    1
    2
    3
    4
    5
    const string myName ="Bruce Wayne";
    if (someVar == myName)
    {
       ...
    }

    实际上被视为

    1
    2
    3
    4
    if (someVar =="Bruce Wayne")
    {
       ...
    }

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

    1
    string1.Equals(string2)