本文我們將考察類型能定義的某些特殊成員在大力簡化處理類型及其對象實例需要的語法方面這些類型有助於面向對象設計
類型構造器
你已經熟悉了什麼是構造器它負責對象實例狀態的初始化除了實例構造器以外Microsoft(r)NET公共語言運行時(CLR)還支持類型構造器(也叫做靜態構造器類構造器或類型初始化)類型構造器可被應用到接口類和數值類型它允許任何在類型中聲明的成員被訪問之前實現必要的初始化類型構造器不需要參數並且總是返回void類型類型構造器只訪問類型的靜態字段並且其通常的目的是初始化這些字段在類型的任何實例被創建之前以及類型的任何靜態字段或方法被引用之前必須要保證已經運行了類型構造器
許多語言(包括C#)在定義類型時都自動產生類型構造器但是某些語言需要顯式(手工)實現類型構造器
為了理解類型構造器讓我們研究一下列在C#中定義的類型
class Atype {
static int x =
}
在建立這個代碼時編譯器自動地為產生Atype類型構造器這個構造器負責初始化靜態字段x為值如果你使用ILDasm很容易認出類型構造器方法因為它們的名字都是cctor(對於類構造器而言)
在C#中通過在類型中定義靜態構造器方法你可以自己實現類型構造器關鍵字static的使用意味著這時類型構造器而不是實例構造器下面是一個非常簡單的例子
class AType {
static int x
static AType() {
x =
}
}
這個類型定義與前面的相同注意類型構造器決不能試圖創建自己的類型實例而且構造器也不能引用類型的非靜態成員
最後如果你用C#編譯器編譯下列代碼它產生單獨的類型構造器方法
class AType {
static int x =
static AType() {
x =
}
}
這個構造器首先初始化x=然後初始化x=換句話說編譯器產生的結果類型構造器首先包含靜態字段的初始化代碼隨後是類型構造器的代碼
屬性
許多類型定義的屬性可以被重新獲得或修改這些屬性常常都是用類型字段成員來實現的例如下面是包含有兩個字段的類型定義
class Employee {
public String Name
public Int Age
}
如果創建這個類型的實例那麼很容易用以下代碼得到或設置屬性
Employee e = new Employee()
eName = Jeffrey Richter // 設置名字屬性
eAge = // 設置年齡屬性
ConsoleWriteLine(eName) // 顯示 Jeffrey Richter
用這種方式使用屬性非常普通但以我的觀點看上述代碼不會向列出的那樣被實現面向對象設計和編程的立約之一便是數據抽象它的意思就時類型字段不能用公共字段暴露出來因為它太容易被修改太容易讓人寫出不恰當地使用這個字段的代碼從而破壞對象的狀態例如某人很容易編寫下面的代碼破壞Employee對象
eAge = //人的年齡怎麼會是呢?
所以說在設計類型時我強烈建議所有字段都是私有的(private)或至少是受保護的(protected)——決不要公共的(public)然後讓使用類型的人能Get或Set屬性專門為此提供方法打包對字段的訪問的方法就叫做存取器(或訪問器方法)方法這些方法能隨時實現完整性檢查並保證對象的狀態不被破壞例如我重寫了前面定義過的Employee類代碼如圖一雖然這是一個簡單的例子但你能從中明白抽象數據字段的巨大好處你還能從中明白如何輕松實現只讀屬性或者僅僅通過不去實現某個存取器方法來輕松達到只寫屬性
顯示的數據抽象方法有兩個缺點第一因為要實現附加的函數所以要多寫一些代碼第二類型的使用者現在必須要調用方法而不是僅僅引用單個的字段名
eSetAge() // Updates the age
eSetAge() // Throws an exception
我想所有的人都會同意這些缺點與其優點比起來顯得微不足道但運行時仍然提供了一種屬性機制多少使得第一個缺點容易忍受了並且完全消除了第二個缺點
類使用了屬性其功能和上面所示的類相同正如你所看到的屬性簡化了一些代碼但更重要的是允許調用這項下面一樣寫自己的代碼
Age = // 更新年齡
eAge = // 擲出異常Throws an exception
Get屬性存取器的返回值和傳遞到Set屬性存取器參數值類型相同Set屬性存取器的返回值是void而Get屬性存取器沒有入口參數屬性可以是靜態的虛擬的抽象的內部的私有的保護的或公共的另外屬性可以在接口中定義關於這一點將在後面討論
我還應該指出屬性不必於字段關聯例如類型SystemIOFileStream定義了一個長度屬性它返回流中的字節數當長度屬性的Get方法被調用時這個長度不是由字段提供而是調用另一個函數請求底層操作系統返回打開文件流的字節數
當你創建屬性時編譯器實際上發出專門的get_ProName和/或set_ProName存取器方法(這裡ProName是屬性名)大多數編譯器會理解這些專用方法並允許開發人員存取這些有專門屬性語法的方法但是遵守公共語言運行時規范的編譯器不需要完全支持屬性只要支持專用存取方法調用即可
另外對於完全支持屬性的編譯器來說在定義和使用屬性時使用的語法稍有不同例如帶受管擴展的C++需要使用_property關鍵字
索引屬性
某些類型如SystemCollectionsSortedList暴露邏輯元素列表為了能輕松存取這種類型中的元素可以定義一個索引屬性(也叫索引器indexer)一個索引屬性的例子其索引器的的使用極其簡單
BitArray ba = new BitArray()
for (int x = x < x++) {
// 置所有偶數位為on
ba[x] = (x % == )
ConsoleWriteLine(Bit + x + is + (ba[x] ? On Off))
}
BitArray例子中索引器帶一個Int參數bitPosition索引器必須至少帶一個參數參數個數可以是兩個或更多這些參數(以及返回類型)可以是任何類型創建以String作為參數的索引器查找聯合數組中的值是十分普通的事情一種類型可以提供多個索引器只要其原型不同
就像set屬性set索引器存取方法包含一個隱藏的參數值當存取方法被調用時它表示想得到一個新的值BitArray的set存取方法顯示了這個參數值的使用
一個設計良好的索引器應該具備get和set兩個存取方法即便你能只實現get存取方法(對於只讀語義)或者只實現set存取方法(對於只寫語義)建議你的索引器實現兩個存取器理由很簡單索引的使用者不希望只有半個行為例如當編寫下面兩行代碼時使用者不想看到編譯器出錯
String s = SomeObj[] // 如果有存取器編譯 OK
SomeObj[] = s //如果沒有存取器編譯出錯
索引器總是起類型實例的作用並且不能被聲明為靜態但它可以是公共的私有的保護的或內部的
當你創建索引屬性時編譯器實際上會發布專門的get_Item和/或set_Item存取器方法大多數編譯器都會理解這些專門的方法並且會允許開發人員利用專門的索引屬性語法存取這些方法但是與CLS(公共語言系統)兼容的編譯器不需要完全支持索引屬性只要編譯器支持專用存取器調用即可
同樣對於完全支持索引屬性的編譯器在定義和使用這些屬性的時候需要的語法稍有差別例如C++受管擴展需要使用_property關鍵字
結論
本文中所討論的概念對於所有NET的程序員來說極其重要我所提到的特殊的類型成員使組件成為公共語言運行時最重要的內容也就是說現代組件被設計成支持屬性
From:http://tw.wingwit.com/Article/program/net/201311/13534.html