一尘不染

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

c#

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

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

const string Foo = "foo";

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


阅读 372

收藏
2020-05-19

共1个答案

一尘不染

根据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以其编译形式仍将值1与1进行比较,因为在编译程序集B时,常量的值为1。

实际上,如果仅使用程序集B中程序集A的所有内容,则将在不依赖程序集A的情况下编译程序集B。在程序集B中执行包含该表达式的代码不会加载程序集A。

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

这样就可以了:

  • 公共常量Int32 NumberOfDaysInAWeekInGregorianCalendar = 7;
  • 公共常量Int32 NumberOfHoursInADayOnEarth = 24;

虽然不是:

  • 公共常量Int32 AgeOfProgrammer = 25;
  • public const String 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

是的,a const decimal实际上是readonly decimal

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

现在,这是我在C#编译器中唯一了解的魔术,但我认为值得一提。

2020-05-19