.NET中的特殊類型成員
2024-07-10 12:59:59
供稿:網(wǎng)友
 
.net中的特殊類型成員
 
----微軟 .net平臺系列文章之三
 
譯文/趙湘寧
 
 在前面的兩篇文章中,我們研究了類型的基礎(chǔ)知識。本文我們將考察類型能定義的某些特殊成員。在大力簡化處理類型及其對象實例需要的語法方面,這些類型有助于面向?qū)ο笤O(shè)計。
類型構(gòu)造器
 你已經(jīng)熟悉了什么是構(gòu)造器,它負(fù)責(zé)對象實例狀態(tài)的初始化。除了實例構(gòu)造器以外,microsoft(r).net公共語言運行時(clr)還支持類型構(gòu)造器(也叫做靜態(tài)構(gòu)造器、類構(gòu)造器或類型初始化)。類型構(gòu)造器可被應(yīng)用到接口,類和數(shù)值類型。它允許任何在類型中聲明的成員被訪問之前實現(xiàn)必要的初始化。類型構(gòu)造器不需要參數(shù)并且總是返回void類型。類型構(gòu)造器只訪問類型的靜態(tài)字段并且其通常的目的是初始化這些字段。在類型的任何實例被創(chuàng)建之前以及類型的任何靜態(tài)字段或方法被引用之前,必須要保證已經(jīng)運行了類型構(gòu)造器。
許多語言(包括c#)在定義類型時都自動產(chǎn)生類型構(gòu)造器。但是某些語言需要顯式(手工)實現(xiàn)類型構(gòu)造器。
為了理解類型構(gòu)造器,讓我們研究一下列在c#中定義的類型:
class atype {
static int x = 5;
}
 在建立這個代碼時,編譯器自動地為產(chǎn)生atype類型構(gòu)造器。這個構(gòu)造器負(fù)責(zé)初始化靜態(tài)字段x為值5。如果你使用ildasm,很容易認(rèn)出類型構(gòu)造器方法,因為它們的名字都是.cctor(對于類構(gòu)造器而言)。
 在c#中,通過在類型中定義靜態(tài)構(gòu)造器方法,你可以自己實現(xiàn)類型構(gòu)造器。關(guān)鍵字static的使用意味著這時類型構(gòu)造器,而不是實例構(gòu)造器。下面是一個非常簡單的例子:
class atype {
static int x;
static atype() {
x = 5;
}
}
 這個類型定義與前面的相同。注意類型構(gòu)造器決不能試圖創(chuàng)建自己的類型實例,而且構(gòu)造器也不能引用類型的非靜態(tài)成員。
最后,如果你用c#編譯器編譯下列代碼,它產(chǎn)生單獨的類型構(gòu)造器方法:
class atype {
static int x = 5;
static atype() {
x = 10;
}
}
 這個構(gòu)造器首先初始化x=5,然后,初始化x=10。換句話說,編譯器產(chǎn)生的結(jié)果類型構(gòu)造器首先包含靜態(tài)字段的初始化代碼,隨后是類型構(gòu)造器的代碼。
屬性
 許多類型定義的屬性可以被重新獲得或修改。這些屬性常常都是用類型字段成員來實現(xiàn)的。例如,下面是包含有兩個字段的類型定義:
class employee {
public string name;
public int32 age;
}
如果創(chuàng)建這個類型的實例,那么很容易用以下代碼得到或設(shè)置屬性:
employee e = new employee();
e.name = "jeffrey richter"; // 設(shè)置名字屬性
e.age = 36; // 設(shè)置年齡屬性
console.writeline(e.name); // 顯示 "jeffrey richter"
 用這種方式使用屬性非常普通。但以我的觀點看,上述代碼不會向列出的那樣被實現(xiàn)。面向?qū)ο笤O(shè)計和編程的立約之一便是數(shù)據(jù)抽象。它的意思就時類型字段不能用公共字段暴露出來,因為它太容易被修改,太容易讓人寫出不恰當(dāng)?shù)厥褂眠@個字段的代碼,從而破壞對象的狀態(tài)。例如,某人很容易編寫下面的代碼破壞employee對象:
e.age = -5; //人的年齡怎么會是-5呢?
所以說,在設(shè)計類型時,我強(qiáng)烈建議所有字段都是私有的(private)或至少是受保護(hù)的(protected)——決不要公共的(public)。然后,讓使用類型的人能get或set屬性,專門為此提供方法。打包對字段的訪問的方法就叫做存取器(或訪問器方法)方法。這些方法能隨時實現(xiàn)完整性檢查并保證對象的狀態(tài)不被破壞。例如,我重寫了前面定義過的employee類,代碼如圖一。雖然這是一個簡單的例子,但你能從中明白抽象數(shù)據(jù)字段的巨大好處,你還能從中明白如何輕松實現(xiàn)只讀屬性,或者僅僅通過不去實現(xiàn)某個存取器方法來輕松達(dá)到只寫屬性。
 圖一中顯示的數(shù)據(jù)抽象方法有兩個缺點。第一,因為要實現(xiàn)附加的函數(shù),所以要多寫一些代碼。第二,類型的使用者現(xiàn)在必須要調(diào)用方法而不是僅僅引用單個的字段名:
