.NET本质论 类型基础
類型概述
類型是CLR程序的生成塊(building block).
CLR類型(CLR type)是命名的可重用抽象體.
CLR類型定義由零個或多個成員(member)組成.類型的成員控制類型如何使用.以及類型如何工作.類型的每個成員都有自己的訪問修飾符(access modifier)控制對于成員的訪問.類型的可訪問成員會被經常引用,組合在一起就是類型的合同(contract).
除了控制對給定成員的訪問,開發人員還能夠控制類型的實例是否需要訪問該成員.多數成員能被定義為按實例(per instance)或按類型(per type)訪問.按實例訪問成員(per-instance member)需要通過這個類型的實例才能訪問.按類型訪問成員(per-type member)則沒有這種要求.
CTS有三種基本類型的成員:字段,方法和嵌套類型.字段是一個命名的存儲單元,它隸屬于所聲明的類型.方法是一個命名的操作,它可以被調用和執行.嵌套類型則是一種簡單的輔助類型,它被定義為聲明類型的實現的一部分.其他類型成員(例如:屬性,事件)是以附加元數據的形式出現的方法(屬性和事件實際上也是方法).
類型的字段控制內存如何分配.CLR使用類型的字段來決定分配多少內存給這個類型.CLR會給static字段分配一次內存:即在類型被首次加載的時候.CLR在每次分配類型實例時,都會為non-static(instance)[非靜態(實例)]字段分配內存.在分配內存時,CLR初始化所有的static字段,并且為它們賦予默認值.對于數值類型,默認值是零,對于布爾類型,默認值是false.對于對象引用,默認值是null.CLR也會初始化堆分配的(heap-allocated)實例字段,同樣賦予上述默認值.
CLR保證static字段和堆分配(heap-allocated)實例字段的初始化狀態.CLR將把局部變量分配在堆棧中.
就customerCount來說,類型被首次使用之前內存會分配和初始化.對于其他字段,每當新的AcmCorp.LOB實例被分配在堆上時,內存都會被分配和初始化.
默認情況下,確切的內存布局是不透明的.CLR將使用虛擬的內存布局,并且經常會重新排序字段以優化訪問和使用,如圖3.1所示.注意,聲明的順序是:isGoodCustomer,lastName,banlance,extra和firstInitial.如果CLR以類型聲明的順序布局字段,它將不得不在字段間插入空間量(padding),以避免對個別字段的不對齊訪問--這將會影響性能.為了避免這點,CLR對字段重新排序以便不再有不必要的空間量.因此,在作者的32位IA-32機器上,這意味著最終采用的順序是:balance,lastName,firstInitial,isGoodCustomer和extra.這種布局的結果是取消不必要的空間量,并能很好地對齊數據.然而,CLR確切的布局策略并沒有正式的文檔,并且,對于不同版本的CLR也不可能只依賴某一種特定的策略.
CLR提供了兩種將字段聲明為常量值的方式.第一種方式所適用的字段,它的常量值是在編譯時計算的--這是效率最高的:字段的靜態值僅僅作為一個字面值存儲在類型的元數據模塊中,在運行時它并不是一個真正的字段.準確地說,編譯器需要內聯任何到字面字段的訪問,從本質上講,它是將字面值嵌入到指令流中.在C#中聲明字面字段,必須使用const關鍵字.這還需要一個初始化表達式,使得它的值能夠在編譯時計算出來.
任何試圖修改這個字段的做法都將作為編譯時錯誤被捕獲
對于第二種方式,CLR允許程序員將字段聲明為不變的(immutable),它將一個字段聲明為initonly,并動態地初始化.如果將initonly特性應用到一個字段,那么,一旦構造函數執行完畢,就不允許再對字段值修改.在C#種要指定一個initonly字段,就必須使用readonly關鍵字.
注意,這段代碼動態地生成了created字段的初始化值,它是基于當前時間的.也就是說,在新的實例構造函數執行完畢后,假如created的值被設置,就不能再改變它
類型和初始化
在討論類型成員之前,有兩個方法需要引起特別關注.類型允許提供一個特別方法,在它首次被初始化時調用.這個類型初始化器是一個簡單的靜態方法,它有一個眾所周知的名字(.cctor).一個類型最多只有一個類型初始化器,它沒有參數和返回值,也不能被直接調用.它們是被CLR作為類型初始化的一部分自動調用的.
這段代碼語義等價于下面的類型定義,它使用了C#字段初始化表達式,而不是顯式的類型初始化器
對于這兩種情況,作為結果的CLR類型都將有一個類型初始化器.在前一種情況下,你可以把任意語句放到初始化器中.而對于后一種情況.則只能用初始化表達式.但在這兩種情況下,最后的結果類型都會有同樣.cctor方法,并且,t字段在被訪問之前就已被初始化了.
根據這個類型定義,字段將以這個順序進行初始化:t2,t3,t1
至于類型初始化器實際運行的時機,CLR將靈活處理.類型初始化器總是保證在首次訪問類型的靜態字段之前執行.除此之外,CLR還支持兩種策略:默認策略是在首次訪問類型的任何成員之前,執行類型初始化器;第二種策略(通過beforefieldinit元數據特性標明)給予CLR更大的靈活性.標記為beforefieldinit的類型與沒有標記的類型在兩個方面是不同的.其一,在第一個成員被訪問前,CLR將充分擁有調用類型初始化器的主動權;其二,CLR會推遲對于類型初始化器的調用,直到有一個靜態字段被首次訪問之時.這意味著在beforefieldinit類型上調用靜態方法,并不保證類型初始化器會執行.它同時說明,在類型初始化器執行以前,就可以自由地創建實例并使用它.也就是說,CLR將保證在任何方法使用到一個靜態字段之前,執行類型初始化器
C#編譯器會在所有缺乏顯式類型初始化器方法的類型上設置一個beforefieldinit特性,而帶有顯式的類型初始化器方法的類型將不會被設置這個元數據特性.在靜態字段聲明中存在初始化表達式,將不會影響C#編譯器是否使用beforefieldinit特性.
當類型的實例每次被分配時,CLR將自動調用另外一個不同的方法.這個方法被稱為構造函數(constructor),并有一個截然不同的名字.ctor.構造函數不像類型初始化器,它可以接收它想要的參數.此外,它還能夠使用方法重載的規則,即一個類型可以提供多個重載的構造函數方法.不帶任何參數的構造函數被稱為類型的默認構造函數.為了授予或禁止對個別成員的訪問,構造函數方法還可以使用訪問修飾符,它們與字段或者標準的方法使用的修飾符是一樣的.這與類型初始化方法有很大不同,類型初始化方法總是private.
C#編譯器將在所生成的.ctor方法中,在顯示的方法體之前,插入non-static字段初始化表達式.就默認構造函數來說,t2和t3初始化語句會在t1的初始化語句之前.
C#編譯器還支持鏈式(chaining)構造函數,允許一個構造函數調用另一個構造函數.
類型和接口
我們經常需要根據兩個或更多的類型所設的公共假設將類型劃分成不同的類別.這種歸類相當于類型的附加文檔,因為只有顯示地聲明屬于這個類別的類型.才被認為是可以共享該類別中所隱含的假設.在CLR中,將這些類型的類別稱為接口(interface).接口是整合到類型系統中的類型歸類.因為接口代表的類別自身就是類型,所以,你可以聲明字段(變量及方法參數)來獲取類別的從屬關系,而不是對要用到的實際的具體類型進行硬編碼(hard-code).這種松散的要求允許在實現上的可替代性,它是多態(polymorphism)的基石
從結構上說,接口是CLR的另外一種類型.接口有類型名,可以有成員,其限制條件就是它既不能有實例字段,也不能有帶實現的實例方法.從結構上說,接口與其他類型的真正區別是,在類型的元數據上是否存在interface特性.在CLR中使用接口的語義是特別規定的.
接口是形成分類或類型家族的抽象類型.對接口類型的變量,字段和參數進行聲明是合法的.但實例化一個僅僅基于接口的對象是不合法的.更進一步地說,接口類型的變量,字段和參數必須引用一個具體類型的實例.而這個具體實例則必須顯式地被聲明與該接口兼容
一個類型聲明兼容多個接口是合法的.當一個具體類型(例如,一個類)聲明兼容多個接口時,就說明這個類型的實例可在多個上下文中切換.
接口對還能夠將顯式的要求強加于兼容它的類型上,特別是包含抽象方法聲明(abstract method declaration)的接口.這些方法聲明相當于對支持該接口的所有類型的要求.如果一個具體類型聲明兼容接口I,那么,這個具體類型必須提供接口I中的所有抽象方法的實現
類型和基類型
除了用多重接口聲明兼容性(也就是繼承),一個類型還可以指定最多一個基類型.基類型不能是接口,而且嚴格來說,它所支持的接口集也不能被認為是聲明類型的基類型.此外,接口本身沒有基類型.準確地說,一個接口最多有一組所支持接口,這和具體類型一樣.
沒有指定基類型的非接口類型將使用System.Object作為它們的基類型.有時基類型將從CLR觸發不同的運行時語義(例如,引用類型與值類型,按引用封送,委托).基類型也能用于將通用成員打包為單個類型,這樣能夠為多個類型所支持.當定義一個類型時,你可以控制該類型是否作為基類型使用.假如將類型聲明為sealed,將會禁止將它作為基類型使用.另一方面,假如聲明為abstract,那么不允許直接實例化該類型,它的用處僅限作為基類型.接口類型總是隱式的abstract.如果一個類型既不是abstract,也不是sealed,那么,程序員便可以把它當作基類型使用,也可以實例化為新的對象.不是abstract的類型經常作為具體(concrete)類型被引用.
就跨程序集可訪問性而言,基類型的non-private成員成為派生類型的合同的一部分.派生類型的方法能夠訪問基類型的non-private成員,就如同它們是派生類型中被顯示聲明.派生類型中的成員名可能會與基類型中的non-private成員名發生沖突(不論是偶爾的還是精心設計的).如果發生這種情況,那么在派生類型中,這兩種成員都會存在.如果這個成員是static,則可以用類型名區分.如果成員是non-static,就可以使用語言相關的關鍵字(如this或者base)進行限定,要么選擇派生類型成員,要么選擇基類型成員.
當基類型和派生類型存在同名的方法時,CLR支持兩種基本的策略:按名字的隱藏(hide-by-name)和按簽名隱藏(hide-by-signature).通過在派生類型的方法上添加或者不添加hidebysig元數據特性,從而指明方法的聲明將采取那種策略.當使用按簽名隱藏(hide-by-signature)聲明方法時,只有名字相同和簽名相同的基類型的方法將被隱藏.對于基類型中的其他同名方法,則在派生類型的合同中是可見的.相比之下,當使用按名字隱藏(hide-by-name)聲明方法時,派生類型的方法隱藏了基類型的所有同名方法,而不在乎它們的簽名.用C++定義的類型默認情況是按名字隱藏的,因為這是C++語言最初定義的方式.用C#定義的類型卻不同,它們總是使用按簽名隱藏.用VB.NET定義的類型可以采用這兩種策略中的任何一個,具體取決于該方法是使用了Overloads(按簽名隱藏)關鍵字,還是Shadows(按名字隱藏)關鍵字
關于派生類型的構造函數和基類型的構造函數是如何協同工作的,C#語言有著自己的解決之道,如圖3.5所示.在面對帶初始化表達式的實例字段的聲明時,編譯器產生的.ctor會首先以聲明的順序調用所有的字段初始化器.一旦派生類型的字段初始化器被調用,派生類型構造函數將使用程序員提供的參數調用基類型構造函數(如果使用base構件).如果基類型構造函數完成執行,派生類型構造函數會繼續執行構造函數的主體(例如,花括號中構造函數的部分).這意味著當基類型的構造函數執行時,派生類型的構造函數的主體還沒有開始執行.
?
轉載于:https://www.cnblogs.com/revoid/p/6666482.html
總結
以上是生活随笔為你收集整理的.NET本质论 类型基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: react-dnd 拖拽
- 下一篇: 算法之大数加减乘除