簡介
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 類型系統的裝箱和取消裝箱功能,程序員可以將引用類型和值類型存儲到對象類型的變量中。但是,對於引用類型、值類型與基本對象類型之間的轉換,還有一些性能缺陷。
為了說明這一點,以下代碼示例創建了一個簡單的 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 方法獲取的對象類型顯式轉換成值類型,該過程稱作取消裝箱:
Stack s = new Stack();
s.Push(3);
int i = (int) s.Pop();
值類型和引用類型之間的裝箱和取消裝箱操作非常繁重。
而且,在當前的實現中,無法限制堆棧中放置的數據類型。實際上,可以先創建堆棧,然後將 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 對象存儲該類型。要實現此操作,只需實例化 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> where
KeyType : 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:[email protected],與 C# 語言設計人員取得聯系。