《.net編程先鋒C#》第五章 類(下)(轉)

來源:互聯網
上載者:User
編程 5.2.3 方法屏蔽
重定義方法的一個不同手段就是要屏蔽基類的方法。當從別人提供的類衍生類別時,這個功能特

別有價值。看清單 5.6,假設BaseClass由其他人所寫,而你從它派生出 DerivedClass 。

清單 5.6 Derived Class 實現一個沒有包含於 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: }

在這個例子中, DerivedClass 通過TestMethod()實現了一個額外的功能。但是,如果基類的

開發人員認為把TestMethod()放在基類中是個好主意,並使用相同的名字實現它時,會出現什麼問題

呢?(見清單5.7)

清單 5.7 Base Class 實現和 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: }

在優秀的程式設計語言中,你現在會遇到一個真正的大麻煩。但是,C#會給你提出警告:
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()'。要想使當前方法改寫原來的實現,加上 override關鍵字。否則加上新

的關鍵字。)
具有了修飾符new,你就可以告訴編譯器,不必重寫衍生類別或改變使用到衍生類別的代碼,你的方法就

能屏蔽新加入的基類方法。清單5.8 顯示如何在例子中運用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修飾符,編譯器就知道你重定義了基類的方法,它應該屏蔽基類方法。但是,如果

你按以下方式編寫:
DerivedClass test = new DerivedClass();
((BaseClass)test).TestMethod();
基類方法的實現就被調用了。這種行為不同於改寫方法,後者保證大部指派生方法獲得調用。
5.3 類屬性
有兩種途徑揭示類的命名屬性——通過域成員或者通過屬性。前者是作為具有公用訪問性的成員變數而被實現的;後者並不直接回應儲存位置,只是通過 存取標誌(accessors)被訪問。
當你想讀出或寫入屬性的值時,存取標誌限定了被實現的語句。用於讀出屬性的值的存取標誌記為關鍵字get,而要修改屬性的值的讀寫符標誌記為set。在你對該理論一知半解以前,請看一下清單5.9中的例子,屬性SquareFeet被標上了get和set的存取標誌。
清單 5.9 實現屬性存取標誌
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類有一個命名為SquareFeet的屬性,它可以被讀和寫。實際的值儲存在一個可以從類內部訪問的變數中——如果你想當作一個域成員重寫它,你所要做的就是忽略存取標誌而把變數重新定義為:
public int SquareFeet;
對於一個如此簡單的變數,這樣不錯。但是,如果你想要隱藏類內部儲存結構的細節時,就應該採用存取標誌。在這種情況下,set 存取標誌給值參數中的屬性傳遞新值。(可以改名,見第10行。)
除了能夠隱藏實現細節外,你還可自由地限定各種操作:
get和set:允許對屬性進行讀寫訪問。
get only:只允許讀屬性的值。
set only:只允許寫屬性的值。
除此之外,你可以獲得實現在set標誌中有效代碼的機會。例如,由於種種原因(或根本沒有原因),你就能夠拒絕一個新值。最好是沒有人告訴你它是一個動態屬性——當你第一次請求它後,它會儲存下來,故要儘可能地延遲資源分派。

5.4 索引
你想過象訪問數組那樣使用索引訪問類嗎 ?使用C#的索引功能,對它的期待便可了結。

文法基本上象這樣:
屬性 修飾符 聲明 { 聲明內容}

具體的例子為
public string this[int nIndex]
{
get { ... }
set { ... }
}

索引返回或按給出的index設定字串。它沒有屬性,但使用了public修飾符。聲明部分由類型string和this 組成用於表示類的索引。get和set的執行規則和屬性的規則相同。(你不能取消其中一個。) 只存在一個差別,那就是:你幾乎可以任意定義大括弧中的參數。限制為,必須至少規定一個參數,允許ref 和out 修飾符。
this關鍵字確保一個解釋。索引沒有使用者定義的名字,this 表示預設介面的索引。如果類實現了多個介面,你可以增加更多個由InterfaceName.this說明的索引。

