程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [CLR via C#]8.1~8.3 實例構造器和類型構造器

[CLR via C#]8.1~8.3 實例構造器和類型構造器

編輯:C#入門知識

  類實例構造器是允許將類型的實例初始化為良好狀態的一種特殊的方法。

  類實例構造器方法在"方法定義元數據表"中始終叫.ctor(代表constructor)。創建一個引用類型的實例時,首先為實例的數據字段分配內存,然後初始化對象的附加字段(類型對象指針和同步塊索引),最後調用類型的實例構造器來設置對象的初始化狀態。

  構造引用類型的對象時,在調用類型的實例構造器之前,為對象分配的內存總是先被歸零。構造器將沒有顯式重寫的所有字段保證都有一個0或null值。     與其他方法不同,實例構造器永遠不能被繼承。也就是說,類只有類自己定義的實例構造器。     如果你定義的類沒有顯式定義任何構造器,C#編譯器將定義一個默認(無參)構造器。在它的實現中,只能簡單地調用了基類的無參構造器。
 

piblic  SomeType () : 

  一個類可以定義多個實例構造器。每個構造器都必須有一個不同的簽名,而且都可以有不同的可訪問性。     為了使代碼"可驗證"(verifiable),類的實例構造器在訪問從基類繼承的任何字段之前,必須先調用基類的構造器。     在極少數情況下,可以在不調用實例構造器的前提下創建一個類型的實例。比如Object的MemberciseClone方法。     C#語言提供了一個簡單的語法,允許在構造引用類型的一個實例時,對類型中定義的字段進行初始化:
Internal   Int32 m_x =
  我們來看下生成的IL代碼吧,著重看下.ctor方法中的代碼!   明顯可以看出,實例構造器把值5存到字段m_x,再調用基類的構造器。也就是說,C#編譯器提供了一種簡化的語法,允許以"內聯"的方式初始化實例字段。編譯器幫助我們調用構造器方法來執行初始化,但是我們要注意代碼的"膨脹效應",如下類定義:
   Int32 m_x =  String m_s =  Double m_d = 
         SomeType() {  SomeType(Int32 x) {  SomeType(String s) {  m_d = 
  使用反編譯器會查看到有三個構造器.ctor方法。   

 

  值類型(struct)構造器的工作方式與引用類型(class)的構造器截然不同。CLR總是允許創建值類型的實例,並且沒有辦法阻止值類型的實例化。所以,值類型其實並不需要定義構造器,C#編譯器根本不會為值類型生成默認的無參構造器。如以下代碼:

   

  CLR是允許為值類型定義構造器。但執行這種構造器的唯一方式就是寫代碼來顯示地調用它們。如下:
 
    ==  =  Point (, =  Point (, 

 =m_y =   

  正確答案是0,為什麼呢?因為代碼中沒有任何地方顯式調用過Point的構造器,即使值類型提供了無參構造器。更明顯的是,C#編譯器會報錯:error CS0568:結構不能包含顯式地無參構造器。     C#編譯器故意不允許值類型帶有無參構造器,旨在避免開發人員對這種構造器在什麼時候調用產生迷茫。沒有無參構造器,值類型的字段總是被初始化為0或null。     嚴格的說,只有當值類型的字段嵌套到引用類型中,才保證會被初始化0或null。基於棧的值類型字段不保證為0或null。     注意,雖然C#編譯器不允許值類型帶有無參構造器,但是CLR允許。你可以使用其他語言(如IL匯編語言)定義帶有無參構造器的值類型。     由於C#是不允許為值類型定義無參構造器,所以編譯一下類型是,會報錯。
 
     Int32 m_x = 
    為了生成"可驗證"的代碼,在訪問值類型的任何一個字段前,都需要對全部字段進行賦值,所以,值類型的任何構造器必須初始化值類型的全部字段。
 
    =
  編譯這段代碼時,C#編譯器會報錯,說字段"SomeValType.m_y"必須賦值。   為了修正這個問題,需要在構造器中為y賦一個值(通常為0)。   下面是對值類型的全部字段進行賦值的一個替代方案:

     = 
    m_x =
}

  除了實例構造器,CLR還支持類型構造器(type constructor),也稱為靜態構造器(static constructor)、類構造器(class constructor)等。

  類型構造器可用於接口(C#不允許)、引用類型和值類型。     實例構造器的作用是設置類型的實例的初始狀態。類型構造器的作用是設置類型的初始狀態。   類型默認沒有定義類型構造器,如果定義,也只能定義一個,此外,類型構造器永遠沒有參數。   比如:
  
 
    

  類型構造器的調用比較麻煩,JIT編譯器在編譯一個方法時,會檢查代碼中都引用了那些類型。任何一個類型定義了類型構造器,JIT編譯器都會檢查針對當前AppDomain,是否已經執行了這個類型構造器。CLR總要確保一個類型構造器只執行一次。為了保證這一點,在調用類型構造器時,調用線程要獲取一個互斥線程同步鎖。這樣一來,如果多個線程試圖調用某一個類型的類型構造器,只有一個線程可以獲得鎖,其他線程會被阻塞。第一個線程會執行靜態構造器中的代碼。當第一個線程離開構造器後,正在等待的線程將被喚醒,然後發現構造器中的代碼已經被執行過。因此,這些線程不會再次執行代碼,將直接從構造器方法返回。另外,如果再次調用這樣的一個方法,CLR知道該方法所在類型的類型構造器已經被執行過了,從而確保構造器不會再被調用。     雖然在值類型中能定義一個類型構造器,但不要這麼做,因為CLR有時不會調用值類型的類型構造器。     由於CLR保證了一個類型構造器在每個AppDomian中只執行一次,而且這種執行時線程安全的,所以非常適合在類型構造器中初始化類型需要的任何單實例(Singleton)對象。     類型構造器中的代碼只能訪問類型的靜態字段,並且它的常規用途就是初始化這些字段。和實例構造器一樣,C#提供了簡單的原發來初始化類型的靜態字段。如;
    Int32 s_x = 

  類型初始化的性能:   在編譯一個方法時,JIT編譯器要決定是否在方法中生成一個對類型構造器的調用。如果JIT編譯器決定生成這個調用,它還必須決定將這個調用添加到什麼位置。具體什麼位置,有以下兩種可能:     1)JIT編譯器可以剛好在創建類型的第一個實例前,或者剛好在訪問類的一個非繼承的字段或成員之前生成這個調用。這稱為"精確"(precise)語義,因為CLR調用類型構造器的時機拿捏恰到好處。     2)JIT編譯器可能在首次訪問一個靜態字段或一個靜態/實例方法之前,或者在調用一個實例構造器之前,隨便找個時間生成。這稱為"字段初始化前"(before-field-init)語義,因為CLR只保證訪問成員之前會運行類型構造器,可能提前很早就允許了。   "字段初始化前"語義是首選的,因為它是CLR能夠自由選擇調用類型構造器的時間,而CLR會盡可能地利用這一點來生成運行得更快的代碼。   默認情況下,語言的編譯器會選擇對你定義的類型來說最恰當的一種語義,並在類型定義元數據表的行中設置beforefieldinit標識,從而告訴CLR這個選擇。   現在重點關注下C#編譯器具體如何讓選擇,以及這些選擇會對性能產生什麼樣的影響,如下代碼:
     Int32 iterations =  *  * 
        Int32 s_x = 
        Precise() { s_x = 
      = (Int32 x = ; x < iterations; x++
            BeforeFieldInit.s_x = = (Int32 x = ; x < iterations; x++
            Precise.s_x = 
      = (Int32 x = ; x < iterations; x++= = (Int32 x = ; x < iterations; x++= 
  運行上述代碼的結果      C#編譯器如果看到一個類(BeforeFieldInit)包含進行了內聯初始化的靜態字段,就會在類的類型定義表中生成一個添加了BeforeFieldInit元數據標記的記錄項。   C#編譯器如果看到一個類(Precise)包含顯式地類型構造器,就不會添加BeforeFieldInit元數據標記。   它的基本原理是:靜態字段只要在訪問之前初始化就可以了,具體什麼時間無所謂。而顯式類型構造器中包含可能具有副作用的代碼,所以需要精確拿捏運行的時間。      

 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved