構造器(constructor) 是允許將類型的實例化初始化爲良好狀態的一種特殊方法。構造器方法在”方法定義元數據表”中始終叫.ctor(代表constructor)。創建一個引用類型的實例時,首先爲實例的數據字段分配內存,然後初始化對象的附加字段(類型對象指針和同步塊索引),最後調用類型的實例構造器來設置對象的初始狀態。
構造引用類型的對象時,在調用類型的實例構造器之前,爲對象分配的內存總是先被歸零。構造器沒有顯式重寫的所有字段保證都有一個0和null。
實例構造器永遠不能被繼承。不能有如下的修飾符:virtual,new,override,sealed和abstract。如果定義的類沒有顯式定義任何構造器,C# 編譯器將定義一個默認(無參)構造器。在它的實現中,只是簡單地調用了基類的無參構造器。
例如:
public class TestClass{}
等價於
public class TestClass{
public TestClass():base(){}
}
如果類的修飾符爲abstract,那麼編譯器生成的默認構造參數的可訪問性爲protected;否則構造器會被賦予public可訪問類。如果基類沒有提供無參構造器,那麼派生類必須顯式調用一個基類的構造器,否則編譯器會報錯。如果類的修飾符爲static(sealed 和abstract),編譯器根本不會在類定義中生成一個默認構造器。
一個了類型可以定義多個實例構造器。每個構造器都必須有一個不同的簽名,而且每個都可以有不同的可訪問性。爲了使代碼可驗證,類的實例構造器在訪問從基類繼承的任何字段之前,必須先調用基類的構造器。如果派生類的構造器沒有顯式調用一個基類無參構造器,C#編譯器會自動生成對默認的基類構造器的調用。最終,System.Object的公共無參構造器會得到調用。該構造器什麼都不做,會直接返回。由於System.Object沒有定義實例數據字段,所以它的構造器無事可做。
在極少數情況下,可以在不調用實例構造器的前提下創建一個類型的實例。一個典型的例子是Objcet的MemberwiseClone方法。該方法的作用是分配內存,初始化對象的附加字段(類型對象指針和同步塊索引),然後將源對象的字節數據複製到新對象中。另外,用運行時序列化器反序列化對象時,通常也不需要調用構造函數。反序列化代碼使用System.Runtime.Serialization.FormatterServices類型的GetUninitializedObject或者GetSafeUninitializedObject方法爲對象分配內存,期間不用調用構造器。
不要在構造器中調用會影響所構造對象的任何虛方法.原因是假如這個虛方法在當前要實例化的類型的派生類型中進行了重寫,就會調用重寫的實現。但在繼承層次結構中,字段尚未初始化。所以,調用虛方法將導致無法預測的行爲 (可以看看我之前寫的這篇關於繼承理解的文章:http://blog.csdn.net/u010533180/article/details/52709684)
C# 語言提供了一個簡單的語法,允許在構造引用類型的一個實例時,對類型中定義的字段進行初始化。
class ConstructorDemo
{
private int m_x = 5;
}
構造ConstructorDemo的對象時,它的字段m_x字段初始化爲5。我們看一下對應的IL代碼:(IL是VS自帶的反編譯工具,讀者可自行查閱)
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// 代碼大小 15 (0xf)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.5
IL_0002: stfld int32 ConstructorDemo::m_x
IL_0007: ldarg.0
IL_0008: call instance void [mscorlib]System.Object::.ctor() //隱式調用了Object的構造函數
IL_000d: nop
IL_000e: ret
} // end of method ConstructorDemo::.ctor
通過反編譯可看到ConstructorDemo的構造器把值5存儲到字段m_x,在調用基類(Object)的構造器。
C# 提供了這樣簡化的語法,允許以內聯方式初始化實例字段。但在幕後,它會將這種語法轉換成構造器方法中的代碼來執行。這同時提醒我們注意代碼的膨脹效益。何爲膨脹?我們通過下面的例子來看。重點和知識點來了。
首先用一個無參構造函數和一個int構造函數來看IL代碼:
class ConstructorDemo
{
private int m_x = 5;
private string m_s = "hello world";
private double m_d = 3.14159;
private byte m_b = 0xff;
public ConstructorDemo()
{
}
public ConstructorDemo(int x)
{
m_x = x;
}
}
對應的IL代碼爲:
無參構造函數的IL:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// 代碼大小 54 (0x36)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.5
IL_0002: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x
IL_0007: ldarg.0
IL_0008: ldstr "hello world"
IL_000d: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s
IL_0012: ldarg.0
IL_0013: ldc.r8 3.1415899999999999
IL_001c: stfld float64 NowCoderProgrammingProject.ConstructorDemo::m_d
IL_0021: ldarg.0
IL_0022: ldc.i4 0xff
IL_0027: stfld uint8 NowCoderProgrammingProject.ConstructorDemo::m_b
IL_002c: ldarg.0
IL_002d: call instance void [mscorlib]System.Object::.ctor()
IL_0032: nop
IL_0033: nop
IL_0034: nop
IL_0035: ret
} // end of method ConstructorDemo::.ctor
int構造函數的IL代碼:
.method public hidebysig specialname rtspecialname
instance void .ctor(int32 x) cil managed
{
// 代碼大小 61 (0x3d)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.5
IL_0002: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x
IL_0007: ldarg.0
IL_0008: ldstr "hello world"
IL_000d: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s
IL_0012: ldarg.0
IL_0013: ldc.r8 3.1415899999999999
IL_001c: stfld float64 NowCoderProgrammingProject.ConstructorDemo::m_d
IL_0021: ldarg.0
IL_0022: ldc.i4 0xff
IL_0027: stfld uint8 NowCoderProgrammingProject.ConstructorDemo::m_b
IL_002c: ldarg.0
IL_002d: call instance void [mscorlib]System.Object::.ctor()
IL_0032: nop
IL_0033: nop
IL_0034: ldarg.0
IL_0035: ldarg.1
IL_0036: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x
IL_003b: nop
IL_003c: ret
} // end of method ConstructorDemo::.ctor
通過上面的兩端IL代碼 我們還沒有發現有什麼問題,現在我們在增加一個構造函數。
class ConstructorDemo
{
private int m_x = 5;
private string m_s = "hello world";
private double m_d = 3.14159;
private byte m_b = 0xff;
public ConstructorDemo()
{
}
public ConstructorDemo(int x)
{
m_x = x;
}
public ConstructorDemo(string s)
{
m_s = s;
m_d = 10;
}
}
我們現在只看新增這個string類型對應的IL源碼:
.method public hidebysig specialname rtspecialname
instance void .ctor(string s) cil managed
{
// 代碼大小 76 (0x4c)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldc.i4.5
IL_0002: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x
IL_0007: ldarg.0
IL_0008: ldstr "hello world"
IL_000d: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s
IL_0012: ldarg.0
IL_0013: ldc.r8 3.1415899999999999
IL_001c: stfld float64 NowCoderProgrammingProject.ConstructorDemo::m_d
IL_0021: ldarg.0
IL_0022: ldc.i4 0xff
IL_0027: stfld uint8 NowCoderProgrammingProject.ConstructorDemo::m_b
IL_002c: ldarg.0
IL_002d: call instance void [mscorlib]System.Object::.ctor()
IL_0032: nop
IL_0033: nop
IL_0034: ldarg.0
IL_0035: ldarg.1
IL_0036: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s
IL_003b: ldarg.0
IL_003c: ldc.r8 10.
IL_0045: stfld float64 NowCoderProgrammingProject.ConstructorDemo::m_d
IL_004a: nop
IL_004b: ret
} // end of method ConstructorDemo::.ctor
我們和上面的int構造函數的IL代碼對比發現,有很多相似的IL,我們在增加一個int和string的類型的構造函數。
class ConstructorDemo
{
private int m_x = 5;
private string m_s = "hello world";
private double m_d = 3.14159;
private byte m_b = 0xff;
public ConstructorDemo()
{
}
public ConstructorDemo(int x)
{
m_x = x;
}
public ConstructorDemo(string s)
{
m_s = s;
m_d = 10;
}
public ConstructorDemo(int x, string s)
{
m_x = x;
m_s = s;
}
}
我們看一下對應的IL代碼:
.method public hidebysig specialname rtspecialname
instance void .ctor(int32 x,
string s) cil managed
{
// 代碼大小 68 (0x44)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldc.i4.5
IL_0002: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x
IL_0007: ldarg.0
IL_0008: ldstr "hello world"
IL_000d: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s
IL_0012: ldarg.0
IL_0013: ldc.r8 3.1415899999999999
IL_001c: stfld float64 NowCoderProgrammingProject.ConstructorDemo::m_d
IL_0021: ldarg.0
IL_0022: ldc.i4 0xff
IL_0027: stfld uint8 NowCoderProgrammingProject.ConstructorDemo::m_b
IL_002c: ldarg.0
IL_002d: call instance void [mscorlib]System.Object::.ctor()
IL_0032: nop
IL_0033: nop
IL_0034: ldarg.0
IL_0035: ldarg.1
IL_0036: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x
IL_003b: ldarg.0
IL_003c: ldarg.2
IL_003d: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s
IL_0042: nop
IL_0043: ret
} // end of method ConstructorDemo::.ctor
在看這個對應的IL代碼發現其實和上面的幾個構造函數有很多重複的代碼,即初始化變量。如果在新增一個字段或者新增一個構造函數,就意味每個構造函數都會有對應重複的變量初始化,尤其是新增內聯字段,代碼會增加的更多。如果每個構造函數對字段值進行賦值操作,那麼對應生成的IL代碼會更多(讀者可自行嘗試)這就是代碼的膨脹。
對應的解決方案就是創建單個構造器來執行這些公共初始化。然後讓其它構造器顯式調用這個公共初始化構造器。這樣可以減少生成的代碼。在C#中利用this關鍵字來顯式調用另一個構造器:對上面的代碼修改如下:
class ConstructorDemo2
{
private int m_x;
private string m_s;
private double m_d;
private byte m_b;
public ConstructorDemo2()
{
m_x = 5;
m_s = "hello world";
m_d = 3.14159;
m_b = 0xff;
}
public ConstructorDemo2(int x)
: this()
{
m_x = x;
}
public ConstructorDemo2(string s)
: this()
{
m_s = s;
m_d = 10;
}
public ConstructorDemo2(int x, string s)
: this()
{
m_x = x;
m_s = s;
}
}
對應的IL代碼如下:
無參:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// 代碼大小 54 (0x36)
.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.5
IL_000a: stfld int32 NowCoderProgrammingProject.ConstructorDemo2::m_x
IL_000f: ldarg.0
IL_0010: ldstr "hello world"
IL_0015: stfld string NowCoderProgrammingProject.ConstructorDemo2::m_s
IL_001a: ldarg.0
IL_001b: ldc.r8 3.1415899999999999
IL_0024: stfld float64 NowCoderProgrammingProject.ConstructorDemo2::m_d
IL_0029: ldarg.0
IL_002a: ldc.i4 0xff
IL_002f: stfld uint8 NowCoderProgrammingProject.ConstructorDemo2::m_b
IL_0034: nop
IL_0035: ret
} // end of method ConstructorDemo2::.ctor
int類型構造參數:
.method public hidebysig specialname rtspecialname
instance void .ctor(int32 x) cil managed
{
// 代碼大小 17 (0x11)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void NowCoderProgrammingProject.ConstructorDemo2::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: ldarg.0
IL_0009: ldarg.1
IL_000a: stfld int32 NowCoderProgrammingProject.ConstructorDemo2::m_x
IL_000f: nop
IL_0010: ret
} // end of method ConstructorDemo2::.ctor
string 類型參數:
.method public hidebysig specialname rtspecialname
instance void .ctor(string s) cil managed
{
// 代碼大小 32 (0x20)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void NowCoderProgrammingProject.ConstructorDemo2::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: ldarg.0
IL_0009: ldarg.1
IL_000a: stfld string NowCoderProgrammingProject.ConstructorDemo2::m_s
IL_000f: ldarg.0
IL_0010: ldc.r8 10.
IL_0019: stfld float64 NowCoderProgrammingProject.ConstructorDemo2::m_d
IL_001e: nop
IL_001f: ret
} // end of method ConstructorDemo2::.ctor
string 和int類型參數
.method public hidebysig specialname rtspecialname
instance void .ctor(int32 x,
string s) cil managed
{
// 代碼大小 24 (0x18)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void NowCoderProgrammingProject.ConstructorDemo2::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: ldarg.0
IL_0009: ldarg.1
IL_000a: stfld int32 NowCoderProgrammingProject.ConstructorDemo2::m_x
IL_000f: ldarg.0
IL_0010: ldarg.2
IL_0011: stfld string NowCoderProgrammingProject.ConstructorDemo2::m_s
IL_0016: nop
IL_0017: ret
} // end of method ConstructorDemo2::.ctor
讀者可以對比一下編譯生成的代碼少了很多。這是很推薦的一種寫法。
我在附上幾幅對比圖,會更加明瞭(左側統一爲ConstructorDemo源碼,右側統一爲ConstructorDemo2):
無參構造函數IL代碼對比圖
int構造函數IL代碼對比圖
string構造函數IL代碼對比圖
int,string構造函數IL代碼對比圖
參考資料:
CLR VIA C#(第3版)