Effective C# Item14: Utilize Constructor Chaining

      当我们写类的构造函数的时候经常会有许多种重载版本,它们完成的工作有一部分是重复的。许多开发者往往是先写第一个构造函数,然后拷贝粘贴它们到其他的构造函数当中去。我们应当停止这种做法。当我们发现许多重载的构造函数都包含有一些普遍的算法时,我们应当将这些逻辑集合到构造函数中,形成构造函数链。通过这种方法我们可以避免代码重复,生成更加高效的对象代码。

      构造函数初始化允许一个构造函数调用其他构造函数。下面的例子展示了一个简单的应用。

    public class MyClass
    
{
        
private ArrayList _coll;
        
private string _name;
        
public MyClass() : this(0"")
        
{
        }

        
public MyClass(int initialCount) : this(initialCount, "")
        
{
        }

        
public MyClass(int initialCount, string name)
        
{
            _coll 
= (initialCount > 0)? new ArrayList(initialCount) : new ArrayList();
            _name 
= name;
        }

    }

      C#不支持默认参数,我们必须重载构造函数来达到这个目的,这就出现了许多的重复代码。我们应当使用构造函数链来完成这种工作,而不应该使用普通的函数来完成这些重复代码。因为如果使用普通函数的话会有造成效率的一些丧失:

    public class MyClass
    
{
        
private ArrayList _coll;
        
private string _name;
        
public MyClass()
        
{
            commonConstructor(
0"");
        }

        
public MyClass(int initialCount) : this(initialCount, "")
        
{
            commonConstructor(initialCount, 
"");
        }

        
public MyClass(int initialCount, string name)
        
{
            commonConstructor(initialCount, name);
        }

        
private void commonConstructor( int initialCount, string name)
        
{
            _coll 
= (initialCount > 0)? new ArrayList(initialCount) : new ArrayList();
            _name 
= name;
        }

    }

      这个版本看起来和原来差不多,但是却生成了较差的中间代码。当我们使用构造函数时,编译器会为构造函数加了一些额外的代码来对成员变量的初始化进行声明。当我们使用构造函数链的时候,编译器会将其写在基础构造器中,并在其他构造函数里调用。但是如果我们使用普通的函数,编译器就不能识别这些重复代码了。第二种版本生成的IL和下面这样的代码的效果是类似的:

    public class MyClass
    
{
        
private ArrayList _coll;
        
private string _name;
        
public MyClass()
        
{
            
object();  //当然这样是不能通过编译的,只是举个例子。下同
            commonConstructor(0"");
        }

        
public MyClass(int initialCount) : this(initialCount, "")
        
{
            
object();
            commonConstructor(initialCount, 
"");
        }

        
public MyClass(int initialCount, string name)
        
{
            
object();
            commonConstructor(initialCount, name);
        }

        
private void commonConstructor( int initialCount, string name)
        
{
            _coll 
= (initialCount > 0)? new ArrayList(initialCount) : new ArrayList();
            _name 
= name;
        }

    }

      如果是第一种方式的话,生成的IL和下面代码的效果是类似的:

    public class MyClass
    
{
        
private ArrayList _coll;
        
private string _name;
        
public MyClass() : this(0"")
        
{
        }

        
public MyClass(int initialCount) : this(initialCount, "")
        
{
        }

        
public MyClass(int initialCount, string name)
        
{
            
object();
            _coll 
= (initialCount > 0)? new ArrayList(initialCount) : new ArrayList();
            _name 
= name;
        }

    }

      还不买构造函数的帐?那我们再考虑一下只读常量的情况。下面的例子中name字段是只读的常量。但是如果我们不用构造函数的话,初始化name就会出现错误:

    public class MyClass
    
{
        
private ArrayList _coll;
        
private readonly string _name;
        
public MyClass()
        
{
            commonConstructor(
0"");
        }

        
public MyClass(int initialCount) : this(initialCount, "")
        
{
            commonConstructor(initialCount, 
"");
        }

        
public MyClass(int initialCount, string name)
        
{
            commonConstructor(initialCount, name);
        }

        
private void commonConstructor( int count, string name)
        
{
            _coll 
= (initialCount > 0)? new ArrayList(initialCount) : new ArrayList();
            _name 
= name;  //不能在构造函数外修改readonly成员变量
        }

    }

      我们不必为此将所有成员变量的初始化写到每一个构造函数中去,C#为我们提供了更好的处理方法。大部分复杂的类都拥有不止一个构造函数,它们的职责就是构建该类型的一个对象。从最根本来说,它们应当是相似的,或者说是逻辑共享的。使用C#构造函数初始化可以很简单的实现这种算法,对于重复的部分我们可以只写一次,而且在执行的时候也只执行一次。

      对于构造函数来说,我们不仅要知道它的执行顺序,还需要了解它的初始化方式。我们应当尽力做到每个成员变量在构造函数中只初始化一次。下面是当我们首次创建一个类型的实例时的执行顺序:

      1. 静态变量成员声明,为其分配内存并置0

      2. 执行静态变量初始化

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

      4. 执行静态构造函数

      5. 实例变量成员声明,为其分配内存并置0

      6. 执行变量初始化

      7. 执行基类构造函数

      8. 执行实例的构造函数

      当我们再次创建此类型实例时执行顺序是从第5步开始的。静态构造函数只会被执行一次。

      C#编译器会保证当对象被创建时其中的所有成员都会被初始化。我们的目标是要保证所有的初始化都遵循我们的要求,并且初始化代码只运行一遍。对于简单的成员变量声明我们可以使用普通的初始化方式。对于逻辑比较复杂的成员变量声明,我们应当是用构造函数对其进行初始化,并将其与其他构造函数结合起来以减少重复代码。

      译自   Effective C#:50 Specific Ways to Improve Your C#                      Bill Wagner著

      对于前面代码中出现的那个object()我再说一下:
      使用构造函数链方法生成的IL:
      这是MyClass(int initialCount): this(initialCount, "")重载

