Effective C# 爲C# 2.0做準備

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.
}


 

 

 

在內部,編譯器生成的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升級你已經存在的代碼時,它會變得更容易,工作量也會變得最少。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章