為了示範一個索引的使用,我建立了一個小型的類,它能夠解析一個主機名稱為IP地址——或一個IP地址清單(以http://www.microsoft.com為例 )。這個列表通過索引可以訪問,你可以看一下清單

5.10 的具體實現。

清單 5.10 通過一個索引擷取一個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: }

為瞭解析主機名稱,我用到了DNS類,它是System .Net 名字空間的一部分。但是,由於這個名字空間並不包含在核心庫中,所以必須在編譯命令列中引用該庫:
csc /r:System.Net.dll /out:resolver.exe dnsresolve.cs
解析代碼是向前解析的。在該 Resolve方法中,代碼調用DNS類的靜態方法GetHostByName,它返回一個IPHostEntry對象。結果,該對象包含有我要找的數組——AddressList數組。在退出Resolve 方法之前,在局部的對象執行個體成員m_arrIPs中,儲存了一個AddressList array的拷貝(類型IPAddress 的Object Storage Service在其中)。
具有現在產生的數組 ,通過使用在類ResolveDNS中求得的索引,應用程式代碼就可以在第37至38行列舉出IP地址。(在第6章 "控制語句",有更多有關語句的資訊。) 因為沒有辦法更改IP地址,所以僅給索引使用了get存取標誌。為了簡單其見,我忽略了數組的邊界溢出檢查。

5.4 事件
當你寫一個類時,有時有必要讓類的客戶知道一些已經發生的事件。如果你是一個具有多年編程經驗的程式員,似乎有很多的解決辦法,包括用於回調的函數指標和用於ActiveX控制項的事件接收(event sinks)。現在你將要學到另外一種把客戶代碼關聯到類通知的辦法——使用事件。
事件既可以被聲明為類域成員(成員變數),也可以被聲明為屬性。兩者的共性為,事件的類型必定是代表元,而函數指標原形和C#的代表元具有相同的含義。
每一個事件都可以被0或更多的客戶佔用,且客戶可以隨時關聯或取消事件。你可以以靜態或者以執行個體方法定義代表元,而後者很受C++程式員的歡迎。
既然我已經提到了事件的所有功能及相應的代表元,請看清單5.11中的例子。它生動地體現了該理論。

清單5.11 在類中實現事件處理
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行聲明了代表元(事件方法原形),它用來給第8行中的EventSource類聲明TextOut事件域成員。你可以觀察到代表元作為一種新的型別宣告,當聲明事件時可以使用代表元。
該類僅有一個方法,它允許我們觸發事件。請注意,你必須進行事件域成員不為null的檢測,因為可能會出現沒有客戶對事件感興趣這種情況。
TestApp類包含了Main 方法,也包含了另外兩個方法,它們都具備事件所必需的訊號。其中一個方法是靜態,而另一個是執行個體方法。
EventSource 被執行個體化,而靜態方法CatchEvent被預關聯上了 TextOut事件:
evsrc.TextOut += new EventHandler(CatchEvent);
從現在起,當事件被觸發時,該方法被調用。如果你對事件不再感興趣,簡單地取消關聯:
evsrc.TextOut -= new EventHandler(CatchEvent);
注意,你不能隨意取消關聯的處理函數——在類代碼中僅建立了這些處理函數。為了證明事件處理函數也和執行個體方法一起工作,餘下的代碼建立了TestApp 的執行個體,並鉤住事件處理方法。
事件在哪方面對你特別有用?你將經常在ASP+中或使用到WFC (Windows Foundation Classes)時,涉及到事件和代表元。

5.5 應用修飾符
在這一章的學習過程中,你已經見過了象public、virtual等修飾符。欲以一種易於理解的方法概括它們,我把它們劃分為三節:

。類修飾符
。成員修飾符
。存取修飾符

5.5.1 類修飾符
到目前為止,我還沒有涉及到類修飾符,而只涉及到了應用於類的存取修飾符。但是,有兩個修飾符你可以用於類:
abstract——關於抽象類別的重要一點就是它不能被執行個體化。只有不是抽象的衍生類別才能被執行個體化。衍生類別必須實現抽象基類的所有抽象成員。你不能給抽象類別使用sealed 修飾符。
sealed——密封 類不能被繼承。使用該修飾符防止意外的繼承,在.NET架構中的類用到這個修飾符。
要見到兩個修飾符的運用,看看清單5.12 ,它建立了一個基於一個抽象類別的密封類(肯定是一個十分極端的例子)。

