为什么 C# 限制可以声明为 const 的类型集? [英] Why does C# limit the set of types that can be declared as const?

查看:27
本文介绍了为什么 C# 限制可以声明为 const 的类型集?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Compiler error CS0283 indicates that only the basic POD types (as well as strings, enums, and null references) can be declared as const. Does anyone have a theory on the rationale for this limitation? For instance, it would be nice to be able to declare const values of other types, such as IntPtr.

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

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. For instance, given the following declaration, any reference to Foo would be replaced with "foo" at compile time.

const string Foo = "foo";

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

This would rule out any mutable types, so maybe they chose this limitation rather than having to determine at compile time whether a given type is mutable?

推荐答案

来自 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.

这基本上是说您只能使用仅由文字组成的表达式.不能使用对任何方法、构造函数(不能表示为纯 IL 文字)的任何调用,因为编译器无法在编译时执行该执行并因此计算结果.此外,由于无法将方法标记为不变(即输入和输出之间存在一对一映射),编译器执行此操作的唯一方法是分析 IL 以查看是否它取决于输入参数以外的东西,特殊情况处理某些类型(如 IntPtr),或者只是禁止对任何代码的每次调用.

This basically says that you can only use expressions that consists solely of literals. Any calls to any methods, constructors (that cannot be represented as pure IL literals) cannot be used, as there is no way for the compiler to do that execution, and thus compute the results, at compile time. Also, since there is no way to tag a method as invariant (ie. there is a one-to-one mapping between input and output), the only way for the compiler to do this would be to either analyze the IL to see if it depends on things other than the input parameters, special-case handle some types (like IntPtr), or just disallow every call to any code.

例如,IntPtr 虽然是值类型,但仍然是一种结构,而不是内置文字之一.因此,任何使用 IntPtr 的表达式都需要调用 IntPtr 结构中的代码,这对于常量声明是不合法的.

IntPtr, as an example, though being a value type, is still a structure, and not one of the built-in literals. As such, any expression using an IntPtr will need to call code in the IntPtr structure, and this is what is not legal for a constant declaration.

我能想到的唯一合法的常量值类型示例是通过声明用零初始化的示例,这几乎没有用.

The only legal constant value type example I can think of would be one that is initialized with zeroes by just declaring it, and that's hardly useful.

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

As for how the compiler treats/uses constants, it will use the computed value in place of the constant name in the code.

因此,您具有以下效果:

Thus, you have the following effect:

  • 没有对原始常量名称、声明它的类或命名空间的引用被编译到此位置的代码中
  • 如果你反编译代码,它就会有幻数,因为如上所述,对常量的原始引用"不存在,只有常量的值
  • 编译器可以使用它来优化甚至删除不必要的代码.例如,if (SomeClass.Version == 1),当 SomeClass.Version 的值为 1 时,实际上会移除 if 语句,并保留正在执行的代码块.如果常量的值不是 1,则整个 if 语句及其块将被删除.
  • 由于常量的值被编译到代码中,而不是对常量的引用,如果常量的值发生变化(它应该不是!)
  • No reference to the original constant name, class it was declared in, or namespace, is compiled into the code in this location
  • If you decompile the code, it will have magic numbers in it, simply because the original "reference" to the constant is, as mentioned above, not present, only the value of the constant
  • The compiler can use this to optimize, or even remove, unnecessary code. For instance, if (SomeClass.Version == 1), when SomeClass.Version has the value of 1, will in fact remove the if-statement, and keep the block of code being executed. If the value of the constant is not 1, then the whole if-statement and its block will be removed.
  • Since the value of a constant is compiled into the code, and not a reference to the constant, using constants from other assemblies will not automagically update the compiled code in any way if the value of the constant should change (which it should not!)

换句话说,在以下场景中:

In other words, with the following scenario:

  1. 程序集 A,包含一个名为Version"的常量,值为 1
  2. 程序集 B,包含一个表达式,该表达式从该常量分析程序集 A 的版本号并将其与 1 进行比较,以确保它可以与程序集一起使用
  3. 有人修改了程序集 A,将常量的值增加到 2,然后重建了 A(但不是 B)

在这种情况下,程序集 B 在其编译形式中仍会将值 1 与 1 进行比较,因为在编译 B 时,常量的值为 1.

In this case, assembly B, in its compiled form, will still compare the value of 1 to 1, because when B was compiled, the constant had the value 1.

事实上,如果这是程序集 B 中程序集 A 中任何内容的唯一用法,程序集 B 将在不依赖于程序集 A 的情况下进行编译.在程序集 B 中执行包含该表达式的代码不会加载程序集 A.

In fact, if that is the only usage of anything from assembly A in assembly B, assembly B will be compiled without a dependency on assembly A. Executing the code containing that expression in assembly B will not load assembly A.

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

Constants should thus only be used for things that will never change. If it is a value that might or will change some time in the future, and you cannot guarantee that all other assemblies are rebuilt simultaneously, a readonly field is more appropriate than a constant.

所以没关系:

  • public const Int32 NumberOfDaysInAWeekInGregorianCalendar = 7;
  • public const Int32 NumberOfHoursInADayOnEarth = 24;

虽然这不是:

  • public const Int32 AgeOfProgrammer = 25;
  • public const String NameOfLastProgrammerThatModifiedAssembly = "Joe Programmer";

编辑 2016 年 5 月 27 日

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

OK, just got an upvote, so I re-read my answer here and this is actually slightly wrong.

现在,C# 语言规范的意图就是我上面写的一切.您不应该使用不能用文字表示为 const 的东西.

Now, the intention of the C# language specification is everything I wrote above. You're not supposed to use something that cannot be represented with a literal as a const.

但是你可以吗?嗯,是的....

But can you? Well, yes....

我们来看看decimal 类型.

public class Test
{
    public const decimal Value = 10.123M;
}

让我们看看这个类在使用 ildasm 时真正是什么样子:

Let's look at what this class looks like really when looked at with 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实际上是一个只读十进制.

That's right, a const decimal is actually a readonly decimal.

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

The real deal here is that the compiler will use that DecimalConstantAttribute to work its magic.

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

Now, this is the only such magic I know of with the C# compiler but I thought it was worth mentioning.

这篇关于为什么 C# 限制可以声明为 const 的类型集?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