e.setage(36); // updates the age
e.setage(-5); // throws an exception 
 我想,所有的人都會同意這些缺點與其優(yōu)點比起來顯得微不足道,但運行時仍然提供了一種屬性機(jī)制,多少使得第一個缺點容易忍受了,并且完全消除了第二個缺點。
 圖二中的類使用了屬性,其功能和圖一所示的類相同。正如你所看到的,屬性簡化了一些代碼,但更重要的是允許調(diào)用這項下面一樣寫自己的代碼:
.age = 36; // 更新年齡
e.age = -5; // 擲出異常throws an exception
 get屬性存取器的返回值和傳遞到set屬性存取器參數(shù)值類型相同。set屬性存取器的返回值是void,而get屬性存取器沒有入口參數(shù)。屬性可以是靜態(tài)的、虛擬的、抽象的、內(nèi)部的、私有的、保護(hù)的或公共的。另外,屬性可以在接口中定義,關(guān)于這一點將在后面討論。
 我還應(yīng)該指出屬性不必于字段關(guān)聯(lián)。例如,類型system.io.filestream定義了一個長度屬性,它返回流中的字節(jié)數(shù)。當(dāng)長度屬性的get方法被調(diào)用時,這個長度不是由字段提供,而是調(diào)用另一個函數(shù)請求底層操作系統(tǒng)返回打開文件流的字節(jié)數(shù)。
 當(dāng)你創(chuàng)建屬性時,編譯器實際上發(fā)出專門的get_proname和/或set_proname存取器方法(這里proname是屬性名)。大多數(shù)編譯器會理解這些專用方法并允許開發(fā)人員存取這些有專門屬性語法的方法。但是,遵守公共語言運行時規(guī)范的編譯器不需要完全支持屬性,只要支持專用存取方法調(diào)用即可。
 另外,對于完全支持屬性的編譯器來說,在定義和使用屬性時使用的語法稍有不同,例如帶受管擴(kuò)展的c++需要使用_property關(guān)鍵字。
索引屬性
 某些類型,如system.collections.sortedlist暴露邏輯元素列表。為了能輕松存取這種類型中的元素,可以定義一個索引屬性(也叫索引器-indexer)。圖三顯示的是一個索引屬性的例子,其索引器的的使用極其簡單:
bitarray ba = new bitarray(14);
for (int x = 0; x < 14; x++) {
// 置所有偶數(shù)位為“on”
ba[x] = (x % 2 == 0);
console.writeline("bit " + x + " is " + (ba[x] ? "on" : "off"));
}
圖三的bitarray例子中,索引器帶一個int32參數(shù):bitposition。索引器必須至少帶一個參數(shù),參數(shù)個數(shù)可以是兩個或更多。這些參數(shù)(以及返回類型)可以是任何類型。創(chuàng)建以string作為參數(shù)的索引器查找聯(lián)合數(shù)組中的值是十分普通的事情。一種類型可以提供多個索引器,只要其原型不同。
 就像set屬性,set索引器存取方法包含一個隱藏的參數(shù),值,當(dāng)存取方法被調(diào)用時,它表示想得到一個新的值。bitarray的set存取方法顯示了這個參數(shù)值的使用。
 一個設(shè)計良好的索引器應(yīng)該具備get和set兩個存取方法。即便你能只實現(xiàn)get存取方法(對于只讀語義)或者只實現(xiàn)set存取方法(對于只寫語義),建議你的索引器實現(xiàn)兩個存取器。理由很簡單,索引的使用者不希望只有半個行為。例如,當(dāng)編寫下面兩行代碼時,使用者不想看到編譯器出錯:
string s = someobj[5]; // 如果有存取器,編譯 ok 
someobj[5] = s; //如果沒有存取器,編譯出錯
 索引器總是起類型實例的作用,并且不能被聲明為靜態(tài)。但它可以是公共的、私有的、保護(hù)的或內(nèi)部的。
 當(dāng)你創(chuàng)建索引屬性時,編譯器實際上會發(fā)布專門的get_item和/或set_item存取器方法。大多數(shù)編譯器都會理解這些專門的方法并且會允許開發(fā)人員利用專門的索引屬性語法存取這些方法。但是,與cls(公共語言系統(tǒng))兼容的編譯器不需要完全支持索引屬性;只要編譯器支持專用存取器調(diào)用即可。
 同樣,對于完全支持索引屬性的編譯器在定義和使用這些屬性的時候,需要的語法稍有差別。例如,c++受管擴(kuò)展需要使用_property關(guān)鍵字。
結(jié)論
 本文中所討論的概念對于所有.net的程序員來說極其重要。我所提到的特殊的類型成員使組件成為公共語言運行時最重要的內(nèi)容。也就是說,現(xiàn)代組件被設(shè)計成支持屬性。
下一篇文章我將介紹委派和事件成員,因為它們與基于組件的應(yīng)用開發(fā)和設(shè)計是不可分割的一個整體。