Effective C# 原则12:选择变量初始化而不是赋值语句
(译注:根据我个人对文章的理解,我把initializer译为:初始化器,它是指初始化语法,也就是在一个类里声明变量的同时,直接创建实例值的方法。
例:object m_o = new
object();如果这段代码不在任何函数内,但在一个类里,它就是一个初始化器,而不管你是把它放在类的开始还以结尾。)
一些类经常不只一个构造函数。时间一长,就难得让它的成员变量以及构造函数进行同步了。最好的确保这样的事不会发生的方法就是:在声明就是的时间就直接初始化,而不是在每个构造函数内进行赋值。而且你应该使用初始化器语法同时为静态的和实例的变量进行初始化。
在C#里,当你声明一个变量时就自然的构造了这个成员变量。直接赋值:
public class MyClass
{
// declare the collection, and initialize it.
private ArrayList _coll = new ArrayList( );
}
忽略你最终会给MyClass添加多少个构造函数,_coll会正确的初始化。编译器会产生一些代码,使得在你的任何一个构造函数调用前,都会初始化你声明的实例变量。当你添加一个新的构造函数时,_coll就给你初始化了。当你添加了一个新的变量,你不用在所有的构造函数里添加初始化代码;直接在声明的地方对它进行初始化就行了。同样重要的是:如果你没有明确的声明任何一个构造函数,编译会默认的给你添加一个,并且把所有的变量初始化过程都添加到这个构造函数里。
初始化器更像是一个到构造函数的方便的快捷方法。初始化生成的代码会放置在类型的构造函数之前。初始化会在执行类型的基类的构造函数之前被执行,并且它们是按你声明的先后关系顺序执行的。
使用初始化器是一个最简单的方法,在你的类型里来避免使用一些没有赋值的变量,但这并不是很好。下面三种情况下,你不应该使用初始化器语法。首先就是,如果你是初始化一个对象为0,或者为null。系统默认会在你任何代码执行前,为所有的内容都初始化为0。系统置0的初始化是基于底层的CPU指令,对整个内存块设置。你的任何其它置0的初始化语句是多余的。C#编译器忠实的添加额外的指令把内存设置为0。这并没有错,只是效率不高。事实上,如果是处理值类型数据,这是很不值的:
MyValType _MyVal1; // initialized to 0
MyValType _MyVal2 = new MyValType(); // also 0
两条语句都是把变量置为0。第一个是通过设置包含_MyVal1的内存来置0;而第二个是通过IL指令initobj,这对变量_MyVal2会产生装箱与拆箱操作。这很要花一点额外的时间(参见原则17)。
第二个低效率的是在你为一个对象添加两个构造函数时会产生。你使用初始化器初始化变量,而所有的构造函数也对这些变量进行了初始化。这个版本的MyClass两个不同的ArrayList对象在它的构造函数内:
public class MyClass
{
// declare the collection, and initialize it.
private ArrayList _coll = new ArrayList( );
MyClass(){}
MyClass( int size )
{
_coll = new ArrayList( size );
}
}
当你创建一个新的MyClass对象时,特别指定集合的大小,你创建了两个数组列表。其中一个很快成为垃圾对象。初始化器在所有的构造函数之前会执行,构造函数会创建第2个数组列表。编译器产生了这个的一个版本,当然这是你决不会手动写出来的。(参见原则14来使用一个恰当的方法来解决这个问题)
public class MyClass
{
// declare the collection, and initialize it.
private ArrayList _coll;
MyClass( )
{
_coll = new ArrayList( );
}
MyClass( int size )
{
_coll = new ArrayList( );
_coll = new ArrayList( size );
}
}
最后一个原因要把初始化放到构造函数里就是促使异常的捕获。你不能在初始化器中使用try块,任何在构造时因成员变量产生的异常可能衍生到对象的外面。你无法试图在你的类里来捕获它。你应该把那些初始化代码移到构造函数里,这样你就可以捕获异常从而保证你的代码很友好(参见原则45)。
变量初始化器是一个最简单的方法,在忽略构造函数时来保证成员变量被正确的初始化。初始化器在所有的构造函数之前被执行。使用这样的语法意味着当你在为后来发布的版本中添加了构造函数时,不会忘记添加恰当的初始化到构造函数里。当构造函数与初始化生成同样的成员对象时,就使用初始化器。阅读简单而且易于维护。