五章 類 (2)
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} IPs 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 的對象存儲在其中)。
具有現在生成的數組 ,通過使用在類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(Cat