关于 readonly 和 const 的区别,网上已经有很多人撰文作了特别的说明。小G在这里也是参考了网上很多人的文章和MSDN的相关内容才写出的这篇随笔。关于这些修改符的理解,是我个人编程时的经验总结,如有不妥之处,欢迎大家拍砖。
原本只是想说说 static 的使用场景,但后来觉得这些修饰关键字都相关,索性就都拿出来讲讲,权当积累了。这些关键字是C#语法中非常基础的部分,没错,非常基础。但是我发现在真实项目中,能将这些关键字应用到正确场景中的人并不多,所以我觉得有必要老调重弹,详细说说它们的用法。
个人认为,其实 readonly 和 const 属于一类关键字,它们只用于修饰字段,决定字段何时被赋值;而 static 则不同,它可以修改字段和方法,决定类型成员是否从属于某一具体的实例,这个我们后面会用实例来作说明。
1.readonly 关键字
readonly 关键字是可以在字段上使用的修饰符。 当字段声明包括 readonly 修饰符时,该声明引入的字段赋值只能作为声明的一部分出现,或者出现在同一类的构造函数中。 这是MSDN上对readonly关键字的解释,是不是有点晦涩难懂?嗯,这是MSDN一贯的作风,哈哈。下面我们用实例来作演示,说明 readonly 具体的应用场景。
“该声明引入的字段赋值只能作为声明的一部分出现”,这句话的意思是,readonly 修改的字段,可以在声明时赋值;“或者出现在同一类的构造函数中”,这句话的意思是,readonly 所修饰的字段,也可以在类的构造函数中赋值。我们以人(People)为例,用代码实例来作说明:
1: void Main()
2: {
3: Male m=new Male("男");
4: Female fm=new Female("女");
5: Console.WriteLine("男士性别:" + m.GetGender);
6: Console.WriteLine("女士性别:" + fm.GetGender);
7: }
8:
9: // 人的基类
10: public class People
11: {
12: private readonly string _gender="男";
13: public People(){}
14: public People(string gender)
15: {
16: this._gender=gender;
17: }
18: public string GetGender
19: {
20: get
21: {
22: return this._gender;
23: }
24: }
25: }
26: // 男性
27: public class Male:People
28: {
29: public Male(string gender):base(gender)
30: {
31: }
32: }
33: // 女性
34: public class Female:People
35: {
36: public Female(string gender):base(gender)
37: {
38: }
39: }
上面这段代码很好理解,我创建了一个 People 的基类,基类中包含一个 readonly 字段 Gender,即性别,并赋予了默认值“男”(没有别的意思,因为大家通常都叫我们先生嘛(^O^));然后派生出了 Male (男性)和 Female (女性)两个具体的类型,并分别创建了它们的含参构造函数,当然,你可以为这两个派生类创建默认的无参构造,但我肯定你不愿意那样做,因为那样做这个世界就真的大一统了,清一色的光棍啊,吼吼~~~你肯定很难想象没有女孩子这个世界会变成什么样子,所以,老老实实地创建含参构造吧(^_^)。
这个实例的结果如下:
男士性别:男 女士性别:女
这看起来没什么特别的,下面我们在 People 基类里添加一个测试方法,试图修改 gender 的值,看看会发生什么:
看到了没?编辑器直接报错了,在构造函数之外的其他方法中修改 readonly 字段是不允许的,这跟人一出生即确定性别是一个道理(当然人类已经逆天了,那部分我们暂且不予理会(-_-)!!!)。
2.const 关键字
const 关键字用于修改字段或局部变量的声明。 它指定字段或局部变量的值是常数,不能被修改。 相比之下,这个关键字就容易理解的多了,const 变量在我看来,它就是一个占位符,是C#的设计者为了让大家更好操作常量而允许大家临时定义的一个标记。如果大家用 ILspy 或 Reflector 反编译过.NET程序包,就会发现,const 字段在程序集中全部被替换成了真实的值而不再是变量名。
仍然用实例来做说明,我们在 People 基类中添加下面这些属性:
1: private const int _numberOfFeet = 2 ;
2: // 脚的数量
3: public int NumberOfFeet
4: {
5: get
6: {
7: return _numberOfFeet;
8: }
9: }
这个属性的作用也很简单,定义了一个常量,用以指示人类脚的数量,并公开一个 NumberOfFeet 属性允许派生类或引用类去获取这个值。我们来尝试给这个属性添加一个 set 写操作器给常量 _numberOfFeet 赋值,看看会发生什么:
提示已经写得很清楚了,赋值号(即=)左侧必须是个变量、属性或索引器,这也说明 _numberOfFeet 是一个常量,上面这个 set 方法体的内容和 2 = 3 的效果是一样的,这显然不符合C#语法要求,而且也不符合逻辑。const 字段不光在实例方法中不可修改,在构造函数和外部引用中也不能对其进行赋值操作,即 const 字段一经声明,不可更改。这里要注意,const 本身即是包含 static 修饰的,所以 get 方法体内,我没有使用 this._numberOfFeet 来调用它。
3.static 关键字
使用 static 修饰符声明属于类型本身而不是属于特定对象的静态成员。 static 修饰符可用于类、字段、方法、属性、运算符、事件和构造函数,但不能用于索引器、析构函数或类以外的类型。 可以这么理解,static 用于指定哪些类型成员是公用的,包括字段、属性、方法、运算符、事件和构造函数;如果一个类被声明为 static,那么此类型不可被实例化,它的所有类型成员都是公用的,也即我们常说的工具类。例如我们经常使用的 Console 类和 Enumerable 类,下面是 Console 类和 Enumerable类 的类型声明:
我们来给 People 类的基类添加一个方法,用于统计人口总数;这个方法不应该从属于某个具体的 People 类实例,无论 Male 还是 Female,只要一出生(创建),世界人口就应该自动递增:
1: void Main()
2: {
3: Male m=new Male("男");
4: Console.WriteLine("当前人口总数:" + m.Population);
5: Female fm=new Female("女");
6: Console.WriteLine("当前人口总数:" + fm.Population);
7: // Console.WriteLine("男士性别:" + m.GetGender);
8: // Console.WriteLine("女士性别:" + fm.GetGender);
9: }
10:
11: // 人的基类
12: public class People
13: {
14: private readonly string _gender="男";
15: public People(){}
16: public People(string gender)
17: {
18: this._gender=gender;
19: }
20: public string GetGender
21: {
22: get
23: {
24: return this._gender;
25: }
26: }
27: private const int _numberOfFeet = 2 ;
28: // 脚的数量
29: public int NumberOfFeet
30: {
31: get
32: {
33: return _numberOfFeet;
34: }
35: }
36:
37: private static int _count = 0;
38: // 人口递增
39: public void AddCount()
40: {
41: _count++;
42: }
43: // 获取人口总数
44: public virtual int Population
45: {
46: get
47: {
48: return _count;
49: }
50: }
51: }
52: // 男性
53: public class Male:People
54: {
55: public Male(string gender):base(gender)
56: {
57: base.AddCount();
58: }
59: }
60: // 女性
61: public class Female:People
62: {
63: public Female(string gender):base(gender)
64: {
65: base.AddCount();
66: }
67: }
这一次,我们在 People 基类中添加了静态字段_count,并添加 AddCount() 方法使其递增,随后修改了 Male 和 Female 的构造函数,调用基类的 AddCount 方法来增加人口数量,程序执行结果如下:
我们看到只要新增一个 People 的实例,无论是 Male 还是 Female,人口总数都会递增。这其实就是 static 关键字的作用,它使得类型成员可以在多个实例间共用。BTW,来说说误用 static 变量的情形吧。
我们知道 ASP.NET 应用中,IIS 本身就是使用多线程来响应用户的请求的。来设想这样一个场景,码农在一个页面后台中添加了一个全局的静态变量,好吧,如果他想在多个用户间传递数据,貌似这样也是可行的,只是所有人都只能拿到最后一次赋值的结果。但是,如果他把用户名或是其他验证信息声明为静态变量,而在权限验证之类的地方又刚好引用了这个变量,想想下面会发生什么。当系统管理员访问这个页面时,当前访问这个页面的用户都拥有管理员权限,直至下一用户登录;再极端一点,如果你把这个变量放在了 BasePage 里,那么将迎来另一次大一统,访问派生自 BasePage 页面的用户,都将拥有同最后登录用户的权限验证信息。好了,乡亲们,无论如何,把私密用户信息共享真的是一个很不负责的做法。
你可能会说我不会犯这样的错误,但这并不代表其他人不会。没错,这是最基本的C#语法,但是我想说,如果我们写了好几年的.NET程序,至今还没搞清楚这些基本概念,抑或是你到现在还不清楚 string 和 StringBuilder 的应用场景一样,这绝对是一件很悲催的事情。
4. static readonly 关键字
这实际上并不是一个独立的关键字了,这是前面两个关键字的组合。我们照猫画虎,来定义一下这个关键字的应用场景。readonly 只能用于字段修饰,static 决定字段属于类型而不是特定的对象实例,于是我们可以得出以下定义:
static readonly 修饰的字段,只能在声明时赋值,或是在静态构造中赋值(关于静态构造的注意事项请参见:《》)。
static readonly 与 const 的应用场景已十分相似,区别在于,static readonly 修饰的是运行时常量,而 const 修饰的是编译时常量;static readonly 可以用于修饰引用型变量,而 const 修饰的引用型变量只能是 string 或 null(其他引用型变量的构造函数初始化是在运行时,而非编译时)。
下面是微软的 Color 类的构造示例,演示了 static readonly 的具体应用场景:
1: public class Color
2: {
3: public static readonly Color Black = new Color(0, 0, 0);
4: public static readonly Color White = new Color(255, 255, 255);
5: public static readonly Color Red = new Color(255, 0, 0);
6: public static readonly Color Green = new Color(0, 255, 0);
7: public static readonly Color Blue = new Color(0, 0, 255);
8: private byte red, green, blue;
9:
10: public Color(byte r, byte g, byte b)
11: {
12: red = r;
13: green = g;
14: blue = b;
15: }
16: }
这是特别说明一下,string 是一个非常特殊的引用型类型,详细的解释,请参见:《》。