国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

[CLR via C#]8. 方法

2019-11-17 03:22:08
字體:
來源:轉載
供稿:網友

[CLR via C#]8. 方法

一、實例構造器和類(引用類型)

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

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

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

它等下于以下的代碼:

piblic class SomeType {    public SomeType () : base () { } }

  

  如果類的修飾符是abstract,那么編譯器生成的默認構造函數的可訪問性就是Internal sealed class SomeType { private Int32 m_x = 5;}  我們來看下生成的IL代碼吧,著重看下.ctor方法中的代碼!  明顯可以看出,實例構造器把值5存到字段m_x,再調用基類的構造器。也就是說,C#編譯器提供了一種簡化的語法,允許以"內聯"的方式初始化實例字段。編譯器幫助我們調用構造器方法來執行初始化,但是我們要注意代碼的"膨脹效應",如下類定義:

internal sealed class SomeType {        private Int32 m_x = 5;        private String m_s = "Hi there";        private Double m_d = 3.14159;        private Byte m_b;         // Here are some constructors.        public SomeType() { /* ... */ }        public SomeType(Int32 x) { /* ... */ }        public SomeType(String s) { /* ...; */ m_d = 10; }    }
  使用反編譯器會查看到有三個構造器.ctor方法。    我們在看下每個構造器方法的IL代碼。  .ctor()    .ctor(Int32)    .ctor(String)    有何發現呢?  編譯器為這三個構造器方法生成代碼時,在每個方法的開始位置,都會包含用于初始化m_x,m_s和m_d的代碼。在這些初始化代碼之后,編譯器會插入對基類構造器的調用。再然后,會插入構造器自己的代碼。  上面類中有三個構造器,所以編譯器就生成的三次初始化的m_x,m_s和m_d的代碼,由于兩方面都會增多,所有就會造成"代碼膨脹"的問題。  那如何解決呢?  可以考慮不是在定義字段的時候初始化,而是創建單個構造器來執行這些公共的初始化。然后,讓其他構造器顯示調用這個公共初始化構造器。
internal sealed class SomeType    {        // 不顯示初始化下面的字段        private Int32 m_x;        private String m_s;        private Double m_d;        private Byte m_b;         // 該構造器將所有的字段都設為默認值        // 其它所有構造器顯式調用這個構造器        public SomeType()        {            m_x = 5;            m_s = "Hi there";            m_d = 3.14159;            m_b = 0xff;        }         // 該構造器將所有的字段都設為默認值,然后修改m_x        public SomeType(Int32 x)            : this()        {            m_x = x;        }         // 該構造器將所有的字段都設為默認值,然后修改m_x        public SomeType(String s)            : this()        {            m_s = s;        }         // 該構造器將所有的字段都設為默認值,然后修改m_x和m_s        public SomeType(Int32 x, String s)            : this()        {            m_x = x;            m_s = s;        }    }

二、實例構造器和結構(值類型)

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

internal struct Point{    pblic Int32 m_x, m_y;}internal sealed class Rectangle {    public Point m_topLeft, m_bottomRight;}

  為了構造一個類Rectangle,必須使用new操作符,而且必須指定一個構造器。在這個例子中,調用的是C#自動生成的默認實例構造器,為Rectangle分配內存時,內存中包含Point值類型的兩個實例。處于對性能的考慮,CLR不會為包含在引用類型中的每個值類型字段都調用一個構造器。但是,如前面提到的,值類型的字段會被初始化為0或null。

  CLR是允許為值類型定義構造器。但執行這種構造器的唯一方式就是寫代碼來顯示地調用它們。如下:
internal struct Point {    // 在C#中,向一個值類型應用關鍵字new,    // 可以調用構造器來初始化值類型的字段。    public Int32 m_x, m_y;    public Point(Inte32 x, Int32 y) {        m_x = x;        m_y = y;    }}  internal sealed class Rectangle {    public Point m_topLeft, m_bottomRight;        public Rectangle() {        m_topLeft = new Point (1, 2);        m_bottomRight = new Point (100, 200);    }}

  好了,讓我們來改變下上面的沒有定義默認的無參構造器。

internal struct Point {    public Int32 m_x, m_y;    public Point() {        m_x =m_y = 5;    }}  internal sealed class Rectangle {    public Point m_topLeft, m_bottomRight;        public Rectangle() {     }}

  現在我們來想象,m_x和m_y值是多少呢?是5嗎?還是0?

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

  注意,在引用類型的構造器中,this被認為是只讀的,所以不能對它賦值。

三、類型構造器

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

  類型構造器可用于接口(C#不允許)、引用類型和值類型。  實例構造器的作用是設置類型的實例的初始狀態。類型構造器的作用是設置類型的初始狀態。  類型默認沒有定義類型構造器,如果定義,也只能定義一個,此外,類型構造器永遠沒有參數。  比如:
internal sealed class SomeRefType {    static SomeRefType () {        //SomeRefType 被首次訪問時,執行這里的代碼。    }}internal struct SomeValType {    //C#允許值類型定義無參構造器    static SomeValType () {        //SomeValType 被首次訪問時,執行這里的代碼。    }}

  類型構造器總是私有的,C#編譯器會自動把它們標記成private。之所以私有,是為了阻止任何有開發人員寫的代碼調用它,對它的調用總是由CLR負責的。

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

  雖然C#不允許值類型為它的實例字段使用內聯字段初始化語法,但可以為靜態字段使用。

  類型初始化的性能:  在編譯一個方法時,JIT編譯器要決定是否在方法中生成一個對類型構造器的調用。如果JIT編譯器決定生成這個調用,它還必須決定將這個調用添加到什么位置。具體什么位置,有以下兩種可能: 1)JIT編譯器可以剛好在創建類型的第一個實例前,或者剛好在訪問類的一個非繼承的字段或成員之前生成這個調用。這稱為"精確"(precise)語義,因為CLR調用類型構造器的時機拿捏恰到好處。 2)JIT編譯器可能在首次訪問一個靜態字段或一個靜態/實例方法之前,或者在調用一個實例構造器之前,隨便找個時間生成。這稱為"字段初始化前"(before-field-init)語義,因為CLR只保證訪問成員之前會運行類型構造器,可能提前很早就允許了。  "字段初始化前"語義是首選的,因為它是CLR能夠自由選擇調用類型構造器的時間,而CLR會盡可能地利用這一點來生成運行得更快的代碼。  默認情況下,語言的編譯器會選擇對你定義的類型來說最恰當的一種語義,并在類型定義元數據表的行中設置beforefieldinit標識,從而告訴CLR這個選擇。  現在重點關注下C#編譯器具體如何讓選擇,以及這些選擇會對性能產生什么樣的影響,如下代碼:
public sealed class TypeConstructorPerformance {    public static void Go() {        const Int32 iterations = 1000 * 1000 * 1000;        PerfTest1(iterations);        PerfTest2(iterations);    }     // 由于這個類沒有顯示定義類型構造器,所以C#在元數據中    // 用BeforeFieldInit來標記類型定義    internal s
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 惠州市| 肥城市| 邵阳县| 鄂州市| 沅江市| 荔波县| 温宿县| 财经| 呼图壁县| 政和县| 吉隆县| 孝义市| 巨鹿县| 洛扎县| 商城县| 蒙城县| 吉木萨尔县| 河北区| 贡山| 景谷| 鲁甸县| 康定县| 延川县| 开原市| 东城区| 青铜峡市| 开封市| 都匀市| 霞浦县| 大姚县| 汝州市| 石河子市| 灵石县| 榆社县| 平和县| 东莞市| 石首市| 榆树市| 台山市| 兴隆县| 澎湖县|