Chapter 2 - Item 14 : Minimize Duplicate Initialization Logic

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得

在現實情況下,常常會需要針對同一類別撰寫多種建構子,以應付不同的初始化需求。本章節提出利用建構子鏈或是預設參數建構子,來達到消除重覆初始化程式碼的目的。

1. 使用建構子鏈或是預設參數消除重覆的初始化邏輯。

建構子鏈:    

public class MyClass
{
    private List<int> _dataList;
    private string _name;

    public MyClass( )
        : this( 0, string.Empty )
    {
    }

    public MyClass( int initialCount )
        : this( initialCount, string.Empty )
    {
    }

    public MyClass( string name )
        : this( 0, name )
    {
    }

    public MyClass( int initialCount, string name )
    {
        _dataList = ( initialCount > 0 ) ?
            new List<int>( initialCount ) :
            new List<int>( );
        _name = name;
    }
}

預設參數:

public class MyClass2
{
    private List<int> _dataList;
    private string _name;

    public MyClass2( int initialCount = 0, string name = "" )
    {
        _dataList = ( initialCount > 0 ) ?
            new List<int>( initialCount ) :
            new List<int>( );
        _name = name;
    }
}

預設參數在一般情況下比建構子鏈能更省程式碼(若參數數量為 2,建構子鏈需要定義 4 種不同的建構子才能滿足所有輸入需求)。即便如此,預設參數在使用上仍有些限制。
    
考慮以下程式碼:

public class MyClass3<T> where T : new() // Constrains by new( )
{
}

public class Client
{
    // Complie Error, due to parameterless constructor is not defined.
    private MyClass3<MyClass2> _myClass3 = new MyClass3<MyClass2>( );
}

為修復此錯誤,需在 MyClass2 加入無參數建構子的定義。

public class MyClass2
{
    private List<int> _dataList;
    private string _name;

    public MyClass2( )
        : this( 0, string.Empty )
    {
    }

    public MyClass2( int initialCount = 0, string name = "" )
    {
        _dataList = ( initialCount > 0 ) ?
            new List<int>( initialCount ) :
            new List<int>( );
        _name = name;
    }
}
Note 1:如果欲利用反射創建 MyClass2 物件,也需明確定義無參數的建構子。

Note 2:預設參數值必須為編譯時期的常數(i.e. name 不能設定為 string.Empty, string.Empty 是靜態唯讀欄位)。

Note 3:承 Note 2,若組件修改預設參數值,客戶端程式也需重新編譯。

2. 使用建構子鏈消除 IL 重覆的物件初始化邏輯。

public class MyClass
{
    private List<int> _dataList;
    private string _name;

    public MyClass( )
    {
        commonConstructor( 0, string.Empty );
    }

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

    public MyClass( string name )
    {
        commonConstructor( 0, name );
    }

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

以上程式碼乍看下沒有問題,但其實效率並不好;因為編譯器並不會自動幫我們消除重覆的物件初始化邏輯(父類別 object 初始化)。
    
編譯後的 IL Code:
    
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor() cil managed
    {
      // 程式碼大小       22 (0x16)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
      IL_0006:  nop
      IL_0007:  nop
      IL_0008:  ldarg.0
      IL_0009:  ldc.i4.0
      IL_000a:  ldsfld     string [mscorlib]System.String::Empty
      IL_000f:  call       instance void CacheTest.MyClass::commonConstructor(int32,
                                                                              string)
      IL_0014:  nop
      IL_0015:  ret
    } // end of method MyClass::.ctor
    
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor(int32 initialCount) cil managed
    {
      // 程式碼大小       22 (0x16)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
      IL_0006:  nop
      IL_0007:  nop
      IL_0008:  ldarg.0
      IL_0009:  ldarg.1
      IL_000a:  ldsfld     string [mscorlib]System.String::Empty
      IL_000f:  call       instance void CacheTest.MyClass::commonConstructor(int32,
                                                                              string)
      IL_0014:  nop
      IL_0015:  ret
    } // end of method MyClass::.ctor
    
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor(string name) cil managed
    {
      // 程式碼大小       18 (0x12)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
      IL_0006:  nop
      IL_0007:  nop
      IL_0008:  ldarg.0
      IL_0009:  ldc.i4.0
      IL_000a:  ldarg.1
      IL_000b:  call       instance void CacheTest.MyClass::commonConstructor(int32,
                                                                              string)
      IL_0010:  nop
      IL_0011:  ret
    } // end of method MyClass::.ctor
    
    每一種建構子都重覆呼叫了基類別的建構子,出現重覆的程式碼。
    
    接著檢查建構子鏈的 IL Code,驗證是否能夠消除重覆的初始化邏輯。
    
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor() cil managed
    {
      // 程式碼大小       15 (0xf)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldc.i4.0
      IL_0002:  ldsfld     string [mscorlib]System.String::Empty
      IL_0007:  call       instance void CacheTest.MyClass::.ctor(int32,
                                                                  string)
      IL_000c:  nop
      IL_000d:  nop
      IL_000e:  ret
    } // end of method MyClass::.ctor
    
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor(int32 initialCount) cil managed
    {
      // 程式碼大小       15 (0xf)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldarg.1
      IL_0002:  ldsfld     string [mscorlib]System.String::Empty
      IL_0007:  call       instance void CacheTest.MyClass::.ctor(int32,
                                                                  string)
      IL_000c:  nop
      IL_000d:  nop
      IL_000e:  ret
    } // end of method MyClass::.ctor
    
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor(string name) cil managed
    {
      // 程式碼大小       11 (0xb)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  ldc.i4.0
      IL_0002:  ldarg.1
      IL_0003:  call       instance void CacheTest.MyClass::.ctor(int32,
                                                                  string)
      IL_0008:  nop
      IL_0009:  nop
      IL_000a:  ret
    } // end of method MyClass::.ctor
    
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor(int32 initialCount,
                                 string name) cil managed
    {
      // 程式碼大小       39 (0x27)
      .maxstack  8
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
      IL_0006:  nop
      IL_0007:  nop
      IL_0008:  ldarg.0
      IL_0009:  ldarg.1
      IL_000a:  ldc.i4.0
      IL_000b:  bgt.s      IL_0014
      IL_000d:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
      IL_0012:  br.s       IL_001a
      IL_0014:  ldarg.1
      IL_0015:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor(int32)
      IL_001a:  stfld      class [mscorlib]System.Collections.Generic.List`1<int32> CacheTest.MyClass::_dataList
      IL_001f:  ldarg.0
      IL_0020:  ldarg.2
      IL_0021:  stfld      string CacheTest.MyClass::_name
      IL_0026:  ret
    } // end of method MyClass::.ctor
    
    結果發現,確實消除重覆的基礎類別(object)初始化邏輯。
    
3. private function 無法設定唯讀欄位。

public class MyClass
{
    private List<int> _dataList;
    private readonly string _name;

    public MyClass( )
    {
        commonConstructor( 0, string.Empty );
    }

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

    public MyClass( string name )
    {
        commonConstructor( 0, name );
    }

    private void commonConstructor( int initialCount, string name )
    {
        _dataList = ( initialCount > 0 ) ?
                new List<int>( initialCount ) :
                new List<int>( );

        // Compile Error, changing the variable that is readonly where outside of
        // constructor.
        _name = name;
    }
}
結論:
1. 使用建構子鏈或預設參數消除重覆的初始化邏輯。

2. C# 物件初始化的建構順序:
    1. 靜態欄位初始化(第一次建構時執行)
    2. 靜態建構子(第一次建構時執行)
    3. 欄位初始化
    4. 基類靜態欄位初始化(第一次建構時執行)
    5. 基類靜態建構子(第一次建構時執行)
    6. 基類欄位初始化
    7. 基類建構子
    8. 建構子