# 原则12:选择变量初始化语法(initializer)而不是赋值语句
**By D.S.Qiu**
尊重他人的劳动,**支持原创,转载请注明****[出处](/blog/1987663)****:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
类经常会有多个构造函数。时间一长,成员变量和构造器很难保持同步。避免这个的最好方式就是在变量声明的时候初始化而不是在构造函数内初始化。你应该使用初始化语法为静态变量和实例变量进行初始化。
在 C# 中,当你声明变量的时候构建变量是很自然的。当你声明变量时直接进行初始化:
```
public class MyClass
{
// declare the collection, and initialize it.
private List<string> labels = new List<string>();
}
```
无论 MyClass 类有多少个构造函数, labels 都会被正确初始化。编译器会在构造器的开头为你定义的实例成员变量生成初始化代码。当你增加一个构造函数, labels 就会获得初始化。同样的,如果你增加一个新的成员变量,你不需要在每个构造函数中添加初始化代码,在定义的地方初始化变量是非常合适的。同样重要的是,初始化语句(initializer①)也会被添加进编译器产生的默认构造函数中。当你的类没有显式定义任何构造函数编译器会创建一个默认构造函数。
初始化语句比构造函数中的语句更方便快捷。初始化语句被放在你的构造函数之前。初始化语句在基类构造函数之前执行,而且它们执行的顺序是定义它们的顺序。
使用初始化语句是你的类中成员变量未初始化最简单的方法,但不完美。有三种情况,你不应该使用初始化语句语法。第一种情况是当你要将对象初始化为0或 null ,系统在你的代码执行之前,会默认为所有内容设置为0。系统产生的0初始化是使用非常低级的 CPU 指令设置整块内存为0。你的任何额外的0初始化都是多余的。 C# 编译器会非常尽责地添加额外指令去又一次设置内存为0。这没有问题——只是低效的。实际上,如果是值类型,那会更低效。
```
MyValType myVal1; // initialized to 0
MyValType myVal2 = new MyValType(); // also 0
```
上面两条语句的变量都会初始化为0。第一个是设置 myVal1 的内存为0。第二个是使用 IL 指令 initObj ,这个会引起 myVal2 的封箱和拆箱操作。这需要相当的额外时间(查看原则45)。
当你对同一个对象进行多次初始化时,第二种情况就来了。只有变量在所有构造函数中接收的初始化代码是一样的才使用初始化语句语法。下面版本的 MyClass 构造函数呢中创建两个不同的 List 对象:
```
public class MyClass2
{
// declare the collection, and initialize it.
private List<string> labels = new List<string>();
MyClass2()
{
}
MyClass2(int size)
{
labels = new List<string>(size);
}
}
```
当你创建 MyClass2 对象,并指定了集合的大小,你就创建了两个数组队列。其中一个立即变为垃圾。变量初始化语句在每个构造函数之前执行。构造函数中创建了第二个数组队列。编译器产生了这样的 MyClass2 版本,当然你是绝不会手动这样写的。(查看原则14,使用恰当方式处理这个问题)。
```
public class MyClass2
{
// declare the collection, and initialize it.
private List<string> labels;
MyClass2()
{
labels = new List<string>();
}
MyClass2(int size)
{
labels = new List<string>();
labels = new List<string>(size);
}
}
```
如果你使用隐式属性也是会出现同样的情况(查看原则1)。你的代码不能访问编译生成的备份存储域,所以隐式属性就不能使用初始化语句。除了在构造函数汇总初始化隐式属性,你别无选择。使用隐式属性仍然具有的一个好处就是在 set 访问器中不用进行逻辑校验。如果你把隐式属性迁移为命名的备份存储域,你应该使用初始化语法而不是构造函数来更新初始化代码。因为使用隐式属性是数据成员的更好选择,原则14说明了如何减少隐式属性数据域的复制。
要在构造函数中初始化的最后一个原因是方便异常的处理。你不能再初始化语句中使用 try 块。初始化成员变量时的任何异常都会传给实例化的对象。你不能在类内对这个异常进行捕获。你需要把初始化代码移到构造函数中,这样就可以实现正确的回收代码来创建你的类型和妥善处理异常(查看原则47)。
成员变量初始化语法是保证是初始化的无论哪个构造函数被调用的最简单方法。初始化语法都在每个构造函数之前执行。使用这个语法意味着你不会因新版本中添加的构造函数而忘记初始化。当所有构造函数以相同方式创建成员变量时,使用初始化语法;这易于阅读,更易于维护。
①:initializer 翻译为初始化语法
小结:
昨晚一直状态不好,就没有写下去,还好这个原则比较简短,就借用早餐+午休的时间完成了。这个原则建议我们要使用初始化语法来初始化成员变量,这样既简单又便于维护,不过除了上面说的三种情况之外。
欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德!
有关本书的其他章节翻译请[点击查看](/category/297763),转载请注明出处,尊重原创!
如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。
转载请在**文首**注明出处:[http://dsqiu.iteye.com/blog/1987663](/blog/1987663)
更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)
- 第一章 C# 语言习惯
- 原则1:使用 属性(Poperty)代替可直接访问的数据成员(Data Member)
- 原则2:偏爱 readonly 而不是 const
- 原则3:选择 is 或 as 而不是强制类型转换
- 原则4:使用条件特性(conditional attribute)代替 #if
- 原则5:总是提供 ToString()
- 原则6:理解几个不同相等概念的关系
- 原则7:明白 GetHashCode() 的陷阱
- 原则8:优先考虑查询语法(query syntax)而不是循环结构
- 原则9:在你的 API 中避免转换操作
- 原则10:使用默认参数减少函数的重载
- 原则11:理解小函数的魅力
- 第二章 .NET 资源管理
- 原则12:选择变量初始化语法(initializer)而不是赋值语句
- 原则13:使用恰当的方式对静态成员进行初始化
- 原则14:减少重复的初始化逻辑
- 原则15:使用 using 和 try/finally 清理资源
- 原则16:避免创建不需要的对象
- 原则17:实现标准的 Dispose 模式
- 原则17:实现标准的 Dispose 模式
- 原则18:值类型和引用类型的区别
- 原则19:确保0是值类型的一个有效状态
- 原则20:更倾向于使用不可变原子值类型
- 第三章 用 C# 表达设计
- 原则21:限制你的类型的可见性
- 原则22:选择定义并实现接口,而不是基类
- 原则23:理解接口方法和虚函数的区别
- 原则24:使用委托来表达回调
- 原则25:实现通知的事件模式
- 原则26:避免返回类的内部对象的引用
- 原则27:总是使你的类型可序列化
- 原则28:创建大粒度的网络服务 APIs
- 原则29:让接口支持协变和逆变
- 第四章 和框架一起工作
- 原则30:选择重载而不是事件处理器
- 原则31:用 IComparable&lt;T&gt; 和 IComparer&lt;T&gt; 实现排序关系
- 原则32:避免 ICloneable
- 原则33:只有基类更新处理才使用 new 修饰符
- 原则34:避免定义在基类的方法的重写
- 原则35:理解 PLINQ 并行算法的实现
- 原则36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原则37:构造并行算法的异常考量
- 第五章 杂项讨论
- 原则38:理解动态(Dynamic)的利与弊
- 原则39:使用动态对泛型类型参数的运行时类型的利用
- 原则40:使用动态接收匿名类型参数