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)。

变量初始化器是一个最简单的方法,在忽略构造函数时来保证成员变量被正确的初始化。初始化器在所有的构造函数之前被执行。使用这样的语法意味着当你在为后来发布的版本中添加了构造函数时,不会忘记添加恰当的初始化到构造函数里。当构造函数与初始化生成同样的成员对象时,就使用初始化器。阅读简单而且易于维护。

========================================================

Item 12: Prefer Variable Initializers to Assignment Statements

Classes often have more than one constructor. Over time, it's easy for the member variables and the constructors to get out of synch. The best way to make sure this doesn't happen is to initialize variables where you declare them instead of in the body of every constructor. You should utilize the initializer syntax for both static and instance variables.

Constructing member variables when you declare that variable is natural in C#. Just assign a value:

public class MyClass
{
  // declare the collection, and initialize it.
  private ArrayList _coll = new ArrayList( );
}

Regardless of the number of constructors you eventually addto the MyClass type, _coll will be initialized properly. The compiler generates code at the beginning of each constructor to execute all the initializers you have defined for your instance member variables. When you add a new constructor, _coll gets initialized. Similarly, if you add a new member variable, you do not need to add initialization code to every constructor; initializing the variable where you define it is sufficient. Equally important, the initializers are added to the compiler-generated default constructor. The C# compiler creates a default constructor for your types whenever you don't explicitly define any constructors.

Initializers are more than a convenient shortcut for statements in a constructor body. The statements generated by initializers are placed in object code before the body of your constructors. Initializers execute before the base class constructor for your type executes, and they are executed in the order the variables are declared in your class.

Using initializers is the simplest way to avoid uninitialized variables in your types, but it's not perfect. In three cases, you should not use the initializer syntax. The first is when you are initializing the object to 0, or null. The default system initialization sets everything to 0 for you before any of your code executes. The system-generated 0 initialization is done at a very low level using the CPU instructions to set the entire block of memory to 0. Any extra 0 initialization on your part is superfluous. The C# compiler dutifully adds the extra instructions to set memory to 0 again. It's not wrongit's just inefficient. In fact, when value types are involved, it's very inefficient.

MyValType _MyVal1;  // initialized to 0
MyValType _MyVal2 = new MyValType(); // also 0

Both statements initialize the variable to all 0s. The first does so by setting the memory containing MyVal1 to 0. The second uses the IL instruction initobj, which causes both a box and an unbox operation on the _MyVal2 variable. This takes quite a bit of extra time (see Item 17).

The second inefficiency comes when you create multiple initializations for the same object. You should use the initializer syntax only for variables that receive the same initialization in all constructors. This version of MyClass has a path that creates two different ArrayList objects as part of its construction:

public class MyClass
{
  // declare the collection, and initialize it.
  private ArrayList _coll = new ArrayList( );

  MyClass( )
  {
  }

  MyClass( int size )
  {
    _coll = new ArrayList( size );
  }
}

When you create a new MyClass, specifying the size of the collection, you create two array lists. One is immediately garbage. The variable initializer executes before every constructor. The constructor body creates the second array list. The compiler creates this version of MyClass, which you would never code by hand. (For the proper way to handle this situation, see Item 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 );
  }
}

The final reason to move initialization into the body of a constructor is to facilitate exception handling. You cannot wrap the initializers in a TRy block. Any exceptions that might be generated during the construction of your member variables get propagated outside of your object. You cannot attempt any recovery inside your class. You should move that initialization code into the body of your constructors so that you implement the proper recovery code to create your type and gracefully handle the exception (see Item 45).

Variable initializers are the simplest way to ensure that the member variables in your type are initialized regardless of which constructor is called. The initializers are executed before each constructor you make for your type. Using this syntax means that you cannot forget to add the proper initialization when you add new constructors for a future release. Use initializers when all constructors create the member variable the same way; it's simpler to read and easier to maintain.

评论: 0 | 引用: 0 | 查看次数: 4775
发表评论
登录后再发表评论!