天天看點

C# 類型和成員基礎以及常量、字段、屬性

在c#中,一個類型内部可以定義多種成員:常量、字段、執行個體構造器、類型構造器(靜态構造器)、方法、操作符重載、轉換操作符、屬性、事件、類型。

類型的可見性有public和internal(預設)兩種,前者定義的類型對所有程式集中的所有類型都可見,後者定義的類型隻對同一程式集内部的所有類型可見:

成員的可通路性(按限制從大到小排列):

private隻能由定義成員的類型或嵌套類型中方法通路

protected隻能由定義成員的類型或嵌套類型或派生類型中方法通路

internal 隻能由同程式集類型中方法通路

protected internal 隻能由定義成員的類型或嵌套類型或派生類型或同程式集類型中方法通路(注意這裡是或的關系)

public 可由任何程式集中任何類型中方法通路

在c#中,如果沒有顯式聲明成員的可通路性,編譯器通常預設選擇private(限制最大的那個),clr要求接口類型的所有成員都是public通路性,c#編譯器知道這一點,是以禁止顯式指定接口成員的可通路性。同時c#還要求在繼承過程中派生類重寫成員時,不能更改成員的可通路性(clr并沒有作這個要求,clr允許重寫成員時放寬限制)。

靜态類

永遠不需要執行個體化的類,靜态類中隻能有靜态成員。在c#中用static這個關鍵詞定義一個靜态類,但隻能應用于class,不能應用于struct,因為clr總是允許值類型執行個體化。

c#編譯器對靜态類作了如下限制:

靜态類必須直接從system.object派生

靜态類不能實作任何接口(因為隻有使用類的一個執行個體才能調用類的接口方法)

靜态類隻能定義靜态成員(字段、方法、屬性、事件)

靜态類不能作為字段、方法參數或局部變量使用

靜态類在編譯後,會生成一個被标記為abstract和sealed的類,同時編譯器不會生成執行個體構造器(.ctor方法)

C# 類型和成員基礎以及常量、字段、屬性

分部類、結構和接口

c#編譯器提供一個partial關鍵字,以允許将一個類、結構或接口定義在多個檔案裡。

在編譯時,編譯器自動将類、結構或接口的各部分合并起來。這僅是c#編譯器提供的一個功能,clr對此一無所知。

C# 類型和成員基礎以及常量、字段、屬性

常量就是代表一恒定資料值的符号,比如我們将圓周率3.12415926定義成名為pi的常量,使代碼更容易閱讀。而且常量是在編譯時就代入運算的(常量就是一個符号,編譯時編譯器就會将該符号替換成實際值),不會造成任何性能上的損失。但這一點也可能會造成一個版本問題,即假如未來修改了常量所代表的值,那麼用到此常量的地方都要重新編譯(我個人認為這也是常量名稱的由來,我們應該将恒定不變的值定義為常量,以免後期改動時産生版本問題)。下面的示例也驗證了這一點,test1和test2方法内部的常量運算在編譯後,就已經運算完成。

C# 類型和成員基礎以及常量、字段、屬性

從上面示例,我們還能看出一點:常量key和value編譯後是靜态成員,這是因為常量通常與類型關聯而不是與執行個體關聯,從邏輯上說,常量始終是靜态成員。但對于在方法内部定義的常量,由于作用域的限制,不可能有方法之外的地方引用到這個常量,是以在編譯後,常量被優化了。

字段是一種資料成員,在oop的設計中,字段通常是用來封裝一個類型的内部狀态,而方法表示的是對這些狀态的一些操作。

在c#中字段可用的修飾符有

static 聲明的字段與類型關聯,而不是與對象關聯(預設情況下字段與對象關聯)

readonly 聲明的字段隻能在構造器裡寫入值(可以通過反射修改)

volatile 聲明的字段為易失字段(用于多線程環境)

這裡要注意的是将一個字段标記為readonly時,不變的是引用,而不是引用的值。示例:

