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