详细解释下 const 常量 和 readonly 常量 的区别吧。
前者是编译时常量,后者是运行时常量。
一般的,Java阵营认为不需要区分两者,把编译时常量和运行时常量交给编译器去区分就可以了,所以在Java里面仅有一种常量 final。
而 C# 则是让程序员自己能够定义。但是,在 Effective C# 的书中,作者强烈建议使用运行时常量 readonly 而不是编译时常量 const,为什么呢,我这里会解释。
那么,什么是编译时常量呢?在编译的时候,编译器会把运行环境中所有的该变量替换成实际值。
比如,
const int i = 10;
static void Main() {
Console.WriteLine(i);
}
会被实际编译成
static void Main() {
Console.WriteLine(10); // 注意,这里变量 i 被替换成了 10
}
若是编译器无法评估实际值,那么就无法使用 const 修饰符。
所以,如下几类的代码是无效的:
1、调用一个函数来获得值:
const int i = GetValue();
2、涉及到了构造函数:
const SomeClass myObj = new SomeClass();
如下几类的代码是合法的:
const int i = 10 + 2 + 3; // 合法,编译器能把 10 + 2 + 3 计算成 15
const int j = i + 4; // 合法,编译器能计算出 19
const String s = "Hello" + "World"; // 依然合法
编译时常量的好处:
1、能够带来稍许的性能上的提升。
实际运行的时候,程序不必计算表达式,因为表达式的值在编译的时候被编译器计算过了。
2、能够给一些“神奇”的值命名。
比如,这种代码是不好的:
if (i > 30) {
// 做某些事情
}
别人看到代码的时候,突然看到数字30,会很难明白这个30是做什么用的。这里30就是一个“神奇”的数值,除了作者,其他人很难弄懂它究竟是干嘛的。
如果把上述代码改写成如下,会较好:
const int maxFileCountToOpen = 30;
if (i > maxFileCountToOpen) {
// 做某些事情
}
代码编译出来的效果是一模一样的,但是在第二个例子中,我们给30命了一个名字,这样增强了程序的可读性。
编译时常量的坏处:会带来版本问题
比如,你要写一个DLL和一个EXE。在DLL中,定义了一个const常量。
A.DLL
--------------
public class AFoo {
public const int Foo = 10;
}
然后,在另外一个项目里面,叫做B.EXE,来引用AFoo.Foo
B.EXE
--------------
public class BFoo {
public static void Main() {
Console.WriteLine(AFoo.Foo);
}
}
运行B.EXE,会打印出 10。非常正确。
若是我们现在要更新A.DLL的版本,把AFoo.Foo的值升级成20:
A.DLL
----------------
public class AFoo {
// public const int Foo = 10;
public const int Foo = 20;
}
编译好A.DLL。再次运行B.EXE,会是什么结果?结果还是会打印出10,而不是最新的版本里面包含的20。
若是你能回答为什么,那么你就对 const 常量已经理解透了。
一个技巧:若是一个常量的作用域是public,不要使用const修饰符。
那么,什么是运行时常量呢?
一般的,运行时常量 readonly 的值是在程序运行的时候被评估的,所以它不会带来性能上的提升。它唯一的好处就是,不允许该常量在运行时被修改。
所以,如下的代码是合法的:
readonly int a = GetValue();
这里函数GetValue会在运行时被评估,然后结果被赋值给a,此后,a的值就不允许被修改。
运行时常量 readonly 还有一个好处是,它可以在构造函数里面赋值:
比如,如下的代码也是合法的:
class SomeClass {
readonly int b;
public SomeClass() {
b = GetValue();
}
}
一个技巧:可以把一些常量定义成public,然后在构造函数里面赋值。
最后一个特点。const常量都是默认static,所以const不可以和static混用。
readonly 常量既可以是静态的,也可以是非静态的。所以,readonly 可以和 static 混用。