C# 2.0 新特性

ilovewebservice發表於2011-03-29

C# 2.0引入了幾項語言擴充套件,其中包括分部型別 (Partial Type) 、可空型別 (Nullable Type)泛型 (Generic)、匿名方法 (Anonymous Method)、迭代器 (Iterator)。

 

分部型別淺顯易懂,看看相關文件就能理解,這裡重點談談泛型。

一、泛型的定義。

微軟文件這樣定義泛型:泛型是這樣的一些類、結構、介面和方法,它們為其儲存或使用的一個或多個型別提供佔位符。

一個簡單的泛型例子:public class List<T>

其中:T就是System.Collections.Generic.List<T>的例項所儲存型別的佔位符,當定義泛型類的例項時,必須指定這個例項所儲存的實際型別,

:List<string> ls=new List<string>();

泛型允許程式設計師將一個實際的資料型別的規約延遲至泛型的例項被建立時才確定。

二、C#2.0為什麼要推出泛型?

先看一個簡單的例子:

下面是一個簡單的 Stack 類,它將資料儲存在一個 object 陣列中,它的兩個方法 Push Pop 分別使用 object接受和返回資料:

public class Stack
{
readonly int m_Size;
int m_StackPointer = 0;
object[] m_Items;
public Stack():this(100)
{}
public Stack(int size)
{
m_Size = size;
m_Items = new object[m_Size];
}
public void Push(object item)
{
if(m_StackPointer >= m_Size)
throw new StackOverflowException();
m_Items[m_StackPointer] = item;
m_StackPointer++;
}

public object Pop()
{
m_StackPointer--;
if(m_StackPointer >= 0)
{
return m_Items[m_StackPointer];
}
else
{
m_StackPointer = 0;
throw new InvalidOperationException(
"Cannot pop an empty stack");
}
}
}

雖然使用型別 object 使得 Stack 類非常靈活但這種方式仍存在某些缺陷。例如,我們可以將任何型別的值(例如一個 Customer 例項)推入堆疊。但是,當從堆疊中檢索某個值時,必須將 Pop 方法的結果顯式強制轉換回相應的型別,這樣的程式碼編寫起來頗為繁瑣,而且在執行時執行的型別檢查會造成額外的開銷從而影響效能:

Stack stack = new Stack();
stack.Push(new Customer());
Customer c = (Customer)stack.Pop();

再比如當我們將一個值型別例如 int的值傳遞給 Push 方法時則該值將自動被裝箱。當以後檢索該 int 時,必須使用顯式型別強制轉換將其取消裝箱:

Stack stack = new Stack();
stack.Push(3);
int i = (int)stack.Pop();

這樣的裝箱和取消裝箱操作由於涉及動態記憶體分配和執行時型別檢查而額外增加了效能開銷。

上述 Stack 類還有一個潛在的問題就是我們無法對放到堆疊上的資料的種類施加限制。實際上,可能會發生這種情況:將一個 Customer 例項推入堆疊,而在檢索到該例項之後卻意外地將它強制轉換為錯誤的型別:

Stack stack = new Stack();
stack.Push(new Customer());
string s = (string)stack.Pop();

雖然上面的程式碼錯誤使用了 Stack 但是從技術角度講該程式碼可以視作是正確的編譯器不會報告編譯時錯誤。這個問題在該程式碼被執行之前不會暴露出來,但在執行該程式碼時會引發 InvalidCastException

顯然如果能夠指定元素型別Stack 類將能夠從中受益。有了泛型,我們便可以做到這一點。泛型為使用c#語言編寫物件導向程式增加了極大的效力和靈活性。不會強行對值型別進行裝箱和拆箱,或對引用型別進行向下強制型別轉換,所以效能得到提高。泛型為開發者提供了一種高效能的程式設計方式,能夠提高程式碼的重用性,並允許開發者編寫非常優雅的解決方案。

 

