Effective-CSharp-2考虑用readonly代替const

C# 有两种常量,一种是编译期 (compile-time)的,另一种是运行期 (runtime)的。

1
2
3
4
5
// Compile-time constant:
public const int Millennium = 2000;

// Runtime constant:
public static readonly int ThisYear = 2004;

以上代码展示了如何在 class 或 struct 的范围内声明这两种常量 此外编译期常量还能在方法中声明,而 readyonly 常量则不行。

编译器的常量取值嵌入目标代码中。例如

1
if(myDateTime.Year == Millennium)

编译成 IL 之后,与直接使用字面量2000是一样的

1
if(myDateTime.Year == 2000)

运行期常量与之不同,如果代码中使用到了运行期常量,那么其生成的 IL 的、也会同样引用该变量,而不会直接使用字面量。

这两种变量支持的值也不一样。编译期的常量只能用来表示内置的 int, float, enum, string。非基本变量不能使用编译期常量声明,需要使用 readonly。在生成 IL 的过程中,只有用来表示这些原始类型的编译期常量才会替换成字面量。

无法编译,试图使用 new 操作符进行初始化: (Ryuu: 即使是参数是值类型也不行,其对象在编译期不存在)

1
2
3
// DON'T DO THIS!
// Does not compile ,use readonly instead:
private const DateTime classCreation = new DateTime(2000, 1, 1, 0, 0, 0);

编译期常量只能用数字,字符串或 null 初始化。readonly 常量在执行完构造函数 (constructor) 之后不可以再修改。(和编译期常量不同,他的值是在执行完构造函数后才初始化的)

在生成 IL 的时候,代码中的编译期常量会直接以其常量值写入,如果在制作另外的程序集 (assembly) 的时候用到了该程序集中的编译期常量,那么这个常量将会以字面值写入另外的程序集。

有的时候开发者确实想把某个值固定在编译期,比如程序版本记录,如果更新整个项目,那么里面每个版本号都会变为最新,如果仅更新其中某些程序集,那么只有更新的程序集的版本号会变为最新值。

const 的性能比 readonly 的要好。由于程序集可以直接访问值,而不用查询变量,因此性能稍高。但是,开发者需要考虑是否值得为了这点性能而使得代码变得僵硬。在决定这么做之前,您应该先通过 profile 工具做性能测试。(可以试试 BenchmarkDotNet)

const 关键字用来声明那些必须在编译期得以确定的值,例如 attribute 参数、switch case 语句的标签、enum 的定义等,偶尔用于声明不会随版本而变化的值。除此之外的值考虑用 readonly 常量声明。