代码改变世界

《Effective C#》Item 12:推荐使用成员初始化语句

2007-09-06 15:54  Jacky_Xu  阅读(156)  评论(0编辑  收藏  举报

为了方便内容的开展,先说说一个对象的构造过程。

对于类型第一个实例的构造过程大致如下:

1. 分配静态成员的内存空间,此时空间存储数据为0

2. 执行静态成员的初始化语句;

3. 执行基类的静态构造函数;

4. 执行类型的静态构造函数;

5. 分配成员的内存空间,此时空间存储数据为0

6. 执行成员的初始化语句;

7. 执行相应的基类构造函数;

8. 执行类型的构造函数。

 

那么对于同类型的后续创建对象,前4个步骤不用执行的,直接从第5步开始。

(这里有些出入,对于如下例子:

    public class clsA

    {

       private static readonly string strA = "A";

       static clsA()

       {

           Debug.WriteLine( strA );

           strA = "A Changed!";

           Debug.WriteLine( strA );

           Debug.WriteLine( "A static constructor!" );

       }

 

       public clsA()

       {

           Debug.WriteLine( "A constructor!" );

       }

    }

 

    public class clsB:clsA

    {

       private static readonly string strB = "B";

       static clsB()

       {

           Debug.WriteLine( strB );

           strB = "B Changed!";

           Debug.WriteLine( strB );

           Debug.WriteLine( "B static constructor!" );

       }

       public clsB():base()

       {

           Debug.WriteLine( "B constructor!" );

       }

    }

 

结果输出为:

B

B Changed!

B static constructor!

A

A Changed!

A static constructor!

A constructor!

B constructor!

 

这里的顺序并不完全符合书中所说,区别主要在于基类静态成员以及静态构造函数的调用,这些语句是在构造函数成员初始化语句中才被调用,即“base()”这句的时候才被调用,这也比较符合静态成员初始化以及静态构造函数的调用,即首次访问此类型的时候才被调用。

因此我觉比较合理的对象构造过程应该如下。

1  分配静态成员的内存空间,此时空间存储数据为0

2  执行静态成员的初始化语句;

3  执行类型的静态构造函数;

4  分配成员的内存空间,此时空间存储数据为0

5  执行成员的初始化语句;

6  执行基类的静态构造函数;

7  执行相应的基类构造函数;

8  执行类型的构造函数。

 

在此多谢xyq1986网友提出的质疑)

 

现在来说说为什么推荐使用成员初始化语句来初始化成员。由于成员初始化先于构造函数的调用,所以更早初始化有利于使用;其次,避免对构造函数重复添加初始化代码,尤其是新增成员的时候,把初始化放到定义成员的位置,减少因构造函数之间的不一致,而造成某些成员未被初始化。而且把成员初始化从构造函数中抽出来,使代码显得更简洁明朗。

例如:

    public class MyList

    {

        //Init class member here

        private ArrayList _List = new ArrayList();

    }

 

是不是所有的成员都可以这样进行初始化呢。事实上,有三种场景是不适合用这样的方式来完成成员初始化。

 

第一种就是给成员赋给“0”或者“null”,这并不是错误语句,而是没有必要的。参看前面的对象构造过程,由于成员首先会被分配内存空间,并且同时已经用“0”进行初始化了。因此显式的赋值会增加指令操作,而影响效率。

 

第二种就是根据不同参数来指明成员初始化的方式,而一般类似操作是放在构造函数中。如果使用成员初始化语句的话,那么在构造函数中重新初始化成员,就会生成一个短暂的临时对象。

例如:

    public class MyList

    {

        //Init class member here

        private ArrayList _List = new ArrayList();

 

        public MyList()

        {

        }

 

        public MyList( int nSize )

        {

            _List = new ArrayList( nSize );

        }

    }

 

以上例子,如果通过“MyList( int nSize )”这个构造函数来创建对象,会先通过成员初始化来初始“_List”成员,然后在构造函数重新初始化此成员。那么实际上,会在内存中产生两个ArrayList对象,前一个已经成为垃圾,需要等待GC进行回收。对于这种情况,用如下的方式可能会更好。

    public class MyList

    {

        //Init class member here

        private ArrayList _List;

 

        public MyList()

        {

            _List = new ArrayList();

        }

 

        public MyList( int nCount )

        {

            _List = new ArrayList( nCount );

        }

    }

 

对于最后一种就是,由于通过成员初始化来初始化成员,在初始化语句地方是不能加入try-catch来捕获异常,因此在初始化过程中所出现的异常会被传递出去,为了保证程序的安全,则需要在调用端进行try-catch捕获。不过这样操作会使调用端的代码显得繁琐,更合理的做法,是在类型中对成员初始化进行异常处理,因此采用构造函数来初始化成员效果更好些。例如:

    public class MyTest

    {

        private TempObject _value;

 

        public MyTest()

        {

            try

            {

                _value = new TempObject();

            }

            catch( Exception err )

            {

                //Handle exception here

            }

        }

    }

 

对于成员初始化的好处以及其的使用限制,到此就已经说完了。不过,我个人更喜欢通过构造函数来初始化成员。对于文章中所说到的,使用构造函数来初始化成员的一些不利之处,例如:多个构造函数之间初始化操作的繁琐或者成员初始化的不统一,我觉得可以通过构造函数调用构造函数的方式来减少此类问题。不过这也只是我个人看法,至于具体用什么并没有明确的标准,只要把不同的方法合理整合到自己的应用程序中就行了。