C#2.0,在2005年已經可以使用了,它有一些主要的新功能。這樣使得目前使 用的一些最好的實際經驗可能會有所改變,這也會隨著下一代工具的發布而修改 。盡管目前你還可以不使用這些功能,但你應該這些做些准備。
當 Visual Studio .net2005發布後,會得到一個新的開發環境,升級的C#語言。附 加到這門語言上的內容確實讓你成為更有工作效率的開發者:你將可以寫更好重 用的代碼,以及用幾行就可以寫出更高級的結構。總而言之,你可以更快的完成 你的工作。
C#2.0有四個大的新功能:范型,迭代,匿名方法,以及部分類型。這些新功能的主要目的就是增強你,做為一個C#開發的開發效率。這一原 則會討論其中的三個新功能,以及為什麼你要為些做准備。與其它新功能相比,范型在對你如何開發軟件上有更大的影響。范型並不是C#的特殊產物。為了實現 C#的范型,MS已經擴展了CLR以及MS的中間語言(MSIL)。C#,托管C++,以及 VB.Net都將可以使用范型。J#也將可以使用這些功能。
范型提供了一種 “參數的多太”,對於你要利用同一代碼來創建一系列相似的類來說 ,這是一個很神奇的方法。當你為范型參數供一個特殊的類型,編譯器就可以生 成不同版本的類。你可以使用范型來創建算法,這些算法與參數化的結構相關的 ,它們在這些結構上實行算法。你可以在.net的Collection名字空間中找到很多 候選的范型:HashTables, ArrayList,Queu,以及Stack都可以存儲不同的對象 而不用管它們是如何實現的。這些集合對於2.0來說都是很好的范型候選類型, 這在System.Collections.Generic存在范型,而且這些對於目前的類來說都一個 副本。C#1.0是存儲一個對System.Obejct類型的引用,盡管當前的設計對於這一 類型來說是可重用的,但它有很多不足的地方,而且它並不是一個類型安全的。 考慮這些代碼:
ArrayList myIntList = new ArrayList( );
myIntList.Add(32 );
myIntList.Add(98.6 );
myIntList.Add ("Bill Wagner" );
這編譯是沒問題的,但這根本無 法表明你的意思。你是真的想設計這樣一個容器,用於存儲總完全不同的元素嗎 ?或者你是想在一個受到限制的語言上工作嗎?這樣的實踐意味著當你移除集合 裡的元素時,你必須添加額外的代碼來決定什麼樣的對象事先已經存在這樣的集 合中。不管什麼情況,你須要從 System.Object強制轉化這些元素到實際的你要 的類型。
這還不只,當你把它們放到1.0版(譯注:是1.0,不是1.1)的集 合中時,值類型的開銷更特殊。任何時候,當你放到個值類型數據到集合中時, 你必須對它進行裝箱。而當你在從集合中刪除它時,你又會再開銷一次。這些損 失雖然小,但對於一個有幾千元素的大集合來說,這些開銷就很快的累積起來了 。通過為每種不同的值類型生成特殊的代碼,范型已經消除了這些損失。
如果你熟悉C++的模板,那麼對於C#的范型就不存在什麼問題了,因為這 些從語法上講是非常相似的。范型的內部的工作,不管它是怎產的,卻是完全不 同的。讓我們看一些簡單的例子來了解東西是如何工作的,以及它是如何實現的 。考慮下面某個類的部份代碼:
public class List
{
internal class Node
{
internal object val;
internal Node next;
}
private Node first;
public void AddHead( object t )
{
// ...
}
public object Head()
{
return first.val;
}
}
這些代碼在集合中存儲System.Object的引用,任何時候你都 可以使用它,在你訪問集合是,你必須添加強制轉換。但使用C#范型,你可以這 樣定義同樣的類:
public class List < ItemType >
{
private class Node < ItemType >
{
internal ItemType val;
internal Node < ItemType > next;
}
private Node < ItemType > first;
public void AddHead( ItemType t )
{
// ...
}
public ItemType Head( )
{
return first.val;
}
}
你可以用對象來代替ItemType, 這個參數類型是用於 定義類的。C#編譯器在實例化列表時,用恰當的類型來替換它們。例如,看一下 這樣的代碼:
List < int > intList = new List < int >();
MSIL可以精確的確保intList中存儲的是且只是整數 。比起目前你所實現的集合(譯注:這裡指C#1.1裡的集合),創建的范型有幾個 好處,首先就是,如果你試圖把其它任何不是整型的內容放到集合中時,C#的編 譯器會給出一個編譯錯誤,而現今,你須要通過測試運行時代碼來附加這些錯誤 。
在C#1.0裡,你要承擔裝箱和拆箱的一些損失,而不管你是從集合中移 出或者是移入一個值類型數據,因為它們都是以System.Object的引用形式存在 的。使用范型,JIT編譯器會為集合創建特殊的實例,用於存儲實際的值類型。 這樣,你就不用裝箱或者拆箱了。還不只這些,C#的設計者還想避免代碼的膨脹 ,這在C++模板裡是相關的。為了節約空間,JIT編譯器只為所有的引用類型生成 一個版本。這樣可以取得一個速度和空間上的平衡,對每個值類型(避免裝箱)會 有一個特殊的版本呢,而且引用類型共享單個運行時的版本用於存儲 System.Object (避免代碼膨脹)。在這些集合中使用了錯誤的引用時,編譯器還 是會報告錯誤。
為了實現范型,CLR以及MSIL語言經歷了一些修改。當你 編譯一個范型類時,MSIL為每一個參數化的類型預留了空間。考慮下面兩個方法 的申明MSIL:
To implement generics, the CLR and the MSIL language undergo some changes. When you compile a generic class, MSIL contains placeholders for each parameterized type. Consider these two method declarations in MSIL:
.method public AddHead (!0 t) {
}
.method public !0 Head () {
}
!0 就是 一個為一個類型預留的,當一個實際的實例被申明和創建時,這個類型才創建。 這就有一種替換的可能:
.method public AddHead (System.Int32 t) {
}
.method public System.Int32 Head () {
}
類似的,變化的實例包含特殊的類。前面的為整型的申 明就變成了為樣:
.locals (class List<int>)
newobj void List<int>::.ctor ()
這展示了C#編譯器 以及JIT編譯是如何為一個范型而共同工作的。C#編譯器生成的MSIL代碼為每一 個類型預留了一個空間,JIT編譯器在則把這些預留的類型轉換成特殊的類型, 要麼是為所有的引用類型用System.Object,或者對值類型言是特殊的值類型。 每一個范型的變量實例化後會帶有類型信息,所以C#編譯器可以強制使用類型安 全檢測。
范型的限制定義可能會對你如何使用范型有很大的影響。記住 ,在CLR還沒有加載和創建這些進行時實例時,用於范型運行時的特殊實例是還 沒有創建的。為了讓MISL可以讓所有的范型實例都成為可能,編譯器須要知道在 你的范型類中使用的參數化類型的功能。C#是強制解決這一問題的。在參數化的 類型上強制申明期望的功能。考慮一個二叉樹的范型的實現。二叉樹以有序方式 存儲對象,也就是說,二叉樹可以只存儲實現了IComparable的類型。你可以使 用約束來實現這一要求:
public class BinaryTree < ValType > where
ValType : IComparable < ValType >
{
}
使用這一定義,使用BinaryTree的實例,如何使用了 一個沒有實現IComparable 接口的類型時是不能通過編譯的。你可以指明多個約 束。假設你想限制你的BinaryTree成為一個支持ISerializable的對象。你只用 簡單的添加更多的限制就行了。注意這些接口以及限制可以在范型上很好的使用 :
public class BinaryTree < ValType > where
ValType : IComparable < ValType > ,
ValType : ISerializable
{
}
你可以為每個個實例化的類型指 明一個基類以及任何數量的接口集合。另外,你可以指明一個類必須有一個無參 數的構造函數。
限制同樣可以提供一些更好的好處:編譯器可以假設這 些在你的范型類中的對象支持指定列表中的某些特殊的接口(或者是基類方法)。 如何不使用任何限制時,編譯器則只假設類型滿員System.Object中定義的方法 。你可能須要添加強制轉換來使用其它的方法,不管什麼時候你使用一個不在 System.Object對象裡的方法時,你應該在限制集合是寫下這些需求。
約 束指出了另一個要盡量使用接口的原因(參見原則19):如果你用接口來定義你的 方法,它會讓定義約束變得很簡單。
迭代也是一個新的語法,通常習慣 上用於少代碼。想像你創建一些特殊的新容器類。為了支持你的用戶,你須要在 集合上創建一些方法來支持逆轉這些集合以及運行時對象。
目前,你可 能通過創建一個實現IEnumerator了的類來完成這些。IEnumerator 包含兩個方 法,Reset和MoveNextand,以及一個屬性:Current。另外,你須要添加 IEnumerable來列出集合上所有實現了的接口,以及它的GetEnumerator方法為你 的集合返回一個IEnumerator。在你寫寫完了以後,你已經寫了一個類以及至少 三個額外的函數,同時在你的主類裡還有一些狀態管理和其它方法。為了演示這 些,目前你須要寫這樣一頁的代碼,來處理列表的枚舉:
public class List : IEnumerable
{
internal class ListEnumerator : IEnumerator
{
List theList;
int pos = -1;
internal ListEnumerator( List l )
{
theList = l;
}
public object Current
{
get
{
return theList [ pos ];
}
}
public bool MoveNext( )
{
pos++;
return pos < theList.Length;
}
public void Reset( )
{
pos = -1;
}
}
public IEnumerator GetEnumerator()
{
return new ListEnumerator( this );
}
// Other methods removed.
}
在這一方面上,C#2.0用yield關鍵字添加了新 的語法,這讓在寫這些迭代時變得更清楚。對於前面的代碼,在C#2.0裡可是樣 的:
public class List
{
public object iterate()
{
int i=0;
while ( i < theList.Length ( ) )
yield theList [ i++ ];
}
// Other methods removed.
}
yield語句讓你只用6行代 碼足足替換了近30行代碼。這就是說,BUG少了,開發時間也少了,以及少的代 碼維護也是件好事。
在內部,編譯器生成的MSIL與目前這30行代碼是一 致的。編譯器為你做了這些,所以你不用做 。編譯器生成的類實現了 IEnumerator 接口,而且添加了你要支持的接口到列表上。
最後一個新 功能就是部分類型。部分類型讓你要吧把一個C#類的實現分開到多個文件中。你 可能很少這樣做,如果有,你自己可以在日常的開發中,使用這一功能來創建多 源文件。MS假設這一修改是讓C#支持IDE以及代碼生成器。目前,你可以在你的 類中使用region來包含所以VS.net為你生成的代碼。而將來(譯注:指C#2.0), 這些工具可以創建部份類而且取代這些代碼到分開的文件中。
使用這一 功能,你要為你的類的申明添加一個partial關鍵字:
public partial class Form1
{
// Wizard Code:
private void InitializeComponent()
{
// wizard code...
}
}
// In another file:
public partial class Form1
{
public void Method ()
{
// etc...
}
}
部分類型有一些限制。類只與源相關的,不管是一個文件還是多 個源文件,它們所生成的MSIL代碼沒有什麼兩樣。你還是要編譯一個完整類的所 有的文件到同樣的程序集中,而且沒有什麼自動的方法來確保你已經添加了一個 完整類的所有源文件到你的編譯項目中。當你把一個類的定義從一文件分開到多 個文件時,你可能會以引發很多問題,所以建議你只用IDE生成部分類型功能。 這包含form,正如我前面介紹的那樣。VS.Net同樣為DataSet(參見原則41)也生 成部分類型,還有web服務代理,所以你可以添加你自己的成員到這些類中。
我沒有太多的談到關於C#2.0的功能,因為添加的與目前的編碼有一些沖 突。你可以使用它,通過范型讓你自己的類型變得簡單,而定義接口可以描述行 為:這些接口可以做為約束。新的迭代語法可以提供更高效的方法來實現枚舉。 你可以通過這一新語法,快速簡單的取代嵌套枚舉。然而,用戶擴展類可能不會 是簡單的取代。現在開發你自己的代碼,在顯而易見的地方利用這些功能,而且 在用C#2.0升級你已經存在的代碼時,它會變得更容易,工作量也會變得最少。
返回教程目錄