常量,字段,構造方法

常量

1.什麼是常量

​ 常量是值從不變化的符號,在編譯以前值就必須肯定。編譯後,常量值會保存到程序集元數據中。因此,常量必須是編譯器識別的基元類型的常量,如:Boolean,Char,Byte,SByte,...,...,...,UInt64,Single,Double,Decimal,String。另外,C#是能夠定義非基元類型的常量的,前提是值必須爲null。c#

public sealed class SomeType
{
    public const SomeType Empty=null;
}

 

2.常量的特性

  • 常量成員將建立元數據,它是直接嵌入在代碼內部,運行時不須要額外分配內存。安全

  • 常量被視爲靜態成員,而不是實例成員。ide

  • 不能獲取常量的地址函數

  • 不能以引用的方式傳遞常量性能

  • 參考上面的特性,若是跨程序引用,嘗試改變常量初始值,不只dll須要從新編譯,引用者也須要編譯spa

字段

1.什麼是字段

字段是一種數據成員,它能夠是值類型的實例也能夠是引用類型的引用。線程

CLR支持類型字段和實例字段,什麼是類型字段?它其實就是咱們熟悉的靜態字段,實例字段就是非靜態字段。3d

1.1類型字段(靜態字段)的內存分配過程

類型對象(靜態對象)是在類型加載到一個AppDomain時建立的,而所需內存也是在內型對象中分配的。指針

接着上面的問題,那麼,何時將類型加載到AppDomain中內?當第一次對引用到該類型的方法進行JIT編譯時,code

1.2實例字段的內存分配過程

實例字段的內存,是在構造容納字段的類型進行實例構造時分配的。

2.字段特性

字段存儲在動態內存中,它不像常量,因此只能在程序運行時,纔可以獲取到它的值。字段能夠是任何類型,不像常量有類型上的限制。

2.1字段修飾符

Static static 指定字段爲類型的一部分,而不是對象的一部分
Instance 默認 指定字段與實例關聯,而不是和類自己關聯
InitOly readonly 只能在構造器方法中進行值的寫入,不然只讀
Volatile volatile 表示,編譯器和CLR以及硬件,不會對這種字段標識的代碼執行「線程不安全的措施」,只有CLR中的基元類型能使用這個修飾符。

2.2 readonly和read/write

一般,字段都是read/write,便可讀可寫的,這也意味着,字段的值會隨着運行可能發生值得變化。而當你把字段標記爲readonly,那麼你就只能在構造函數中,對它進行賦值,編譯器是不會容許你在構造器(構造函數)覺得的任何方法寫入值,或變動值。

固然,C#提供了一種內聯初始化的語法糖來進行readonly值的初始化,這種語法也能夠對常量和其餘形式的字段進行賦值。

public readonly int =250;

固然,使用內聯語法,而不是在構造器中構造,濫用的話可能會有一些性能問題(代碼膨脹等)。

構造方法

實例構造器(引用類型)

什麼是構造器?

構造器是將類型的實例初始化到良好狀態的特殊方法。在「方法定義元數據表」中始終叫.ctor(constructor的簡稱)。

引用類型在內存中如何實例化?

首先爲實例的數據字段分配內存空間,而後是爲初始化對象的附加字段(沒錯,就是咱們常常會提到的同步塊索引和類型對象指針)分配內存,而後最後開闢一個空間來調用實例構造函數進行對象的初始化。

在調用構造器以前,爲對象分配的內存老是先被歸零,爲了保證那些被構造器顯示重寫的字段都得到0或者null的值。

 

實例構造器的特性:

  • 實例構造器永遠不能被繼承,類必須執行本身的構造函數。若是沒有,系統默認會構造一個無參的。

  • 因此,實例構造器不能用new ,override,sealed和abstract修飾

  • 若是類的修飾符爲abstract,那麼構造器可訪問性默認爲protected,不然默認爲public。

  • 若是基類沒有提供無參構造函數(意味着顯示的實現了有參的構造函數),那麼派生類必須顯示調用一個基類的構造器(及爲了保證參數一致),不然編譯報錯。

  • static(sealed和abstract)修飾的類,編譯器不會爲它生成默認的構造函數

  • 一般狀況下,不管如何實例化派生類,基類的構造函數必定會被調用,因此object的構造函數必定會被先調用,可是實時上它什麼也不會幹。

  • 極少數狀況下,對象實例不會調用構造函數。如,Object的MemberwiseClone方法,它是用來分配內存,初始化對象的附加字段的,而後將源對象的字節數據複製到新對象中。

  • Notice:不要在構造器中調用虛方法。由於,假如被實例化的類型重寫了虛方法,就會執行派生類型中的實現,但這個時候,倒是沒有初始化的,因此,容易致使沒法預測的行爲。

