文章目錄
- Microsoft C#
- 潛在的未來功能
- 產生最新的泛型類
- 建立和使用泛型
- 泛型的優點
- 其他語言中的泛型支援
發布日期: 4/30/2003 | 更新日期: 6/25/2004
Prashant Sridharan
Microsoft Corporation
適用於: Microsoft Visual C#(TM)
摘要:Microsoft Corporation 正在開發 C# 語言的下一個主要版本。本文介紹了四種主要的新功能,即泛型、迭代程式、匿名方法和局部類型。
本頁內容
| |
簡介 |
| |
泛型 |
| |
迭代程式 |
| |
匿名方法 |
| |
局部類型 |
| |
符合標準 |
| |
可用性 |
| |
更多資訊 |
簡介
C# 是創新性的新式程式設計語言,它巧妙地結合了最常用的行業語言和研究語言中的功能。在保持 C# 設計思想不變的同時,Microsoft 在 C# 語言中引入了幾種潛在的新功能,提高了開發人員在語言構造方面的效率。
Microsoft C#
自 2001 年 2 月 C# 問世以來,很多開發人員已經開始使用 C# 程式設計語言來構建軟體。而 Microsoft 自身也使用 C# 構建了幾種正式的應用程式,包括 .NET Framework、MSN Web 屬性和 Tablet PC SDK。由此可見,C# 是一種適用於構造高品質商業軟體的語言。
C# 語言中的許多功能是基於以下四種不同設計目標而建立的:
| • |
統一的類型系統以及簡化實值型別和參考型別在 C# 語言中的用法。 |
| • |
通過 XML 注釋、特性、屬性、事件和委託等功能建立基於組件的設計。 |
| • |
藉助 C# 語言的獨特功能(包括安全的指標操作、溢出檢查等)建立實用的開發人員控制功能。 |
| • |
建立諸如 foreach 和 using 語句這樣的實用語言構造,提高開發人員的效率。 |
在 C# 語言的“Visual Studio for Yukon”版本中,Microsoft 計劃通過將廣泛的研究語言和行業語言中的各種功能結合在一起建立一種簡潔、實用的文法。這些語言功能包括泛型、迭代程式、匿名方法和局部類型。
潛在的未來功能
實際上,C# 的未來創新功能主要基於統一的類型系統、基於組件的開發、開發人員控制功能和實用的語言構造。下面總結了 Microsoft 計劃在 C# 語言的下一個主要版本中提供的四種主要的新功能。這些功能的設計尚未完成,Microsoft Corporation 歡迎廣大的開發人員針對這些功能發表評論。
返回頁首
泛型
隨著項目變得越來越複雜,程式員日益需要一種方法來更好地重複使用和自訂他們現有的基於組件的軟體。為了實現在其他語言中重複使用進階代碼,程式員通常要使用一種名為“泛型”的功能。C# 將包括一種安全且高效的泛型,它與 C++ 中的模板和 Java 語言中提出的泛型在文法上只是稍有差別,但在實現方式上卻存在很大差別。
產生最新的泛型類
利用目前的 C#,程式員可以通過在基本物件類型的執行個體中儲存資料來建立有限形式的真正泛型。由於在 C# 中每個對象都是從基本物件類型繼承的,再加上統一 .NET 類型系統的裝箱和unboxing功能,程式員可以將參考型別和實值型別儲存到物件類型的變數中。但是,對於參考型別、實值型別與基本物件類型之間的轉換,還有一些效能缺陷。
為了說明這一點,以下程式碼範例建立了一個簡單的 Stack 類型,其中包含兩個操作“Push”和“Pop”。Stack 類將其資料存放區在物件類型的數組中,Push 和 Pop 方法使用基本物件類型來接受和返回資料:
public class Stack{ private object[] items = new object[100]; public void Push(object data) { ... } public object Pop() { ... }}
然後,就可以將自訂類型(例如 Customer 類型)壓入堆棧。但是,如果程式需要檢索資料,則需要將 Pop 方法的結果(基本物件類型)顯式轉換成 Customer 類型。
Stack s = new Stack();s.Push(new Customer());Customer c = (Customer) s.Pop();
如果將一個實值型別(例如一個整數)傳遞給 Push 方法,運行時會自動將其轉換為參考型別(該過程稱作裝箱),然後將其儲存在內部資料結構中。與此類似,如果程式要從堆棧中檢索一個實值型別(例如一個整數),則需要將從 Pop 方法擷取的物件類型顯式轉換成實值型別,該過程稱作unboxing:
Stack s = new Stack();s.Push(3);int i = (int) s.Pop();
實值型別和參考型別之間的裝箱和unboxing操作非常繁重。
而且,在當前的實現中,無法限制堆棧中放置的資料類型。實際上,可以先建立堆棧,然後將 Customer 類型壓入堆棧。然後,可使用同一堆棧並嘗試將資料彈出,接著將其轉換為其他類型,如下例所示:
Stack s = new Stack();s.Push(new Customer());Employee e = (Employee) s.Pop();
儘管上一個程式碼範例錯誤地使用了要實現的單個類型 Stack 類,應視為錯誤,但它實際上卻是合法代碼,對它進行編譯時間不會出現問題。但在運行時,該程式會由於無效轉換操作而失敗。
建立和使用泛型
使用 C# 中的泛型可以根據它們所用的類型建立專供編譯器使用的高效資料結構。建立這些所謂的參數化型別後,其內部演算法保持不變,但其內部資料的類型可以隨著終端使用者的設定而改變。
為了協助開發人員節省學習該語言的時間,C# 中泛型的聲明方法與 C++ 中的大致相同。程式員可以按通常的方法建立類和結構,並使用角括弧標記(< 和 >)指定型別參數。使用類時,必須用該類的使用者提供的實際類型替換每個參數。
下例將建立一個 Stack 類,在此類聲明後的角括弧中指定並聲明一個名為 ItemType 的型別參數。泛型 Stack 類的執行個體將接受為其建立的類型並在本機存放區該類型的資料,而不是在建立的類型與基本物件類型之間進行轉換。型別參數 ItemType 充當代理,直到在執行個體化過程中指定了類型並將其用作內部項數組的類型(即 Push 方法的參數類型和 Pop 方法的傳回型別):
public class Stack<ItemType>{ private ItemType[] items; public void Push(ItemType data) { ... } public ItemType Pop() { ... }}
當程式按照以下樣本使用 Stack 類時,您可以指定泛型類使用的實際類型。本例使用執行個體化語句中的角括弧標記將原始的整數類型指定為參數,指示 Stack 類使用此類型:
Stack<int> stack = new Stack<int>();stack.Push(3);int x = stack.Pop();
執行此操作時,程式將建立 Stack 類的新執行個體,其中的每個 ItemType 都被提供的整數參數替換。實際上,當程式用整數參數建立 Stack 類的新執行個體時,在 Stack 類內部本機存放區的項目數組將為整數,而不是對象。程式還消除了與將整數壓入堆棧相關聯的裝箱問題。此外,當程式從堆棧彈出項目時,您無需將其顯式轉換為相應的類型,因為 Stack 類的當前特定執行個體會將整數本機存放區在其資料結構中。
如果希望程式在 Stack 類中儲存其他類型的項目,則必須建立一個 Stack 類的新執行個體並將新類型指定為參數。假設有一個簡單的 Customer 類型,希望程式使用 Stack Object Storage Service該類型。要實現此操作,只需執行個體化 Stack 類並將 Customer 對象作為其型別參數,即可輕鬆重複使用程式碼:
Stack<Customer> stack = new Stack<Customer>();stack.Push(new Customer());Customer c = stack.Pop();
當然,如果程式建立了一個將 Customer 類型作為參數的 Stack 類,則只能在該堆棧中儲存 Customer 類型。實際上,C# 中的泛型具有嚴格的類型,這意味著您不能在該堆棧中儲存整數,如以下樣本所示:
Stack<Customer> stack = new Stack<Customer>();stack.Push(new Customer());stack.Push(3) // 編譯時間錯誤Customer c = stack.Pop(); // 不需要類型轉換。
泛型的優點
使用泛型,程式員只需編寫、測試和部署一次代碼,即可對各種不同的資料類型重複使用該代碼。第一個 Stack 樣本具備此功能,第二個 Stack 樣本允許程式重複使用對其應用程式效能影響不大的代碼。對於實值型別,第一個 Stack 樣本具有較大的效能問題,而第二個 Stack 樣本完全消除了這種問題,因為它去除了裝箱和向下的類型轉換。
而且,編譯時間還會對泛型進行檢查。當程式使用提供的型別參數執行個體化泛型類時,此型別參數只能是程式在類定義中指定的類型。例如,如果程式建立了一個 Customer 物件類型的 Stack,就無法將整數壓入堆棧。通過強制執行這種操作,可以產生更可靠的代碼。
此外,與其他嚴格的類型實現相比,泛型的 C# 實現降低了代碼的膨脹速度。使用泛型建立具有類型的集合,可以在保持操作效能優勢的同時避免建立每個類的特定變體。例如,程式可以建立一個參數化的 Stack 類,而無需建立用於儲存整數的 IntegerStack、用於儲存字串的 StringStack 以及用於儲存 Customer 類型的 CustomerStack。
這樣可以增加代碼的可讀性。只需建立一個 Stack 類,程式就可以將與某個堆棧相關聯的所有操作封裝在一個使用方便的類中。然後,在建立 Customer 類型的 Stack 時,儘管其中儲存了 Customer 類型,但顯而易見,程式使用的仍然是堆棧資料結構。
多個型別參數
泛型可以使用任意多個參數類型。上面的 Stack 樣本中只使用了一種類型。假設您建立了一個儲存值和鍵的簡單 Dictionary 類。在程式中可以通過聲明兩個參數(放在類定義的角括弧中並用逗號分隔)來定義一個泛型版本的 Dictionary 類:
public class Dictionary<KeyType, ValType>{ public void Add(KeyType key, ValType val) { ... } public ValType this[KeyType key] { ... }}
使用該 Dictionary 類時,需要在執行個體化語句的角括弧中提供多個以逗號分隔的參數,並為 Add 函數和索引產生器提供正確類型的參數:
Dictionary<int, Customer> dict = new Dictionary<int, Customer>();dict.Add(3, new Customer());Customer c = dict.Get[3];
約束
通常情況下,程式並不僅僅局限於根據給定的型別參數儲存資料,而是經常需要使用型別參數的成員來執行程式泛型中的語句。
為什麼需要約束
假設在 Dictionary 類的 Add 方法中,您需要使用所提供的鍵的 CompareTo 方法比較項目,例如:
public class Dictionary<KeyType, ValType>{ public void Add(KeyType key, ValType val) { ... switch(key.CompareTo(x)) { } ... }}
遺憾的是,正如預期那樣,型別參數 KeyType 在編譯時間是泛型。如果這樣編寫,編譯器假設對型別參數為 KeyType 的 key 執行個體只能執行適用於基本物件類型(例如 ToString)的操作。結果,由於 CompareTo 方法未定義,編譯器將顯示編譯錯誤。然而,程式可以將 key 變數轉換為包含 CompareTo 方法的對象,例如 IComparable 介面。在以下樣本中,程式將 KeyType 參數類型的執行個體 key 顯式轉換為程式可以編譯的 IComparable 介面:
public class Dictionary<KeyType, ValType>{ public void Add(KeyType key, ValType val) { ... switch(((IComparable) key).CompareTo(x)) { } ... }}
然而,如果立即執行個體化 Dictionary 類而且提供的型別參數沒有實現 IComparable 介面,則程式將遇到執行階段錯誤,尤其是 InvalidCastException 異常。
聲明約束
在 C# 中,程式可以為泛型類中聲明的每個型別參數提供可選約束列表。約束表示要將一個類型構造成泛型所必須滿足的要求。可以使用 where 關鍵字聲明約束,該關鍵字後跟“參數-要求”對,其中“參數”必須是泛型中定義的某個參數,“要求”必須是類或介面。
為了滿足在 Dictionary 類中使用 CompareTo 方法的需要,程式可以對 KeyType 型別參數添加約束,要求傳遞給 Dictionary 類作為第一個參數的任何類型都必須實現 IComparable 介面,例如:
public class Dictionary<KeyType, ValType> where KeyType : IComparable{ public void Add(KeyType key, ValType val) { ... switch(key.CompareTo(x)) { } ... }}
這樣,編譯代碼時就會檢查代碼,以確保程式每次使用 Dictionary 類時,作為第一個參數傳遞的類型都實現了 IComparable 介面。此外,程式在調用 CompareTo 方法之前,再也無需將變數顯式轉換為 IComparable 介面了。
多重約束
對於任何給定的型別參數,程式可以為其指定任意多個介面約束,但最多隻能指定一個類約束。每個新約束都以另一個“參數-要求”對的形式進行聲明,並且給定的泛型的每個約束都用逗號分隔。以下樣本中的 Dictionary 類包含兩種參數,KeyType 和 ValType。KeyType 型別參數有兩個介面約束,而 ValType 型別參數有一個類約束:
public class Dictionary<KeyType, ValType> whereKeyType : IComparable,KeyType : IEnumerable,ValType : Customer{ public void Add(KeyType key, ValType val) { ... switch(key.CompareTo(x)) { } ... }}
運行時的泛型
泛型類的編譯方法與常規類的編譯方法幾乎沒有差別。事實上,編譯結果只不過是中繼資料和中繼語言 (IL)。當然,為了接受代碼中使用者提供的類型,應對 IL 進行參數化。根據提供的型別參數是實值型別還是參考型別,泛型的 IL 的用法會有所不同。
當將實值型別作為參數首次構造泛型時,運行時將使用提供的參數替換 IL 中的相應位置來建立一個專用的泛型。針對每個用作參數的唯一實值型別,將一次性建立專用的泛型。
例如,假設程式碼聲明了一個由整數構造的 Stack:
Stack<int> stack;
此時,運行時將產生一個專用的 Stack 類,並用整數替換此類的相應參數。現在,無論程式碼何時使用整數 Stack,運行時都將重複使用產生的專用 Stack 類。以下樣本將建立整數 Stack 的兩個執行個體,每個執行個體均使用由此整數 Stack 的運行時所產生的程式碼來建立:
Stack<int> stackOne = new Stack<int>();Stack<int> stackTwo = new Stack<int>();
但是,如果在程式碼中的其他位置又建立了一個 Stack 類,並使用不同的實值型別(例如長整型或使用者定義的結構)作為其參數,則運行時將產生其他形式的泛型,而這時會替換 IL 相應位置中的長整型參數。為使用實值型別構造的泛型建立專用類的優點是可以獲得更好的效能。畢竟每個專用的泛型類都是在“本地”包含實值型別,因此不必再進行轉換。
泛型與參考型別的工作方式稍有不同。首次使用任何參考型別構造泛型時,運行時用對象引用替換 IL 中的參數來建立專用的泛型。之後,每當使用參考型別作為參數執行個體化構造的類型時,無論構造的是何種類型,運行時都會重複使用先前建立的專用泛型。
例如,假設有兩個參考型別,Customer 類和 Order 類,並進一步假設您建立了 Customer 類型的 Stack:
Stack<Customer> customers;
此時,運行時將產生專用 Stack 類,該類並不儲存資料,而是儲存隨後填充的對象引用。假設下一行代碼建立了一個其他參考型別的 Stack,稱為 Order:
Stack<Order> orders = new Stack<Order>();
與實值型別不同,沒有為 Order 類型建立另一個專用的 Stack 類,而是建立了專用 Stack 類的執行個體並設定 orders 變數來引用它。對於替換型別參數的每個對象引用,按照 Order 類型的大小分配記憶體空間,並將指標設定為引用該記憶體位置。假設您隨後遇到了一行用於建立 Customer 類型的 Stack 的代碼:
customers = new Stack<Customer>();
同上一個使用 Order 類型建立的 Stack 類一樣,建立了專用 Stack 類的另一個執行個體,並將其中包含的指標設定為引用 Customer 類型大小的記憶體地區。由於不同的程式在參考型別的數量上存在著很大差異,因此泛型的 C# 實現通過將參考型別的數量減少到編譯器為參考型別的泛型類建立的專用類數量,大大降低了代碼的膨脹速度。
此外,當使用型別參數(無論是實值型別還是參考型別)執行個體化泛型 C# 類時,可以在運行時使用反射和實際類型進行查詢,並且可以確定其型別參數。
C# 泛型與其他實現之間的差異
C++ 範本與 C# 泛型存在著顯著的差別。C# 泛型被編譯成 IL,這使得在運行時會智能地為每個實值型別建立相應的專用類型,而為參考型別只會建立一次專用類型;C++ 範本實際上是代碼擴充宏,它為提供給模板的每個型別參數產生一個專用類型。因此,當 C++ 編譯器遇到模板(例如整數 Stack)時,它會將模板代碼擴充為 Stack 類並將整數作為該類本身的類型包含在其中。無論型別參數是實值型別還是參考型別,如果不專門設計連結器來降低代碼膨脹速度,C++ 編譯器每次都會建立一個專用類,從而導致比使用 C# 泛型更顯著的代碼膨脹速度。
而且,C++ 範本不能定義約束。C++ 範本只能通過使用一個成員(可能屬於也可能不屬於型別參數),隱式定義約束。如果最終傳遞給泛型類的型別參數中存在該成員,程式將正常運行。否則,程式將失敗,並可能返回隱藏的錯誤資訊。由於 C# 泛型可以聲明約束,並且具有嚴格的類型,因此不存在這些潛在的錯誤。
現在,Sun Microsystems 已經在新版本的 Java 語言(代碼名稱為“Tiger”)中添加了其他的泛型。Sun 選擇的實現不需要修改 JAVA 虛擬機器。因此,Sun 面臨著如何在未修改的虛擬機器上實現泛型的問題。
提出的 Java 實現使用與 C++ 中的模板和 C# 中的泛型類似的文法,包括型別參數和約束。然而,由於它處理實值型別與處理參考型別的方式不一樣,因此未修改的 JAVA 虛擬機器不支援實值型別的泛型。因此,Java 中的泛型無法得到有效執行。事實上,Java 編譯器會在需要返回資料時,從指定的約束(如果聲明了)或基本物件類型(如果未聲明約束)插入自動向下的類型轉換。此外,Java 編譯器將在運行時產生一個專用類型,然後使用它執行個體化任何構造類型。最後,由於 JAVA 虛擬機器本身不支援泛型,因此無法在運行時確定泛型執行個體的型別參數,而且反射的其他用途也會受到嚴重限制。
其他語言中的泛型支援
Microsoft 的目標是在 Visual J#(TM)、Visual C++ 和 Visual Basic 中支援使用和建立泛型。儘管不同語言實現此功能的時間有早有晚,但 Microsoft 的所有其他三種語言都將包含對泛型的支援。同時,C# 小組正努力在泛型的基礎運行時中加入相應的功能,為實現多語言支援奠定基礎。Microsoft 與第三方語言夥伴緊密協作,以確保在基於 .NET 的語言中建立和使用泛型。
返回頁首
迭代程式
迭代程式是基於研究語言中的類似功能(例如 CLU、Sather 和 Icon)而構造的語言。簡單說來,通過迭代程式,類型可輕鬆地聲明 foreach 語句對其元素進行迭代的方式。
為什麼需要迭代程式
現在,如果類需要使用 foreach 迴圈結構支援迭代操作,則它們必須實現“列舉程式模式”。例如,編譯器將左側的 foreach 迴圈結構擴充為右側的 while 迴圈結構:
List list = ...;foreach(object obj in list){DoSomething(obj);} Enumerator e = list.GetEnumerator();while(e.MoveNext()){ object obj = e.Current; DoSomething(obj);
值得注意的是,為了使 foreach 迴圈能夠正常運行,List 資料結構(所迭代的執行個體)必須支援 GetEnumerator 函數。建立 List 資料結構後,必須實現 GetEnumerator 函數,以返回 ListEnumerator 對象:
public class List{ internal object[] elements; internal int count; public ListEnumerator GetEnumerator() { return new ListEnumerator(this); }}
所建立的 ListEnumerator 對象不僅必須實現 Current 屬性和 MoveNext 方法,而且還必須維護其內部狀態,以便程式在每次執行該迴圈時都可以移到下一項。此內部狀態機器對於 List 資料結構而言比較簡單,但對於需要遞迴迴圈的資料結構(例如二叉樹)來說,該狀態機器將相當複雜。
由於實現此列舉程式模式需要開發人員投入大量的精力並編寫大量代碼,因此 C# 包含一種新的結構,使得類可以輕鬆地指示 foreach 迴圈對其內容進行迭代的方式。
定義迭代程式
由於迭代程式是 foreach 迴圈結構的邏輯對應物,因此其定義方式類似於函數:使用 foreach 關鍵字並在後面帶有一對圓括弧。在以下樣本中,程式將為 List 型別宣告一個迭代程式。迭代程式的傳回型別由使用者決定,但是由於 List 類內部儲存的是物件類型,因此以下迭代程式樣本的傳回型別為對象:
public class List{ internal object[] elements; internal int count; public object foreach() { }}
值得注意的是,實現列舉程式模式後,程式需要維護內部狀態機器以便跟蹤程式在資料結構中的位置。迭代程式具有內建狀態機器。使用新的 yield 關鍵字,程式可以將值返回到調用該迭代程式的 foreach 語句。當 foreach 語句下次迴圈並再次調用迭代程式時,此迭代程式將在上一個 yield 語句停止的位置開始執行。在以下樣本中,程式將產生三個字串類型:
public class List{ internal object[] elements; internal int count; public string foreach() { yield "microsoft"; yield "corporation"; yield "developer division"; }}
在以下樣本中,調用此迭代程式的 foreach 迴圈將執行三次,每次都會按照前三個 yield 語句指定的順序接收字串:
List list = new List();foreach(string s in list){Console.WriteLine(s);}
如果要讓程式實現迭代程式以遍曆列表中的元素,則需要使用 foreach 迴圈修改此迭代程式使其遍曆元素數組,並在每次迭代中產生數組中的每個項目:
public class List{ internal object[] elements; internal int count; public object foreach() { foreach(object o in elements) { yield o; } }}
迭代程式的工作原理
迭代程式代表所在的程式處理實現列舉程式模式的日常操作。C# 編譯器將您在迭代程式中編寫的代碼轉換成使用列舉程式模式的相應類和代碼,而無需建立類和建立狀態機器。通過這種方式,迭代程式顯著提高了開發人員的工作效率。
返回頁首
匿名方法
匿名方法是另一種實用的語言結構,它使程式員能夠建立可裝箱在委託中、並且可在以後執行的代碼塊。它們基於稱作 λ 函數的語言概念,並且類似於 Lisp 和 Python 中的對應語言概念。
建立委託代碼
委託是引用方法的對象。調用委託時,將調用它所引用的方法。以下樣本舉例說明了一個簡單的表單,其中包含列表框、文字框和按鈕三個控制項。初始化按鈕時,程式將指示其 Click 委託引用該對象中其他位置儲存的 AddClick 方法。在 AddClick 方法中,文字框的值儲存在列表框中。由於 AddClick 方法被添加到按鈕執行個體的 Click 委託中,因此每次單擊該按鈕時都將調用此方法。
public class MyForm{ ListBox listBox; TextBox textBox; Button button; public MyForm() {listBox = new ListBox(...);textBox = new TextBox(...);button = new Button(...);button.Click += new EventHandler(AddClick);} void AddClick(object sender, EventArgs e) { listBox.Items.Add(textBox.Text); }}
使用匿名方法
上一個樣本非常直觀。其中建立了一個單獨的函數,並對其進行了委託引用,每當調用此委託時,程式都會調用該函數。在該函數中,執行了一系列的可執行步驟。使用匿名方法,程式無需為該類建立整個新方法,而可以直接引用委託中包含的可執行步驟。匿名方法的聲明方法是先執行個體化一個委託,然後在執行個體化語句之後加上一對錶示執行範圍的花括弧,最後加上一個用於終止語句的分號。
在以下樣本中,程式修改委託建立語句以直接修改列表框,而不是引用代表程式來修改該列表框的函數。儲存代碼的目的是為了修改委託建立語句之後的執行範圍中的列表框。
public class MyForm{ ListBox listBox; TextBox textBox; Button button; public MyForm() {listBox = new ListBox(...);textBox = new TextBox(...);button = new Button(...);button.Click += new EventHandler(sender, e){ listBox.Items.Add(textBox.Text);};}}
請注意,“匿名”方法中的代碼是如何訪問和處理其執行範圍以外聲明的變數的。實際上,匿名方法可以引用由類和參數聲明的變數,也可以引用所在方法聲明的局部變數。
向匿名方法傳遞參數
有趣的是,“匿名”方法語句包含兩個參數,即 sender 和 e。查看 Button 類的 Click 委託的定義,您會發現委託引用的任何函數都必須包含兩個參數,第一個參數為物件類型,第二個參數為 EventArgs 類型。在第一個樣本中,程式未使用“匿名”方法,而是向 AddClick 方法傳遞了兩個參數,類型分別為對象和 EventArgs。
即使以內聯方式編寫此代碼,委託仍必須接收兩個參數。在“匿名”方法中,必須聲明兩個參數的名稱,這樣關聯的代碼塊才能使用它們。當觸發按鈕上的 Click 事件時,將調用“匿名”方法並將相應的參數傳遞給該方法。
匿名方法的工作原理
遇到“匿名”委託時,C# 編譯器會自動將其執行範圍內的代碼轉換為唯一命名類中的唯一命名函數。然後將設定儲存代碼塊的委託,以引用編譯器產生的對象和方法。調用委託時,將通過編譯器產生的方法執行“匿名”方法塊。
返回頁首
局部類型
儘管在單個檔案中維護類型的所有原始碼是物件導向編程的好方法,但有時效能約束會使得類型變大。此外,在某些情況下將類型分割成子類型所耗費的開銷是無法讓人接受的。而且,程式員經常會建立或使用應用程式來發布原始碼和修改結果代碼。遺憾的是,當再次發布原始碼時,所有現有的原始碼修改將被覆蓋。
局部類型允許您將包含大量原始碼的類型分割成多個不同的源檔案,以便於開發和維護。此外,局部類型可用於將電腦產生的類型部分與使用者編寫的類型部分分隔開,從而更易於補充或修改工具產生的程式碼。
在以下樣本中,兩個 C# 代碼檔案 File1.cs 和 File2.cs 中都定義了名為 Foo 的類。如果不使用局部類型,將會出現編譯錯誤,因為這兩個類存在於同一個命名空間中。使用 partial 關鍵字,可以指示編譯器:別處可能包含此類的其他定義。
File1.cs File2.cs public partial class Foo{public void MyFunction(){// 在此處執行操作}} public partial class Foo{ public void MyOtherFunction() { // 在此處執行操作 }}
編譯時間,C# 編譯器將收集局部類型的所有定義並將它們組合在一起。編譯器產生的結果 IL 顯示了組合而成的單個類,而不是將多個類分別作為單獨的類進行連續顯示。
返回頁首
符合標準
2001 年 12 月,歐洲電腦製造商協會 (ECMA) 將 C# 程式設計語言批准為一項標準 (ECMA 334)。此後不久,C# 標準便得到國際標準組織 (ISO) 的快速跟蹤處理,預計很快就會得到批准。C# 標準的建立是新程式設計語言發展史中的重要裡程碑,它預示著未來有望在各種作業系統平台上編寫多種實現。實際上,我們從其簡短的曆史中可以看到,許多第三方編譯器供應商和研究人員已經將它當作標準來實現並建立了自己的 C# 編譯器版本。
Microsoft 歡迎客戶對在 C# 語言中添加上面提到的功能提供反饋意見,並打算將這些功能提交給進行中的語言標準化進程。
返回頁首
可用性
下面介紹的功能將在 C# 編譯器的未來版本中實現。2003 年年初,Visual Studio .NET 的“Everett”版本將包含為完全符合 ECMA 標準而略作修改的 C# 版本。此版本不包含本文介紹的功能。Microsoft 打算在 Visual Studio 的“VS for Yukon”版本中包含這些功能,但具體的發布日期還未確定。
在接下來的幾個月中,Microsoft 將發布有關這些功能的詳細資料,包括所有規範。歡迎廣大程式員和語言設計團體就這些功能以及任何其他感興趣的語言功能提出自己的看法和反饋。您可以將電子郵件發送到 mailto:sharp@microsoft.com,與 C# 語言設計人員取得聯絡。
返回頁首
更多資訊
C# Community Web 網站:http://www.csharp.net
Visual C#(tm) Product Web 網站:http://msdn.microsoft.com/vcsharp