下面的示例宣告一個帶有型別形參 T 的泛型 Stack 類。型別形參在 < > 分隔符中指定並放置在類名後。Stack<T> 的例項的型別由建立時所指定的型別確定,該例項將儲存該型別的資料而不進行資料型別轉換。這有別於同 object 之間的相互轉換。型別形參 T 只起佔位符的作用,直到在使用時為其指定了實際型別。注意,這裡的 T 用作內部項陣列的元素型別、傳遞給 Push 方法的引數型別和 Pop 方法的返回型別:

public class Stack<T>
{
T[] items;
int count;

public void Push(T item) {...}

public T Pop() {...}
}

在使用泛型類 Stack<T> 將指定用於替換 T 的實際型別。在下面的示例中,指定了 int 作為 T 的型別實參 (type argument)

Stack<int> stack = new Stack<int>();
stack.Push(3);
int x = stack.Pop();

Stack<int> 型別稱為構造型別 (constructed type)。在 Stack<int> 型別中出現的每個 T 都被替換為型別實參 int。在建立 Stack<int> 的例項後items 陣列的本機儲存是 int[] 而不是 object[]。無疑,這比非泛型的 Stack提供了更高的儲存效率。同樣,Stack<int> Push Pop 方法所操作的也是 int 型別的值。如果將其他型別的值推入堆疊則產生編譯時錯誤。而且在檢索值時也不再需要將它們顯式強制轉換為原始型別。

泛型提供了強型別機制這意味著如果將一個 int 值推入 Customer 物件的堆疊將導致錯誤。正如 Stack<int> 僅限於操作 int 值一樣,Stack<Customer> 僅限於操作 Customer 物件,編譯器將對下面示例中的最後兩行報告錯誤:

Stack<Customer> stack = new Stack<Customer>();
stack.Push(new Customer());
Customer c = stack.Pop();
stack.Push(3);                   // Type mismatch error
int x = stack.Pop();             // Type mismatch error

泛型型別宣告可以含有任意數目的型別形參。上面的 Stack<T> 示例只有一個型別形參,而一個泛型 Dictionary 類可能具有兩個型別形參,一個用於鍵的型別,一個用於值的型別:

public class Dictionary<K,V>
{
public void Add(K key, V value) {...}

public V this[K key] {...}
}

在使用上述 Dictionary<K,V> 必須提供兩個型別實參

Dictionary<string,Customer> dict = new Dictionary<string,Customer>();
dict.Add("Peter", new Customer());
Customer c = dict["Peter"];

三、泛型的應用。

 •輕量級的結構中使用泛型

public struct Point<T>
{
public T X;
public T Y;
}
Point<int> point;
point.X = 1;
point.Y = 2;
Point<double> point;
point.X = 1.2;
point.Y = 3.4;
Default關鍵字
public T Pop()
{
m_StackPointer--;
if(m_StackPointer >= 0)
{
return m_Items[m_StackPointer];
}
else
{
m_StackPointer = 0;
throw new InvalidOperationException(
"Cannot pop an empty stack");
}
}

default關鍵字
• 假設您不希望在堆疊為空時引發異常,而是希望返回堆疊中儲存的型別的預設值
– 值型別返回0(整型、列舉和結構)
– 引用型別返回null
• 如果是基於object,則可以簡單的返回null
public T Pop()
{
m_StackPointer--;
if(m_StackPointer >= 0)
{
return m_Items[m_StackPointer];
}
else
{
m_StackPointer = 0;
return default(T);
}
}

多個泛型
•單個型別可以定義多個泛型
class Node<K,T>
{
public K Key;
public T Item;
public Node<K,T> NextNode;
public Node()
{
Key = default(K);
Item = defualt(T);
NextNode = null;
}
public Node(K key,T item,Node<K,T> nextNode)
{
Key = key;
Item = item;
NextNode = nextNode;
}
}
public class LinkedList<K,T>
{
Node<K,T> m_Head;
public LinkedList()
{
m_Head = new Node<K,T>();
}
public void AddHead(K key,T item)
{
Node<K,T> newNode = new Node<K,T>
(key,item,m_Head.NextNode);
m_Head.NextNode = newNode;
}
}

