《.net編程先鋒C#》第五章 類
2024-07-10 13:00:23
供稿:網(wǎng)友
第五章 類
前一章討論了數(shù)據(jù)類型和它們的用法。現(xiàn)在我們轉(zhuǎn)移到c#中至關(guān)重要的結(jié)構(gòu)——類。沒(méi)有了
類,就連簡(jiǎn)單的c#程序都不能編譯。這一章假定你知道了一個(gè)類的基本組成部分:方法、屬性、構(gòu)
造函數(shù)和析構(gòu)函數(shù)。 c#在其中增加了索引和事件。
在這一章中,你學(xué)到下列有關(guān)類的話題。
。 使用構(gòu)造函數(shù)和析構(gòu)函數(shù)
。給類寫方法
。給一個(gè)類增加屬性存取標(biāo)志
。實(shí)現(xiàn)索引
。創(chuàng)建事件并通過(guò)代表元為事件關(guān)聯(lián)客戶
。應(yīng)用類、成員和存取修飾符。
5.1 構(gòu)造函數(shù)和析構(gòu)函數(shù)
在你可以訪問(wèn)一個(gè)類的方法、屬性或任何其它東西之前, 第一條執(zhí)行的語(yǔ)句是包含有相
應(yīng)類的構(gòu)造函數(shù)。甚至你自己不寫一個(gè)構(gòu)造函數(shù),也會(huì)有一個(gè)缺省的構(gòu)造函數(shù)提供給你。
class testclass
{
public testclass(): base() {} // 由編譯器提供
}
一個(gè)構(gòu)造函數(shù)總是和它的類名相同,但是,它沒(méi)有聲明返回類型??傊?,構(gòu)造函數(shù)總是
public的,你可以用它們來(lái)初始化變量。
public testclass()
{
// 在這給變量
// 初始化代碼等等。
}
如果類僅包含靜態(tài)成員(能以類型調(diào)用,而不是以實(shí)例調(diào)用的成員),你可以創(chuàng)建一個(gè)
private的構(gòu)造函數(shù)。
private testclass() {}
盡管存取修飾符在這一章的后面將要大篇幅地討論,但是private意味著從類的外面不可能
訪問(wèn)該構(gòu)造函數(shù)。所以,它不能被調(diào)用,且沒(méi)有對(duì)象可以自該類定義被實(shí)例化。
并不僅限于無(wú)參數(shù)構(gòu)造函數(shù)——你可以傳遞初始參數(shù)來(lái)初始化成員。
public testclass(string strname, int nage) { ... }
作為一個(gè)c/c++程序員,你可能習(xí)慣于給初始化寫一個(gè)附加的方法,因?yàn)樵跇?gòu)造函數(shù)中沒(méi)有
返回值。當(dāng)然,盡管在c#中也沒(méi)有返回值,但你可以引發(fā)一個(gè)自制的異常,以從構(gòu)造函數(shù)獲得返回
值。更多有關(guān)異常處理的知識(shí)在第七章 "異常處理"中有討論。
但是,當(dāng)你保留引用給寶貴的資源,應(yīng)該想到寫一個(gè)方法來(lái)解決:一個(gè)可以被顯式地調(diào)用來(lái)
釋放這些資源。問(wèn)題是當(dāng)你可以在析構(gòu)函數(shù)(以類名的前面加"~"的方式命名)中做同樣的事情時(shí),為
何還要寫一個(gè)附加的方法.
public ~testclass()
{
// 清除
}
你應(yīng)該寫一個(gè)附加方法的原因是垃圾收集器,它在變量超出范圍后并不會(huì)立即被調(diào)用,而僅當(dāng)
間歇期間或內(nèi)存條件滿足時(shí)才被觸發(fā)。當(dāng)你鎖住資源的時(shí)間長(zhǎng)于你所計(jì)劃的時(shí)間時(shí),它就會(huì)發(fā)生。
因此,提供一個(gè)顯式的釋放方式是一個(gè)好主意,它同樣能從析構(gòu)函數(shù)中調(diào)用。
public void release()
{
// 釋放所有寶貴的資源
}
public ~testclass()
{
release();
}
調(diào)用析構(gòu)函數(shù)中的釋放方法并不是必要的——總之,垃圾收集會(huì)留意釋放對(duì)象。但沒(méi)有忘記清
除是一種良好的習(xí)慣。
5.2 方法
既然對(duì)象能正確地初始化和結(jié)束,所剩下來(lái)的就是往類中增加功能。在大多數(shù)情況下,功能的
主要部分在方法中能得到實(shí)現(xiàn)。你早已見過(guò)靜態(tài)方法的使用,但是,這些是類型(類)的部分,不是
實(shí)例(對(duì)象)。
為了讓你迅速入門,我把這些方法的煩瑣問(wèn)題安排為三節(jié):
。方法參數(shù)
。改寫方法
。方法屏蔽
5.2.1 方法參數(shù)
因方法要處理更改數(shù)值,你多多少少要傳遞值給方法,并從方法獲得返回值。以下三個(gè)部分涉及
到由傳遞值和為調(diào)用者獲取返回結(jié)果所引起的問(wèn)題。
。輸入?yún)?shù)
。引用參數(shù)
。輸出參數(shù)
5.2.1.1 輸入?yún)?shù)
你早已在例子中見過(guò)的一個(gè)參數(shù)就是輸入?yún)?shù)。你用一個(gè)輸入?yún)?shù)通過(guò)值傳遞一個(gè)變量給一個(gè)方
法——方法的變量被調(diào)用者傳遞進(jìn)來(lái)的值的一個(gè)拷貝初始化。清單5.1 示范輸入?yún)?shù)的使用。
清單 5.1 通過(guò)值傳遞參數(shù)
1: using system;
2:
3: public class squaresample
4: {
5: public int calcsquare(int nsidelength)
6: {
7: return nsidelength*nsidelength;
8: }
9: }
10:
11: class squareapp
12: {
13: public static void main()
14: {
15: squaresample sq = new squaresample();
16: console.writeline(sq.calcsquare(25).tostring());
17: }
18: }
因?yàn)槲覀鬟f值而不是引用給一個(gè)變量,所以當(dāng)調(diào)用方法時(shí)(見第16行),可以使用一個(gè)常量表達(dá)式
(25)。整型結(jié)果被傳回給調(diào)用者作為返回值,它沒(méi)有存到中間變量就被立即顯示到屏幕上 。
輸入?yún)?shù)按c/c++程序員早已習(xí)慣的工作方式工作。如果你來(lái)自vb,請(qǐng)注意沒(méi)有能被編譯器處理
的隱式byval或byref——如果沒(méi)有設(shè)定,參數(shù)總是用值傳遞。
這點(diǎn)似乎與我前面所陳述的有沖突:對(duì)于一些變量類型,用值傳遞實(shí)際上意味著用引用傳遞。
迷惑嗎? 一點(diǎn)背景知識(shí)也不需要:com中的東西就是接口,每一個(gè)類可以擁有一個(gè)或多個(gè)接口。一個(gè)
接口只不過(guò)是一組函數(shù)指針,它不包含數(shù)據(jù)。重復(fù)該數(shù)組會(huì)浪費(fèi)很多內(nèi)存資源;所以,僅開始地址
被拷貝給方法,它作為調(diào)用者,仍然指向接口的相同指針。那就是為什么對(duì)象用值傳遞一個(gè)引用。
5.2.1.2 引用參數(shù)
盡管可以利用輸入?yún)?shù)和返回值建立很多方法,但你一想到要傳遞值并原地修改它(也就是在相
同的內(nèi)存位置),就沒(méi)有那么好運(yùn)了。這里用引用參數(shù)就很方便。
void mymethod(ref int ninout)
因?yàn)槟銈鬟f了一個(gè)變量給該方法(不僅僅是它的值),變量必須被初始化。否則,編譯器會(huì)報(bào)警。
清單 5.2 顯示如何用一個(gè)引用參數(shù)建立一個(gè)方法。
清單 5.2 通過(guò)引用傳遞參數(shù)
1: // class squaresample
2: using system;
3:
4: public class squaresample
5: {
6: public void calcsquare(ref int none4all)
7: {
8: none4all *= none4all;
9: }
10: }
11:
12: class squareapp
13: {
14: public static void main()
15: {
16: squaresample sq = new squaresample();
17:
18: int nsquaredref = 20; // 一定要初始化
19: sq.calcsquare(ref nsquaredref);
20: console.writeline(nsquaredref.tostring());
21: }
22: }
正如所看到的,所有你要做的就是給定義和調(diào)用都加上ref限定符。因?yàn)樽兞客ㄟ^(guò)引用傳遞,你
可以用它來(lái)計(jì)算出結(jié)果并傳回該結(jié)果。但是,在現(xiàn)實(shí)的應(yīng)用程序中,我強(qiáng)烈建議要用兩個(gè)變量,一
個(gè)輸入?yún)?shù)和一個(gè)引用參數(shù)。
5.2.1.3 輸出參數(shù)
傳遞參數(shù)的第三種選擇就是把它設(shè)作一個(gè)輸出參數(shù)。正如該名字所暗示,一個(gè)輸出參數(shù)僅用于從
方法傳遞回一個(gè)結(jié)果。它和引用參數(shù)的另一個(gè)區(qū)別在于:調(diào)用者不必先初始化變量才調(diào)用方法。這
顯示在清單5.3中。
清單 5.3 定義一個(gè)輸出參數(shù)
1: using system;
2:
3: public class squaresample
4: {
5: public void calcsquare(int nsidelength, out int nsquared)
6: {
7: nsquared = nsidelength * nsidelength;
8: }
9: }
10:
11: class squareapp
12: {
13: public static void main()
14: {
15: squaresample sq = new squaresample();
16:
17: int nsquared; // 不必初始化
18: sq.calcsquare(15, out nsquared);
19: console.writeline(nsquared.tostring());
20: }
21: }
5.2.2 改寫方法
面向?qū)ο笤O(shè)計(jì)的重要原則就是多態(tài)性。不要理會(huì)高深的理論,多態(tài)性意味著:當(dāng)基類程序員已
設(shè)計(jì)好用于改寫的方法時(shí),在派生類中,你就可以重定義(改寫)基類的方法。基類程序員可以用
virtual 關(guān)鍵字設(shè)計(jì)方法:
virtual void canboverridden()
當(dāng)從基類派生時(shí),所有你要做的就是在新方法中加入override關(guān)鍵字:
override void canboverridden()
當(dāng)改寫一個(gè)基類的方法時(shí),你必須明白,不能改變方法的訪問(wèn)屬性——在這章的后面,你會(huì)學(xué)
到更多關(guān)于訪問(wèn)修飾符的知識(shí)。
除了改寫基類方法的事實(shí)外,還有另一個(gè)甚至更重要的改寫特性。當(dāng)把派生類強(qiáng)制轉(zhuǎn)換成基類
類型并接著調(diào)用虛擬方法時(shí),被調(diào)用的是派生類的方法而不是基類的方法。
((baseclass)derivedclassinstance).canboverridden();
為了演示虛擬方法的概念,清單 5.4 顯示如何創(chuàng)建一個(gè)三角形基類,它擁有一個(gè)可以被改
寫的成員方法(computearea)。
清單 5.4 改寫一個(gè)基類的方法
1: using system;
2:
3: class triangle
4: {
5: public virtual double computearea(int a, int b, int c)
6: {
7: // heronian formula
8: double s = (a + b + c) / 2.0;
9: double darea = math.sqrt(s*(s-a)*(s-b)*(s-c));
10: return darea;
11: }
12: }
13:
14: class rightangledtriangle:triangle
15: {
16: public override double computearea(int a, int b, int c)
17: {
18: double darea = a*b/2.0;
19: return darea;
20: }
21: }
22:
23: class triangletestapp
24: {
25: public static void main()
26: {
27: triangle tri = new triangle();
28: console.writeline(tri.computearea(2, 5, 6));
29:
30: rightangledtriangle rat = new rightangledtriangle();
31: console.writeline(rat.computearea(3, 4, 5));
32: }
33: }
基類triangle定義了方法computearea。它采用三個(gè)參數(shù),返回一個(gè)double結(jié)果,且具有公共訪
問(wèn)性。從triangle類派生出的是rightangledtriangle,它改寫了computearea 方法,并實(shí)現(xiàn)了自己
的面積計(jì)算公式。兩個(gè)類都被實(shí)例化,且在命名為triangletestapp的應(yīng)用類的main() 方法中得到
驗(yàn)證。
我漏了解釋第14行:
class rightangledtriangle : triangle
在類語(yǔ)句中冒號(hào)(:)表示rightangledtriangle從類 triangle派生。那就是你所必須要做的
,以讓c#知道你想把 triangle當(dāng)作rightangledtriangle的基類。
當(dāng)仔細(xì)觀察直角三角形的computearea方法時(shí),你會(huì)發(fā)現(xiàn)第3個(gè)參數(shù)并沒(méi)有用于計(jì)算。但是,利
用該參數(shù)就可以驗(yàn)證是否是“直角”。如清單5.5所示。
清單 5.5 調(diào)用基類實(shí)現(xiàn)
1: class rightangledtriangle:triangle
2: {
3: public override double computearea(int a, int b, int c)
4: {
5: const double depsilon = 0.0001;
6: double darea = 0;
7: if (math.abs((a*a + b*b - c*c)) > depsilon)
8: {
9: darea = base.computearea(a,b,c);
10: }
11: else
12: {
13: darea = a*b/2.0;
14: }
15:
16: return darea;
17: }
18: }
該檢測(cè)簡(jiǎn)單地利用了畢達(dá)哥拉斯公式,對(duì)于直角三角形,檢測(cè)結(jié)果必須為0。如果結(jié)果不為0,類
就調(diào)用它基類的 computearea來(lái)實(shí)現(xiàn)。
darea = base.computearea(a,b,c);
例子的要點(diǎn)為:通過(guò)顯式地利用基類的資格檢查,你就能輕而易舉地調(diào)用基類實(shí)現(xiàn)改寫方法。
當(dāng)你需要實(shí)現(xiàn)其在基類中的功能,而不愿意在改寫方法中重復(fù)它時(shí),這就非常有幫助。
5.2.3 方法屏蔽
重定義方法的一個(gè)不同手段就是要屏蔽基類的方法。當(dāng)從別人提供的類派生類時(shí),這個(gè)功能特
別有價(jià)值。看清單 5.6,假設(shè)baseclass由其他人所寫,而你從它派生出 derivedclass 。
清單 5.6 derived class 實(shí)現(xiàn)一個(gè)沒(méi)有包含于 base class中的方法
1: using system;
2:
3: class baseclass
4: {
5: }
6:
7: class derivedclass:baseclass
8: {
9: public void testmethod()
10: {
11: console.writeline("derivedclass::testmethod");
12: }
13: }
14:
15: class testapp
16: {
17: public static void main()
18: {
19: derivedclass test = new derivedclass();
20: test.testmethod();
21: }
22: }
在這個(gè)例子中, derivedclass 通過(guò)testmethod()實(shí)現(xiàn)了一個(gè)額外的功能。但是,如果基類的
開發(fā)者認(rèn)為把testmethod()放在基類中是個(gè)好主意,并使用相同的名字實(shí)現(xiàn)它時(shí),會(huì)出現(xiàn)什么問(wèn)題
呢?(見清單5.7)
清單 5.7 base class 實(shí)現(xiàn)和 derived class相同的方法
1: class baseclass
2: {
3: public void testmethod()
4: {
5: console.writeline("baseclass::testmethod");
6: }
7: }
8:
9: class derivedclass:baseclass
10: {
11: public void testmethod()
12: {
13: console.writeline("derivedclass::testmethod");
14: }
15: }
在優(yōu)秀的編程語(yǔ)言中,你現(xiàn)在會(huì)遇到一個(gè)真正的大麻煩。但是,c#會(huì)給你提出警告:
hiding2.cs(13,14): warning cs0114: 'derivedclass.testmethod()' hides inherited member
'baseclass.testmethod()'. to make the current method override that implementation, add
the override keyword. otherwise add the new keyword.
(hiding2.cs(13,14):警告 cs0114:'derivedclass.testmethod()' 屏蔽了所繼承的成員
'baseclass.testmethod()'。要想使當(dāng)前方法改寫原來(lái)的實(shí)現(xiàn),加上 override關(guān)鍵字。否則加上新
的關(guān)鍵字。)
具有了修飾符new,你就可以告訴編譯器,不必重寫派生類或改變使用到派生類的代碼,你的方法就
能屏蔽新加入的基類方法。清單5.8 顯示如何在例子中運(yùn)用new修飾符。
清單 5.8 屏蔽基類方法
1: class baseclass
2: {
3: public void testmethod()
4: {
5: console.writeline("baseclass::testmethod");
6: }
7: }
8:
9: class derivedclass:baseclass
10: {
11: new public void testmethod()
12: {
13: console.writeline("derivedclass::testmethod");
14: }
15: }
使用了附加的new修飾符,編譯器就知道你重定義了基類的方法,它應(yīng)該屏蔽基類方法。但是,如果
你按以下方式編寫:
derivedclass test = new derivedclass();
((baseclass)test).testmethod();
基類方法的實(shí)現(xiàn)就被調(diào)用了。這種行為不同于改寫方法,后者保證大部分派生方法獲得調(diào)用。
5.3 類屬性
有兩種途徑揭示類的命名屬性——通過(guò)域成員或者通過(guò)屬性。前者是作為具有公共訪問(wèn)性的成員變量而被實(shí)現(xiàn)的;后者并不直接回應(yīng)存儲(chǔ)位置,只是通過(guò) 存取標(biāo)志(accessors)被訪問(wèn)。
當(dāng)你想讀出或?qū)懭雽傩缘闹禃r(shí),存取標(biāo)志限定了被實(shí)現(xiàn)的語(yǔ)句。用于讀出屬性的值的存取標(biāo)志記為關(guān)鍵字get,而要修改屬性的值的讀寫符標(biāo)志記為set。在你對(duì)該理論一知半解以前,請(qǐng)看一下清單5.9中的例子,屬性squarefeet被標(biāo)上了get和set的存取標(biāo)志。
清單 5.9 實(shí)現(xiàn)屬性存取標(biāo)志
1: using system;
2:
3: public class house
4: {
5: private int m_nsqfeet;
6:
7: public int squarefeet
8: {
9: get { return m_nsqfeet; }
10: set { m_nsqfeet = value; }
11: }
12: }
13:
14: class testapp
15: {
16: public static void main()
17: {
18: house myhouse = new house();
19: myhouse.squarefeet = 250;
20: console.writeline(myhouse.squarefeet);
21: }
22: }
house類有一個(gè)命名為squarefeet的屬性,它可以被讀和寫。實(shí)際的值存儲(chǔ)在一個(gè)可以從類內(nèi)部訪問(wèn)的變量中——如果你想當(dāng)作一個(gè)域成員重寫它,你所要做的就是忽略存取標(biāo)志而把變量重新定義為:
public int squarefeet;
對(duì)于一個(gè)如此簡(jiǎn)單的變量,這樣不錯(cuò)。但是,如果你想要隱藏類內(nèi)部存儲(chǔ)結(jié)構(gòu)的細(xì)節(jié)時(shí),就應(yīng)該采用存取標(biāo)志。在這種情況下,set 存取標(biāo)志給值參數(shù)中的屬性傳遞新值。(可以改名,見第10行。)
除了能夠隱藏實(shí)現(xiàn)細(xì)節(jié)外,你還可自由地限定各種操作:
get和set:允許對(duì)屬性進(jìn)行讀寫訪問(wèn)。
get only:只允許讀屬性的值。
set only:只允許寫屬性的值。
除此之外,你可以獲得實(shí)現(xiàn)在set標(biāo)志中有效代碼的機(jī)會(huì)。例如,由于種種原因(或根本沒(méi)有原因),你就能夠拒絕一個(gè)新值。最好是沒(méi)有人告訴你它是一個(gè)動(dòng)態(tài)屬性——當(dāng)你第一次請(qǐng)求它后,它會(huì)保存下來(lái),故要盡可能地推遲資源分配。
5.4 索引
你想過(guò)象訪問(wèn)數(shù)組那樣使用索引訪問(wèn)類嗎 ?使用c#的索引功能,對(duì)它的期待便可了結(jié)。
語(yǔ)法基本上象這樣:
屬性 修飾符 聲明 { 聲明內(nèi)容}
具體的例子為
public string this[int nindex]
{
get { ... }
set { ... }
}
索引返回或按給出的index設(shè)置字符串。它沒(méi)有屬性,但使用了public修飾符。聲明部分由類型string和this 組成用于表示類的索引。get和set的執(zhí)行規(guī)則和屬性的規(guī)則相同。(你不能取消其中一個(gè)。) 只存在一個(gè)差別,那就是:你幾乎可以任意定義大括弧中的參數(shù)。限制為,必須至少規(guī)定一個(gè)參數(shù),允許ref 和out 修飾符。
this關(guān)鍵字確保一個(gè)解釋。索引沒(méi)有用戶定義的名字,this 表示默認(rèn)接口的索引。如果類實(shí)現(xiàn)了多個(gè)接口,你可以增加更多個(gè)由interfacename.this說(shuō)明的索引。
為了演示一個(gè)索引的使用,我創(chuàng)建了一個(gè)小型的類,它能夠解析一個(gè)主機(jī)名為ip地址——或一個(gè)ip地址列表(以http://www.microsoft.com為例 )。這個(gè)列表通過(guò)索引可以訪問(wèn),你可以看一下清單
5.10 的具體實(shí)現(xiàn)。
清單 5.10 通過(guò)一個(gè)索引獲取一個(gè)ip地址
1: using system;
2: using system.net;
3:
4: class resolvedns
5: {
6: ipaddress[] m_arrips;
7:
8: public void resolve(string strhost)
9: {
10: iphostentry iphe = dns.gethostbyname(strhost);
11: m_arrips = iphe.addresslist;
12: }
13:
14: public ipaddress this[int nindex]
15: {
16: get
17: {
18: return m_arrips[nindex];
19: }
20: }
21:
22: public int count
23: {
24: get { return m_arrips.length; }
25: }
26: }
27:
28: class dnsresolverapp
29: {
30: public static void main()
31: {
32: resolvedns mydnsresolver = new resolvedns();
33: mydnsresolver.resolve("http://www.microsoft.com");
34:
35: int ncount = mydnsresolver.count;
36: console.writeline("found {0} ip's for hostname", ncount);
37: for (int i=0; i < ncount; i++)
38: console.writeline(mydnsresolver[i]);
39: }
40: }
為了解析主機(jī)名,我用到了dns類,它是system .net 名字空間的一部分。但是,由于這個(gè)名字空間并不包含在核心庫(kù)中,所以必須在編譯命令行中引用該庫(kù):
csc /r:system.net.dll /out:resolver.exe dnsresolve.cs
解析代碼是向前解析的。在該 resolve方法中,代碼調(diào)用dns類的靜態(tài)方法gethostbyname,它返回一個(gè)iphostentry對(duì)象。結(jié)果,該對(duì)象包含有我要找的數(shù)組——addresslist數(shù)組。在退出resolve 方法之前,在局部的對(duì)象實(shí)例成員m_arrips中,存儲(chǔ)了一個(gè)addresslist array的拷貝(類型ipaddress 的對(duì)象存儲(chǔ)在其中)。
具有現(xiàn)在生成的數(shù)組 ,通過(guò)使用在類resolvedns中求得的索引,應(yīng)用程序代碼就可以在第37至38行列舉出ip地址。(在第6章 "控制語(yǔ)句",有更多有關(guān)語(yǔ)句的信息。) 因?yàn)闆](méi)有辦法更改ip地址,所以僅給索引使用了get存取標(biāo)志。為了簡(jiǎn)單其見,我忽略了數(shù)組的邊界溢出檢查。
5.4 事件
當(dāng)你寫一個(gè)類時(shí),有時(shí)有必要讓類的客戶知道一些已經(jīng)發(fā)生的事件。如果你是一個(gè)具有多年編程經(jīng)驗(yàn)的程序員,似乎有很多的解決辦法,包括用于回調(diào)的函數(shù)指針和用于activex控件的事件接收(event sinks)?,F(xiàn)在你將要學(xué)到另外一種把客戶代碼關(guān)聯(lián)到類通知的辦法——使用事件。
事件既可以被聲明為類域成員(成員變量),也可以被聲明為屬性。兩者的共性為,事件的類型必定是代表元,而函數(shù)指針原形和c#的代表元具有相同的含義。
每一個(gè)事件都可以被0或更多的客戶占用,且客戶可以隨時(shí)關(guān)聯(lián)或取消事件。你可以以靜態(tài)或者以實(shí)例方法定義代表元,而后者很受c++程序員的歡迎。
既然我已經(jīng)提到了事件的所有功能及相應(yīng)的代表元,請(qǐng)看清單5.11中的例子。它生動(dòng)地體現(xiàn)了該理論。
清單5.11 在類中實(shí)現(xiàn)事件處理
1: using system;
2:
3: // 向前聲明
4: public delegate void eventhandler(string strtext);
5:
6: class eventsource
7: {
8: public event eventhandler textout;
9:
10: public void triggerevent()
11: {
12: if (null != textout) textout("event triggered");
13: }
14: }
15:
16: class testapp
17: {
18: public static void main()
19: {
20: eventsource evsrc = new eventsource();
21:
22: evsrc.textout += new eventhandler(catchevent);
23: evsrc.triggerevent();
24:
25: evsrc.textout -= new eventhandler(catchevent);
26: evsrc.triggerevent();
27:
28: testapp theapp = new testapp();
29: evsrc.textout += new eventhandler(theapp.instancecatch);
30: evsrc.triggerevent();
31: }
32:
33: public static void catchevent(string strtext)
34: {
35: console.writeline(strtext);
36: }
37:
38: public void instancecatch(string strtext)
39: {
40: console.writeline("instance " + strtext);
41: }
42: }
第4行聲明了代表元(事件方法原形),它用來(lái)給第8行中的eventsource類聲明textout事件域成員。你可以觀察到代表元作為一種新的類型聲明,當(dāng)聲明事件時(shí)可以使用代表元。
該類僅有一個(gè)方法,它允許我們觸發(fā)事件。請(qǐng)注意,你必須進(jìn)行事件域成員不為null的檢測(cè),因?yàn)榭赡軙?huì)出現(xiàn)沒(méi)有客戶對(duì)事件感興趣這種情況。
testapp類包含了main 方法,也包含了另外兩個(gè)方法,它們都具備事件所必需的信號(hào)。其中一個(gè)方法是靜態(tài)的,而另一個(gè)是實(shí)例方法。
eventsource 被實(shí)例化,而靜態(tài)方法catchevent被預(yù)關(guān)聯(lián)上了 textout事件:
evsrc.textout += new eventhandler(catchevent);
從現(xiàn)在起,當(dāng)事件被觸發(fā)時(shí),該方法被調(diào)用。如果你對(duì)事件不再感興趣,簡(jiǎn)單地取消關(guān)聯(lián):
evsrc.textout -= new eventhandler(catchevent);
注意,你不能隨意取消關(guān)聯(lián)的處理函數(shù)——在類代碼中僅創(chuàng)建了這些處理函數(shù)。為了證明事件處理函數(shù)也和實(shí)例方法一起工作,余下的代碼建立了testapp 的實(shí)例,并鉤住事件處理方法。
事件在哪方面對(duì)你特別有用?你將經(jīng)常在asp+中或使用到wfc (windows foundation classes)時(shí),涉及到事件和代表元。
5.5 應(yīng)用修飾符
在這一章的學(xué)習(xí)過(guò)程中,你已經(jīng)見過(guò)了象public、virtual等修飾符。欲以一種易于理解的方法概括它們,我把它們劃分為三節(jié):
。類修飾符
。成員修飾符
。存取修飾符
5.5.1 類修飾符
到目前為止,我還沒(méi)有涉及到類修飾符,而只涉及到了應(yīng)用于類的存取修飾符。但是,有兩個(gè)修飾符你可以用于類:
abstract——關(guān)于抽象類的重要一點(diǎn)就是它不能被實(shí)例化。只有不是抽象的派生類才能被實(shí)例化。派生類必須實(shí)現(xiàn)抽象基類的所有抽象成員。你不能給抽象類使用sealed 修飾符。
sealed——密封 類不能被繼承。使用該修飾符防止意外的繼承,在.net框架中的類用到這個(gè)修飾符。
要見到兩個(gè)修飾符的運(yùn)用,看看清單5.12 ,它創(chuàng)建了一個(gè)基于一個(gè)抽象類的密封類(肯定是一個(gè)十分極端的例子)。
清單 5.12 抽象類和密封類
1: using system;
2:
3: abstract class abstractclass
4: {
5: abstract public void mymethod();
6: }
7:
8: sealed class derivedclass:abstractclass
9: {
10: public override void mymethod()
11: {
12: console.writeline("sealed class");
13: }
14: }
15:
16: public class testapp
17: {
18: public static void main()
19: {
20: derivedclass dc = new derivedclass();
21: dc.mymethod();
22: }
23: }
5.5.2 成員修飾符
與有用的成員修飾符的數(shù)量相比,類修飾符的數(shù)量很少。我已經(jīng)提到了一些,這本書即將出現(xiàn)的例子描述了其它的成員修飾符。
以下是有用的成員修飾符:
abstract——說(shuō)明一個(gè)方法或存取標(biāo)志不能含有一個(gè)實(shí)現(xiàn)。它們都是隱式虛擬,且在繼承類中,你必須提供 override關(guān)鍵字。
const——這個(gè)修飾符應(yīng)用于域成員或局部變量。在編譯時(shí)常量表達(dá)式被求值,所以,它不能包含變量的引用。
event ——定義一個(gè)域成員或?qū)傩宰鳛轭愋褪录?。用于捆綁客戶代碼到類的事件。
extern——告訴編譯器方法實(shí)際上由外部實(shí)現(xiàn)。第10章 “和非受管代碼互相操作” 將全面地涉及到外部代碼。
override——用于改寫任何基類中被定義為virtual的方法和存取標(biāo)志。要改寫的名字和基類的方法必須一致。
readonly——一個(gè)使用 readonly修飾符的域成員只能在它的聲明或者在包含它的類的構(gòu)造函數(shù)中被更改。
static——被聲明為static的成員屬于類,而不屬于類的實(shí)例。你可以用static 于域成員、方法、屬性、操作符甚至構(gòu)造函數(shù)。
virtual——說(shuō)明方法或存取標(biāo)志可以被繼承類改寫。
5.5.3 存取修飾符
存取修飾符定義了某些代碼對(duì)類成員(如方法和屬性)的存取等級(jí)。你必須給每個(gè)成員加上所希望的存取修飾符,否則,默認(rèn)的存取類型是隱含的。
你可以應(yīng)用4個(gè) 存取修飾符之一:
public——任何地方都可以訪問(wèn)該成員,這是具有最少限制的存取修飾符。
protected——在類及所有的派生類中可以訪問(wèn)該成員,不允許外部訪問(wèn)。
private——僅僅在同一個(gè)類的內(nèi)部才能訪問(wèn)該成員。甚至派生類都不能訪問(wèn)它。
internal——允許相同組件(應(yīng)用程序或庫(kù))的所有代碼訪問(wèn)。在.net組件級(jí)別,你可以把它視為public,而在外部則為private。
為了演示存取修飾符的用法,我稍微修改了triangle例子,使它包含了新增的域成員和一個(gè)新的派生類(見清單 5.13)。
清單 5.13 在類中使用存取修飾符
1: using system;
2:
3: internal class triangle
4: {
5: protected int m_a, m_b, m_c;
6: public triangle(int a, int b, int c)
7: {
8: m_a = a;
9: m_b = b;
10: m_c = c;
11: }
12:
13: public virtual double area()
14: {
15: // heronian formula
16: double s = (m_a + m_b + m_c) / 2.0;
17: double darea = math.sqrt(s*(s-m_a)*(s-m_b)*(s-m_c));
18: return darea;
19: }
20: }
21:
22: internal class prism:triangle
23: {
24: private int m_h;
25: public prism(int a, int b, int c, int h):base(a,b,c)
26: {
27: m_h = h;
28: }
29:
30: public override double area()
31: {
32: double darea = base.area() * 2.0;
33: darea += m_a*m_h + m_b*m_h + m_c*m_h;
34: return darea;
35: }
36: }
37:
38: class prismapp
39: {
40: public static void main()
41: {
42: prism prism = new prism(2,5,6,1);
43: console.writeline(prism.area());
44: }
45: }
triangle 類和 prism 類現(xiàn)在被標(biāo)為 internal。這意味著它們只能在當(dāng)前組件中被訪問(wèn)。
請(qǐng)記住“.net組件”這個(gè)術(shù)語(yǔ)指的是包裝( packaging,),而不是你可能在com+中用到的組件。
triangle 類有三個(gè) protected成員,它們?cè)跇?gòu)造函數(shù)中被初始化,并用于面積計(jì)算的方法中。由于這些成員是protected 成員,所以我可以在派生類prism中訪問(wèn)它們,在那里執(zhí)行不同的面積計(jì)算。
prism自己新增了一個(gè)成員m_h,它是私有的——甚至派生類也不能訪問(wèn)它。
花些時(shí)間為每個(gè)類成員甚至每個(gè)類計(jì)劃一種保護(hù)層次,通常是個(gè)好主意。當(dāng)需要引入修改時(shí),全面的計(jì)劃最終會(huì)幫助你,因?yàn)闆](méi)有程序員會(huì)愿意使用“沒(méi)有文檔”的類功能。
5.6 小結(jié)
這章顯示了類的各種要素,它是運(yùn)行實(shí)例(對(duì)象)的模板。在一個(gè)對(duì)象的生命期,首先被執(zhí)行的代碼是個(gè)構(gòu)造函數(shù)。構(gòu)造函數(shù)用來(lái)初始化變量,這些變量后來(lái)在方法中用于計(jì)算結(jié)果。
方法允許你傳遞值、引用給變量,或者只傳送一個(gè)輸出值。方法可以被改寫以實(shí)現(xiàn)新的功能,或者你可以屏蔽基類成員,如果它實(shí)現(xiàn)了一個(gè)具有和派生類成員相同名字的方法。
命名屬性可以被當(dāng)作域成員(成員變量)或?qū)傩源嫒?biāo)志實(shí)現(xiàn)。后者是get和set存取標(biāo)志,忽略一個(gè)或另外一個(gè),你可以創(chuàng)建僅寫或僅讀屬性。存取標(biāo)志非常適合于確認(rèn)賦給屬性的值。
c#類的另外一個(gè)功能是索引,它使象數(shù)組語(yǔ)法一樣訪問(wèn)類中值成為可能。還有,如果當(dāng)類中的某些事情發(fā)生時(shí),你想客戶得到通知,要讓它們與事件關(guān)聯(lián)。
當(dāng)垃圾收集器調(diào)用析構(gòu)函數(shù)時(shí),對(duì)象的生命就結(jié)束了。由于你不能準(zhǔn)確地預(yù)測(cè)這種情況什么時(shí)候會(huì)發(fā)生,所以應(yīng)該創(chuàng)建一個(gè)方法以釋放這些寶貴的資源,當(dāng)你停止使用它們時(shí)本文來(lái)源于網(wǎng)頁(yè)設(shè)計(jì)愛好者web開發(fā)社區(qū)http://www.html.org.cn收集整理,歡迎訪問(wèn)。