《解析C#類中的構造函數》
一. C#中的構造函數概述:
C#中類包含數據成員和函數成員。函數成員提供了操作類中數據的某些功能,包括方法、屬性、構造器和終結器、運算符和索引器。
- 構造函數設計的定義:
構造器是允許將類型的實例初始化為良好狀態的一種特殊方法。
- 2. 構造函數設計的作用:
c#中創建一個引用類型的實例時,首先為實例的數據字段分配內存,然后初始化對象的附加字段(類型對象指針和同步塊索引),最后調用類型的實例構造器來設置對象的初始化狀態。
- 3. 對類沒有定義任何共有的或受保護的構造器,導致該類不能使用new在外部代碼中實例化,此方式的作用:
(1).類僅用作某些靜態成員或屬性的容器,因此永遠不會實例化它。
(2).希望類僅通過調用某個靜態成員函數來實例化(對象實例化工廠方法)
4.基類構造函數的使用:
編譯器在調用基類的構造器前,會初始化任何使用了簡化語法的字段,以維持源代碼給人留下的“這些字段總是有一個值”的印象,如果基類的構造器調用了一個虛方法,它回調派生類中定義的一個方法,就可能出現問題。在這種情況下,用簡化語法來初始化的字段在虛方法調用之前就已經初始化了。
二. C#類中構造函數的分類:
- 1. 實例構造函數:
(1).實例構造函數概述:
聲明一個與包含的類同名的方法,該方法沒有返回類型。【如果沒有提供任何構造函數,編譯器會在后臺創建一個默認的構造函數】
(2).實例構造函數的作用:
A.把所有的成員字段初始化為標準的默認值。
B. 構造引用類型的對象時,在調用類型的實例構造器之前,為對象分配的內存總是先被歸零,構造器沒有顯示重寫的所有字段保證都有一個0或null值。實例構造器永遠不能被繼承。
C. 為了使代碼“可驗證”,類的實例構造器在訪問從基類集成的任何字段之前,必須先調用基類的構造器。如果派生類的構造器沒有顯示調用一個基類構造器,C#編譯器會自動生成對默認的基類構造器的調用。
- 2. 靜態構造函數:
(1).靜態構造函數概述:
靜態構造函數(類型構造器、類型初始化構造器):靜態構造函數(類型構造器)不能含有參數,沒有修飾符,只能編寫無參數的構造函數,這種構造函數只能執行一次,一個類也只能有一個靜態構造函數,當前面的構造函數是實例構造函數,只要創建類的對象,就會執行它。
(2).靜態構造函數運行原理:
.NET運行庫沒有確保什么時候執行靜態構造函數,所以不應該把要求在某個特定時刻執行的代碼放在靜態構造函數中。也不能預計不同類的靜態構造函數按照什么順序執行。
(3).靜態構造函數的調用:
靜態構造函數沒有訪問修飾符,其他C#代碼從來不調用它,但是在類加載時,總是又.NET運行庫調用它。靜態構造函數只能訪問類的靜態成員,不能訪問類的實例成員。
3.構造函數使用范圍:
(1).無參數的實例構造函數與靜態構造函數可以在同一類中同時定義,有參的實例構造函數與靜態構造函數也可以在同一類中同時定義。【在類加載時執行靜態構造函數,而在創建實例時執行實例構造函數】
(2). C#編譯器不允許值類型(結構)定義無參數的構造器,所以編譯器永遠不會生成自動調用它的代碼,沒有無參數構造器,值類型(結構)的字段總是被初始化為0或null。【C#不允許值類型(結構)定義無參數的構造器,但是CLR允許。C#運行值類型(結構)定義有參數的構造器】
三. C#類中替代構造函數的方式:
1.在極少數情況下,可以在不調用實例構造器的前提下創建一個類型的實例。使用Object的MemberwiseClone方法,該方法的作用是分配內存,初始化對象的附加字段(類型對象指針和同步塊索引),然后將源對象的字節數據復制到新對象中。
2.在用運行時序列化器反序列化對象時,通常也不需要調用構造器,反序列化使用FormatterService類型的GetUninitialzedObject或者GetSafeUninitializedObject方法為對象分配內存,期間不會調用一個構造器。
四. C#類中構造器的使用情形:
1.靜態構造函數的使用:
類中有一些靜態字段或屬性,需要在第一次使用類之前,從外部源中初始化這些靜態字段和屬性。
2.構造函數的調用:
在C#類中,構造器可以調用其他構造器。C#構造函數初始化器可以包含對同一類的另一個構造函數的調用,也可以包含對直接基類的構造函數的調用。初始化器中不能有多個調用。【使用this關鍵字實現初始化器,調用參數最匹配的那個構造器】構造函數初始化器在構造函數的函數體之前進行。
五. C#類中類型構造器的性能:
1. 類型構造器的調用:
JIT編譯器必須決定是否生成代碼來調用它,而CLR必須確保對它的調用是線程安全的。
- 2. JIT編譯器在生成一個對類型構造器的調用,這個調用添加的位置情況:
(1).JIT編譯器可以剛好在創建類型的第一個實例之前,或者剛好在訪問類的一個非繼承的字段或成員之前生成這個調用。即為精確語義。
(2).JIT編譯器可以在首次訪問一個靜態字段或者一個靜態/實例方法之前,或者在調用一個實例構造器之前,隨便找一個時間生成調用。即為字段初始化前語義。
3. .JIT對調用的位置選擇:
“字段初始化前”語義是首選的,因為它使CLR能夠自由選擇調用類型構造器的時機,而CLR會盡可能地利用這一點來生成運行得更快的代碼。
4.C#編譯器如果看到一個類(BeforeFieldInit)包含進行了內聯初始化的靜態字段,會在類的類型定義表中生成一個添加了BeforeFiledInit元數據標記的記錄項。C#編譯器如果看到一個類包含顯示的類型構造器,就不會添加BeforeFiledInit元數據標記。【靜態字段只要在訪問之前初始化就可以了,具體什么時間無所謂。而顯式類型構造器可能包含具有副作用的代碼,所以需要在精確拿捏運行的時間】
文章列表