泛型別名
using List = LinkedList<int,string>;
class ListClient
{
static void Main(string[] args)
{
List list = new List();
list.AddHead(123,"AAA");
}
}
• 在檔案頭部使用using為特定型別取別名
• 別名作用範圍是整個檔案
泛型約束-概述
•為什麼要泛型約束?
•先看以下示例:
public class LinkedList<K,T>
{
T Find(K key)
{...}
public T this[K key]
{
get{return Find(key);}
}
}
LinkedList<int,string> list = new
LinkedList<int,string>();
list.AddHead(123,"AAA");
list.AddHead(456,"BBB");
string item = list[456];
Debug.Assert(item == "BBB");

• Find方法實現
T Find(K key)
{
Node<K,T> current = m_Head;
while(current.NextNode != null)
{
if(current.Key == key) //Will not compile
break;
else
current = current.NextNode;
}
return current.Item;
}
因為編譯器不知道K(例項化時的實際型別)是否支援== 運算子。例
如,預設情況下,結構不提供這樣的實現。
public interface IComparable
{
int CompareTo(object obj);
}
if(current.Key.CompareTo(key) == 0)
if(((IComparable)(current.Key)).CompareTo(key) == 0)

泛型約束-派生約束
• Find方法的解決方法
– 泛型增加一個派生約束(Derivation Constraints)
public class LinkedList<K,T> where K : IComparable
{
T Find(K key)
{
Node<K,T> current = m_Head;
while(current.NextNode != null)
{
if(current.Key.CompareTo(key) == 0)
break;
else
current = current.NextNode;
}
return current.Item;
}
//Rest of the implementation
}
1.Where關鍵字
2.K:IComparable表示K只接受實現IComparable介面的
型別。儘管如此,還是無法避免傳入值型別的K所帶來的裝箱問題。
System.Collections.Generic 名稱空間定義了泛型介面
IComparable<T>:
public interface IComparable<T>
{
int CompareTo(T other);
bool Equals(T other);
}
泛型約束-派生約束
• 在C#2.0中,所有的派生約束必須放在類的實際派生列表之後,如:
• 通常,只須在需要的級別定義約束。比如:在Node節點定義
IComparable<K>約束是沒有意義的。如果一定要在Node上定義
IComparable<K>約束,則LinkedList上也要定義此約束
public class LinkedList<K,T> : IEnumerable<T> where K : IComparable<K>
{...}
class Node<K,T> where K : IComparable<K>
{...}
public class LinkedList<KeyType,DataType>
where KeyType : IComparable<KeyType>
{
Node<KeyType,DataType> m_Head;
}

迭代器是 C# 2.0 中的新功能。迭代器是方法、get 訪問器或運算子,它使您能夠在結構中支援 foreach 迭代,而不必實現整個 IEnumerable 介面。您只需提供一個迭代器,即可遍歷類中的資料結構。當編譯器檢測到迭代器時,它將自動生成 IEnumerableIEnumerable<T> 介面的 CurrentMoveNextDispose 方法。

迭代器概述

  • 迭代器是可以返回相同型別的值的有序序列的一段程式碼。

  • 迭代器可用作方法、運算子或 get 訪問器的程式碼體。

  • 迭代器程式碼使用 yield return 語句依次返回每個元素。yield break 將終止迭代。

  • 可以在類中實現多個迭代器。每個迭代器都必須像任何類成員一樣有唯一的名稱,並且可以在 foreach 語句中被客戶端程式碼呼叫,如下所示:foreach(int x in SampleClass.Iterator2){}

  • 迭代器的返回型別必須為 IEnumerableIEnumeratorIEnumerable<T> 或 IEnumerator<T>。

yield 關鍵字用於指定返回的值。到達 yield return 語句時,會儲存當前位置。下次呼叫迭代器時將從此位置重新開始執行。

迭代器對集合類特別有用,它提供一種簡單的方法來迭代不常用的資料結構(如二進位制樹)。

相關文章