清單 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 成員修飾符
與有用的成員修飾符的數量相比,類修飾符的數量很少。我已經提到了一些,這本書即將出現的例子描述了其它的成員修飾符。
以下是有用的成員修飾符:
abstract——說明一個方法或存取標誌不能含有一個實現。它們都是隱式虛擬,且在繼承類中,你必須提供 override關鍵字。
const——這個修飾符應用於域成員或局部變數。在編譯時間常量運算式被求值,所以,它不能包含變數的引用。
event ——定義一個域成員或屬性作為類型事件。用於捆綁客戶代碼到類的事件。
extern——告訴編譯器方法實際上由外部實現。第10章 “和非受管代碼互相操作” 將全面地涉及到外部代碼。
override——用於改寫任何基類中被定義為virtual的方法和存取標誌。要改寫的名字和基類的方法必須一致。
readonly——一個使用 readonly修飾符的域成員只能在它的聲明或者在包含它的類的建構函式中被更改。

static——被聲明為static的成員屬於類,而不屬於類的執行個體。你可以用static 於域成員、方法、屬性、操作符甚至建構函式。
virtual——說明方法或存取標誌可以被繼承類改寫。

5.5.3 存取修飾符
存取修飾符定義了某些代碼對類成員(如方法和屬性)的存取等級。你必須給每個成員加上所希望的存取修飾符,否則,預設的存取類型是隱含的。
你可以應用4個 存取修飾符之一:
public——任何地方都可以訪問該成員,這是具有最少限制的存取修飾符。
protected——在類及所有的衍生類別中可以訪問該成員,不允許外部存取。
private——僅僅在同一個類的內部才能訪問該成員。甚至衍生類別都不能訪問它。
internal——允許相同組件(應用程式或庫)的所有代碼訪問。在.NET組件層級,你可以把它視為public,而在外部則為private。
為了示範存取修飾符的用法,我稍微修改了Triangle例子,使它包含了新增的域成員和一個新的衍生類別(見清單 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 類現在被標為 internal。這意味著它們只能在當前組件中被訪問。
請記住“.NET組件”這個術語指的是封裝( packaging,),而不是你可能在COM+中用到的組件。
Triangle 類有三個 protected成員,它們在建構函式中被初始化,並用於面積計算的方法中。由於這些成員是protected 成員,所以我可以在衍生類別Prism中訪問它們,在那裡執行不同的面積計算。
Prism自己新增了一個成員m_h,它是私人的——甚至衍生類別也不能訪問它。
花些時間為每個類成員甚至每個類計劃一種保護層次,通常是個好主意。當需要引入修改時,全面的計劃最終會協助你,因為沒有程式員會願意使用“沒有文檔”的類功能。
5.6 小結
這章顯示了類的各種要素,它是運行執行個體(對象)的模板。在一個對象的生命期,首先被執行的代碼是個建構函式。建構函式用來初始設定變數,這些變數後來在方法中用於計算結果。
方法允許你傳遞值、引用給變數,或者只傳送一個輸出值。方法可以被改寫以實現新的功能,或者你可以屏蔽基類成員,如果它實現了一個具有和衍生類別成員相同名字的方法。
命名屬性可以被當作域成員(成員變數)或屬性存取標誌實現。後者是get和set存取標誌,忽略一個或另外一個,你可以建立僅寫或僅讀屬性。存取標誌非常適合於確認賦給屬性的值。
C#類的另外一個功能是索引,它使象數組文法一樣訪問類中值成為可能。還有,如果當類中的某些事情發生時,你想客戶得到通知,要讓它們與事件關聯。
當垃圾收集器調用解構函式時,對象的生命就結束了。由於你不能準確地預測這種情況什麼時候會發生,所以應該建立一個方法以釋放這些寶貴的資源,當你停止使用它們時。

相關文章

E-Commerce Solutions

Leverage the same tools powering the Alibaba Ecosystem

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。