內聯語法(在字段一節提到過)方式實現初始化實例字段,其實也是轉換成構造器方法中的代碼來實現。

實例構造器(值類型)

CLR是容許值類型建立實例,可是c#編譯器是不會默認爲值類型構建構造函數的,而且值類型構造器必須顯示調用才執行。如上面所說,即便你本身定義了一個構造函數,無論它是有參仍是無參,編譯器都不會去自動調用它,若是你想執行,必須本身顯示進行調用。

然而,上面說那麼多,在C#中,編譯器根本不容許你定義值類型的無參構造函數,它會報:error CS0568:結構不能包含顯示的無參構造函數。

同理,你不能對值類型的字段成員進行內聯賦值,由於內聯語句其實是經過構造器進行賦值,以下面的代碼:

internal struct SomeValType
{
    private int m=5;
}

 

上面的代碼,會報:結構中不能有實例字段初始值設定項。

因此,值類型的字段老是被初始化爲0或null,由於沒有真正意義上的構造函數爲它初始化其餘值,只有你手動去調用構造函數(因此這裏咱們不理解爲初始化)。

當你提供一個有參構造函數時,你須要爲全部的字段進行賦值,不然會報:error CS0171:在控制返回到調用方法以前,字段XXX必須徹底賦值。

類型構造器(靜態構造器)

什麼是類型構造器?

實例構造器是爲了讓類的實例有一個良好的可驗證的初始值。而類型構造器是爲靜態類型服務,顧名思義,類型構造器則是爲了讓類型有良好的初始狀態。

類型構造器特徵

  • 默認沒有構造函數

  • (類型)靜態構造器永遠不能有參數

  • 必須標記爲static,由於靜態類型的成員必須爲靜態成員

  • 不能賦予任何訪問修飾符,默認爲隱式類型,C#默認爲private

  • 類型構造器中的代碼只能訪問類型的靜態字段(常規用途就是初始化這些字段)

類型構造器的調用過程

類型構造器調用過程大體以下:

JIT編譯器在編譯到一個靜態方法時,會查看引用了哪些靜態類型。若是這個靜態類型定義了一個構造函數,JIT編譯器會檢查當前AppDomain,是否已經執行過了這個類型構造器。若是已經執行過,就不添加對它的調用。若是從未執行過,JIT編譯器會在它的本機代碼中添加對類型構造器的調用。

重要的是:爲何靜態類型的特性是十分適合作單例呢?由於CLR經常是確保每個AppDomain中,一個類型構造器都只執行一次,那麼上述的機制不足以很好的支撐這個特性,由於,多個線程下如何保證呢?爲了保證這一點,調用類型構造器時,每個調用線程都會獲取一個互斥線程同步鎖,在這樣的機制下,若是多個線程試圖同時調用某個類型的靜態構造器,只有一個線程能夠得到鎖,其餘的線程會被阻塞。只有第一個線程會執行靜態構造器的代碼。當一個線程離開構造器後,正在等待的線程纔會被喚醒,後面的線程會發現,類型構造器已經被執行過了,將直接從構造方法返回。這樣就能確保不會被再次調用。而且以上是線程安全的。

因此,單例模式就是藉助上面的特性,你想構建的單例對象,則也應該放到類型構造器中進行初始化。

注意:值類型中也能夠定義類型(靜態)構造器,可是是不推薦這麼作的,由於有時候CLR有時不會調用值類型的靜態類型構造器。

 
internal struct StructValType
{
    //雖然值類型的構造函數必須有參數,可是這個是靜態構造函數,因此它是必定沒有參數的,也不用遵照,必須初始化全部成員的值
    static StructValType()
    {
        Console.WriteLine("我會出現嗎?");
    }
    public int x;
}
​
    class BaseClass
    {
        public string ClassName { get; set; }
​
         static BaseClass()
        {
            Console.WriteLine("I'm BaseClass static Constructor without param");
        }
​
        public BaseClass()
        {
            Console.WriteLine("I'm BaseClass Constructor without param");
        }
    }

 

上述代碼,BaseClass中和StructValType中都有static構造函數,再對兩個類進行實例時,你能夠發現值類型的靜態函數是沒有被調用的。

注意:單個線程中,兩個類型構造器包含互相引用的代碼可能出問題,由於你沒法把握二者的實現順序,也就沒法保證能正確的引用。由於是CLR負責類型構造器的調用,因此不能要求以特定的順序調用類型構造器。

若是,類型構造器拋出未處理的異常,CLR會認爲類型不可用。試圖訪問該類型的任何字段和方法都會拋出System.TypeInitializationException異常。

相關文章
相關標籤/搜索