clr支援兩種屬性:無參屬性和有參屬性(c#中稱為索引器)。

面向對象設計和程式設計的重要原則之一就是資料封裝,這意味着字段(封裝對象的内部狀态)永遠不應該公開。是以,clr提供屬性機制來通路字段内容(vs中輸入propfull加兩次tab會為我們自動生成字段和屬性的代碼片斷)。

下面的示例中,person對象内部有一個表示年齡的字段,如果直接公開這個字段,則不能儲存外部不會将age設定為0或1000,這顯然是沒有意義的(也破壞了資料封裝性),是以通過屬性,可以在操作字段時,加一些額外邏輯,以保證資料的有效性。

編譯上述代碼後,實際上編譯器會将屬性内的get和set通路器各生成一個方法,方法名稱是get_和set_加上屬性名,是以說屬性的本質是方法。

C# 類型和成員基礎以及常量、字段、屬性

如果隻是為了封裝一個字段而建立一個屬性,c#還為我們提供了一種更簡單的文法,稱為自動實作的屬性(aip)。下面是一個示例(在vs中輸入prop加兩次tab會為我們生成aip片斷):

C# 類型和成員基礎以及常量、字段、屬性

這裡要注意一點,由于aip的支援字段是編譯器自動生成的,而且編譯器每次編譯都可能更改這個名稱。是以在任何要序列化和反序列化的類型中,都不要使用aip功能。

在實作程式設計中,我們經常構造一個對象,然後設定對象的一些公共屬性或字段。為此c#為我們提供了一種簡化的文法來完成這些操作。如下示例:

使用對象初始化器的文法時,實際上編譯器為我們生成的代碼和上面是一緻的,但是下面的代碼明顯更加簡潔。如果本來就是要調用類型的無參構造器,c#還允許我們省略大括号之前的小括号:

如果一個屬性的類型實作了ienumerable或ienumerable<t>接口,那麼這個屬性就被認為是一個集合,我們同樣類似的文法來初始化一個集合。比如我們在上例中的person類中加入一個新屬性skills

然後可以用下面的文法來初始化

這裡我們用new list<string> { "c#", "jquery" }一句來初始化了一個集合(實作上new list<string>完全可以省略,編譯器會根據屬性的類型來自動推斷集合類型),并添加了兩項紀錄。編譯器會我們生成的代碼看起來是這樣的:

前面講到的屬性都沒有參數,實作上還有一種可以帶參數的屬性,稱之為有參屬性(c#中叫索引器)。

上面的例子中,和定義無參屬性不同的是,這裡并沒有屬性名稱,而是用this[參數]的文法來定義一個有參屬性(索引器),這是c#的要求。和無參屬性不同,有參屬性還支援重載:

屬性本質是方法,有參屬性也一樣(對clr來說甚至并不分有參還是無參,對它來說都是方法的調用),那麼有參屬性的編譯後生成的il是什麼樣子呢?事實上c#對所有的有參屬性生成的il方法都預設命名為get_item和set_item。當然這是可以通過在索引器上應用system.runtime.compliserservices.indexernameattribute定制attribute來改變這一預設行為。

C# 類型和成員基礎以及常量、字段、屬性

屬性的get和set通路器是可以定義不同的通路性的,如果get和set通路器的可通路性不一緻,c#要求必須為屬性本身指定限制最小的那一個。

注意:如果同時設定get和set的通路性,會提示“不能為屬性的兩個通路器同時指定可通路性修改符”,因為對屬性或索引器使用通路修飾符受以下條件的制約:

不能對接口或顯式接口成員實作使用通路器修飾符

僅當屬性或索引器同時具有 set 和 get 通路器時,才能使用通路器修飾符。這種情況下,隻允許對其中一個通路器使用修飾符

如果屬性或索引器具有 override 修飾符,則通路器修飾符必須與重寫的通路器的通路性(如果有的話)比對

通路器的可通路性級别必須比屬性或索引器本身的可通路性級别具有更嚴格的限制

屬性沒有存儲資料的功能,資料都存在字段中,是以隻有修改字段的資料才能更改資料,修改屬性的值沒用。

<a target="_blank" href="http://www.cnblogs.com/hecool/p/3151387.html">原文位址</a>

繼續閱讀