编译错误 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 在其编译形式下仍会将 1 与 1 的值进行比较,因为编译 B 时,常量的值为 1。

事实上,如果这是程序集 A 中的任何内容在程序集 B 中的唯一用法,则程序集 B 将在不依赖于程序集 A 的情况下进行编译。执行程序集 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

没错,一个 const decimal 实际上是一个 readonly decimal.

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

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

其他提示

  

没有人有此限制上的理由理论?

如果它允许只是一个理论,我的理论是,原始类型的常量的值可以在MSIL字面操作码参数来表达......但是其他非基本类型的值不能,因为MSIL没有按”吨具有语法来表达一个用户定义的类型的值作为一个字面。

  

相信常量的概念实际上是在C#语法糖,而且它只是替换名称的任何用途与文字值

什么是编译器与其他语言的const对象呢?

您可以只读使用可变类型的我在运行时进行评估。请参阅的差异这篇文章

因为编译器与所述MSIL文字值替换变量

consts限于数字和字符串在C#。换句话说,当你写:

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

实际上视为

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

和是,C#编译器是足够聪明来治疗相等运算符(==)上的字符串作为

string1.Equals(string2)

在我看来,只有值类型可以被表达为一个常数(与弦线的异常,该异常值和对象类型之间的某处站立)。

这对我来说是OK:对象(引用)必须在堆上分配,但常数不是在所有分配的(因为他们是在编译时更换)

总之,所有简单类型,枚举和字符串是不可变的,但是一个结构例如是没有的。你可以有可变状态的结构(字段,属性,甚至引用引用类型)。所以编译器不能确保(在编译时),该结构体变量的内部状态不能改变。因此,编译器需要确定一个类型是由定义在不可变的常量表达式被使用。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top