.method public hidebysig specialname rtspecialname 
        instance 
void  .ctor(int32 initialCount) cil managed
{
  
// Code size       13 (0xd)
  .maxstack  3
  IL_0000:  ldarg.
0
  IL_0001:  ldarg.
1
  IL_0002:  ldstr      
""
  IL_0007:  call       instance 
void ClassLibrary2.MyClass::.ctor(int32,
                                                                  
string)
  IL_000c:  ret
}
 // end of method MyClass::.ctor

      这是MyClass(int initialCount, string name)

.method public hidebysig specialname rtspecialname 
        instance 
void  .ctor(int32 initialCount,
                             
string name) cil managed
{
  
// Code size       37 (0x25)
  .maxstack  4
  IL_0000:  ldarg.
0
  IL_0001:  call       instance 
void [mscorlib]System.Object::.ctor()
  IL_0006:  ldarg.
0
  IL_0007:  ldarg.
1
  IL_0008:  ldc.i4.
0
  IL_0009:  bgt.s      IL_0012
  IL_000b:  newobj     instance 
void [mscorlib]System.Collections.ArrayList::.ctor()
  IL_0010:  br.s       IL_0018
  IL_0012:  ldarg.
1
  IL_0013:  newobj     instance 
void [mscorlib]System.Collections.ArrayList::.ctor(int32)
  IL_0018:  stfld      
class [mscorlib]System.Collections.ArrayList ClassLibrary2.MyClass::_coll
  IL_001d:  ldarg.
0
  IL_001e:  ldarg.
2
  IL_001f:  stfld      
string ClassLibrary2.MyClass::_name
  IL_0024:  ret
}
 // end of method MyClass::.ctor

      使用构造函数调用一般函数方法生成的IL:
      这是MyClass(int initialCount, string name),注意IL_0001

.method public hidebysig specialname rtspecialname 
        instance 
void  .ctor(int32 initialCount,
                             
string name) cil managed
{
  
// Code size       15 (0xf)
  .maxstack  3
  IL_0000:  ldarg.
0
  IL_0001:  call       instance 
void [mscorlib]System.Object::.ctor()
  IL_0006:  ldarg.
0
  IL_0007:  ldarg.
1
  IL_0008:  ldarg.
2
  IL_0009:  call       instance 
void ClassLibrary2.MyClass::commonConstructor(int32,
                                                                              
string)
  IL_000e:  ret
}
 // end of method MyClass::.ctor

      这是commonConstructor( int initialCount, string name)

.method private hidebysig instance void  commonConstructor(int32 initialCount,
                                                           
string name) cil managed
{
  
// Code size       31 (0x1f)
  .maxstack  4
  IL_0000:  ldarg.
0
  IL_0001:  ldarg.
1
  IL_0002:  ldc.i4.
0
  IL_0003:  bgt.s      IL_000c
  IL_0005:  newobj     instance 
void [mscorlib]System.Collections.ArrayList::.ctor()
  IL_000a:  br.s       IL_0012
  IL_000c:  ldarg.
1
  IL_000d:  newobj     instance 
void [mscorlib]System.Collections.ArrayList::.ctor(int32)
  IL_0012:  stfld      
class [mscorlib]System.Collections.ArrayList ClassLibrary2.MyClass::_coll
  IL_0017:  ldarg.
0
  IL_0018:  ldarg.
2
  IL_0019:  stfld      
string ClassLibrary2.MyClass::_name
  IL_001e:  ret
}
 // end of method MyClass::commonConstructor

      可以看到当我们使用构造函数链调用构造函数的重载时并不会马上生成类型的对象,这个动作是在基础构造器中完成的。而当我们使用构造函数调用一般函数时,会在构造函数结束前生成类型的对象,然后再调用函数。这样不但会使中间代码臃肿,还会降低执行的效率。

回到目录
posted on 2006-10-13 09:08  aiya  阅读(770)  评论(0编辑  收藏  举报