Jeffrey Richter
類型構造函數
您應該很熟悉構造函數,它負責設置對象實例的初始狀態。除了實例構造函數,Microsoft® .NET 公共語言運行庫(common language runtime,CLR)還支持類型構造函數(也稱爲靜態構造函數、類構造函數、或者類型初始函數)。類型構造函數可以用於接口、類和值類型。它允許類型在任何成員被訪問前進行所需的初始化工作。類型構造函數不接受任何參數,返回類型必須是 void。類型構造函數僅訪問類型的靜態字段,並且其通常的用途是初始化這些字段。類型構造函數確保在類型的任何實例創建前,以及在類型的任何靜態字段或者方法被引用前運行。
許多語言(包括 C#)自動爲任何定義的類型產生類型構造函數。但是,有些語言需要顯式實現類型構造函數。
爲了理解類型構造函數,查看下面的類型(用 C# 定義):
class AType { static int x = 5; }
在生成該段代碼時,編譯器自動爲 AType 產生一個類型構造函數。該構造函數負責將靜態字段 x 的值初始化爲 5。如果使用 ILDasm,可以很容易地發現類型構造函數方法,因爲它們的名字是 .cctor(即 class constructor)。
在 C# 中,您可以在類型中定義一個靜態的構造函數方法來親自實現類型構造函數。使用 static 關鍵字讓該構造函數成爲一個類型構造函數而不是實例構造函數。下面是一個很簡單的例子:
class AType { static int x; static AType() { x = 5; } }
該類型定義與前面的相同。注意,類型構造函數永遠不能試圖創建其自身類型的實例,並且構造函數不能引用任何此類型的非靜態成員。
最後,對於以下代碼,C# 編譯器只產生一個類型構造函數方法。
class AType { static int x = 5; static AType() { x = 10; } }
該構造函數首先將 x 初始化爲 5,然後將 x 初始化爲 10。換句話說,編譯器最終產生的類型構造函數首先包含靜態字段的初始化代碼,然後纔是類型構造函數方法中的代碼。
屬性
許多類型定義可進行檢索或者改變的屬性。通常來說,這些屬性被實現爲類型的字段成員。例如,下面是一個包含兩個字段的類型定義:
class Employee { public String Name; public Int32 Age; }
如果創建該類型的一個實例,就能夠輕鬆獲取或設置以下屬性,如下所示:
Employee e = new Employee(); e.Name = "Jeffrey Richter"; // Set the Name attribute e.Age = 36; // Set the Age attribute Console.WriteLine(e.Name); // Displays "Jeffrey Richter"
以這種方式對屬性進行操作很常見。但是,我認爲,上面的代碼根本無法實現。面向對象設計和開發的一大特點是數據抽象。數據抽象意味着類型字段永遠不應該被公開,因爲太容易編寫出不恰當使用字段的代碼,破壞對象的狀態。例如,很容易寫出以下代碼來破壞一個Employee對象。
e.Age = -5; // How could someone be -5 years old?
因此,當設計類型時,強烈建議將所有的字段設爲私有,或者至少受保護 - 永遠不要設爲公共。這樣,要讓某類型的用戶獲取或者設置屬性,公開專門用於此目的的方法。將對字段的訪問封裝起來的方法通常稱爲訪問器方法 (accessor)。它們可以進行完整的檢查(可選),保證對象的狀態不會被破壞。例如,我會重寫前面所示的 Employee 類,以模仿HYPERLINK "http://msdn.microsoft.com/msdnmag/issues/01/02/dotnet/figures.asp" /l "fig1"圖 1 中的代碼。這是一個簡單的例子,但是可以看到抽象數據字段的巨大好處,也可以看到使屬性只讀或只寫是多麼容易,無需實現某種訪問器方法。
圖 1 中所示的抽象數據的方式有兩個缺點。首先,因爲必須實現額外的函數,所以需要編寫更多的代碼;其次,使用此類型的用戶必須調用方法,而不是簡單地引用一個字段名。
e.SetAge(36); // Updates the age e.SetAge(-5); // Throws an exception
我想您也會覺得這些缺點問題並不大。不過,運行庫提供一種稱爲屬性 (property) 的機制,可以部分解決第一個缺點並完全克服第二個缺點。
圖 2 中所示的類使用了與圖 1 中的類功能完全相同的屬性。您可以看到,屬性稍微簡化了代碼,更重要的是,它們允許調用方按如下方式編寫代碼:
e.Age = 36; // Updates the age e.Age = -5; // Throws an exception
Get 屬性訪問器 (get property accessor) 返回的值與傳遞給 Set 屬性訪問器 (set property accessor) 的參數是同一類型。Set 屬性具有返回類型 void,Get 屬性沒有任何參數。屬性可以是靜態函數,虛函數,抽象函數,內部函數,私有函數,保護函數或者公有函數。另外,後面會提到,屬性可以在接口中定義。
還應該指出,屬性不一定和某個字段相聯繫。例如,System.IO.FileStream 類型定義了一個 Length 屬性,它返回流的字節總數。在字段中不保留該長度;相反,在調用 Length 屬性的 get 方法時,它調用另一個函數,使底層操作系統返回打開文件流的字節總數。
當創建屬性時,編譯器實際上產生特別的 get_PropName 和/或 set_PropName 訪問器方法(PropName 是屬性的名稱)。大多數編譯器會理解這些特別的方法,從而允許開發人員使用特別的屬性語法來訪問這些方法。但是,不需要遵從通用語言規範(Common Language Specification,CLS)的編譯器完全支持屬性,編譯器只需要支持調用這些特別的訪問器方法。
另外,不完全支持屬性的編譯器可能需要稍微不同的語法來定義和使用屬性。例如,託管擴展的 C++ 需要使用 _property 關鍵字。
索引屬性
有些類型(例如 System.Collections.SortedList)公開元素的邏輯列表。爲了輕鬆訪問這種類型的元素,類型可以定義索引屬性(也稱爲索引器,indexer)。圖 3 顯示索引屬性的一個示例。使用這種類型索引器非常簡單:
BitArray ba = new BitArray(14); for (int x = 0; x < 14; x++) { // Turn all even numbered bits on ba[x] = (x % 2 == 0); Console.WriteLine("Bit " + x + " is " + (ba[x] ? "On" : "Off")); }
在圖 3 所示的 BitArray 示例中,索引器有一個 Int32 的參數:bitPosition。所有索引器必須至少有一個參數,而且可能有兩個或者更多的參數。這些參數(和返回類型)可以是任意類型。常見的情況是,創建一個將 String 作爲參數的索引器,用於在一個關聯數組中查找值。類型可以提供多個重載的索引器,只要它們的原型不同。
象設置屬性一樣,設置索引器訪問器方法包含一個隱含的參數 value,它表示訪問器被調用時新的期望值。BitArray 設置訪問器顯示這個使用的 value 參數。
一個設計良好的索引器應該同時具有 get 和 set 訪問器。儘管您可以只實現 get 訪問器(用於只讀語義)或只實現 set 訪問器(用於只寫語義),建議索引器還是實現兩個訪問器。原因很簡單,使用索引的用戶不會希望只有一半的行爲可用。例如,用戶不希望在編寫下面兩行代碼時出現編譯器錯誤:
String s = SomeObj[5]; // Compiles OK if get accessor exists SomeObj[5] = s; // Compiler error if set accessor doesn't exist
索引器總是作用在特定的類型實例上,並且不能聲明爲靜態的。但是索引器可以被標記爲公有、私有、保護或者內部。
當創建一個索引器屬性時,編譯器實際上產生特殊的 get_item 和/或 set_item 訪問器方法。大多數編譯器會理解這些特殊的方法,從而允許開發人員使用特殊的索引屬性語法訪問這些方法。但是,不需要遵循 CLS 的編譯器完全支持索引器屬性;編譯器只需要支持調用特殊的訪問器方法。
而且,完全支持索引屬性的編譯器可能需要稍微不同的語法來定義和使用這些屬性。例如,託管擴展的 C++需要使用 _property 關鍵字。
小結
本專欄討論的概念對所有 .NET 程序員而言都非常重要。我提到的特殊類型的成員使組件成爲公用語言運行庫中最好的元素,即現代的組件旨在支持屬性。
在下一個專欄中,我會介紹委託和事件成員,因爲它們是基於組件的應用程序開發和設計的一個不可分割的部分。
Jeffrey Richter (http://www.jeffreyrichter.com/) 是 Programming Applications for Microsoft Windows (Microsoft Press, 1999) 的作者,也是 Wintellect (http://www.wintellect.com/) 公司的創始人之一。Wintellect 公司是一家軟件教育、調試和諮詢公司。他是 .NET 和 Win32® 領域的開發和設計專家。Jeff 現在正在撰寫一本關於 Microsoft .NET Framework 開發的書籍,並且正在舉辦 .NET 技術研討班。
翻譯作者Luke是微軟公司的軟件工程師,習慣使用C++和C#開發應用程序。閒暇時間他喜歡音樂,旅遊和懷舊遊戲,並且願意幫助MSDN翻譯更多的文章和其他開發者共享。可以通過[email protected]聯繫他。