34

编译器错误CS0283表明只有基本的 POD 类型(以及字符串、枚举和空引用)可以声明为const. 有人对这种限制的基本原理有理论吗?例如,如果能够声明其他类型的 const 值,例如 IntPtr,那就太好了。

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

const string Foo = "foo";

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

4

6 回答 6

33

来自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 的任何内容,则程序集 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# 编译器的魔法,但我认为值得一提。

于 2009-01-14T00:41:26.663 回答
1

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

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

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

于 2009-01-14T00:16:29.880 回答
1

有人对这种限制的基本原理有理论吗?

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

于 2009-01-14T00:26:39.673 回答
0

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

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

于 2009-01-14T00:44:36.157 回答
0

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

于 2017-05-02T12:50:48.300 回答
-1

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

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

实际上被视为

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

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

string1.Equals(string2)
于 2009-01-14T00:23:19.977 回答