假設需要一個兩個整形變數交換的函數,我們很快就可以嗒嗒嗒嗒的敲出下面的 Swap 函數:
void Swap(ref int lhs, ref int rhs)
{
int temp = lhs;
lhs = rhs;
rhs = temp;
}
隨著項目進展,我們發現,需要用到 Swap 函數的不僅是整形,變數 還有字串,於是我們我又嗒嗒嗒嗒的重載 Swap 函數如下:
void Swap(ref string lhs, ref string rhs)
{
string temp = lhs;
lhs = rhs;
rhs = temp;
}
接下來的開發中,我們又發現還有自訂的結構體,類等等等等都要用到 Swap 函數。如果我們為每一種類型都實現一個相應的 Swap 函數的話,各個版本的 Swap 函數資料類型不同外,其它完完全全一樣。也就是說,項目中存在大量的代碼重複。能不能之實現一個能夠適用於不同資料類型的 Swap 函數,消除這種代碼冗餘,從而減少工作量,提高開發效率呢?
類型轉換
在 C# 中 所有的類型都直接或間接的繼承自 System.Object 類。換句話說,所有的類型都可以轉換為 Object 類。這為我們前面的問題提供了一個解決方案——實現一個以 Object 為型別參數的 Swap 函數。其實現如下:
void Swap(ref object lhs, ref object rhs)
{
object temp = lhs;
lhs = rhs;
rhs = temp;
}
調用的代碼如下:
//a, b 為要傳入 Swap 函數的變數
object objA = a;
object objB = b;
Swap(ref objA, ref objB);
//T 為變數 a 和 b 的資料類型
a = (T)objA;
b = (T)objB;
這一實現利用類型轉換有效重用了 Swap 的代碼,但有兩點不足。
首先是效能問題。每次調用 Swap 函數前,需要對其參數進行一次向上的轉型;調用完之後,又要對其進行一次向下的轉型。如果需要多次調用 Swap 函數(比如在一個很大的迴圈中),轉型帶來的開銷是想當可觀的,特別是當參數為實值型別的時候。
第二是,無法提供編譯時間類型檢查。下面的例子雖然能通過編譯,但運行時會出現異常:
string a = “This is a string”;
int b = 0;
object objA = a;
object objB = b;
Swap(ref objA, ref objB); //可以編譯
a = (string)objA; //出現運行時異常
b = (ing)objB;
針對以上兩點不足,C# 2.0 提出了泛型。
泛型
泛型是C# 2.0 提供的延遲類和函數中資料類型的定義,直到客戶代碼聲明或執行個體化該資料類型。
泛型版的 Swap 函數實現如下
void Swap<T>(ref T lhs, ref T rhs)
{
T temp = lhs;
lhs = rhs;
rhs = temp;
}
泛型集合中的 <T>是obj類型
上例中的型別參數 T 可以執行個體化為任意資料類型。相對於通過類型轉換重用 Swap 函數,它且不需要類型轉換,有效提高效能。而且,它還能提供編譯時間類型檢查。調用文法與普通函數調用完全一樣。
泛型的優勢
從上面例子可以看出,使用泛型具有如下三點優勢:
? 避免重複代碼,最大化代碼重用
? 避免無謂的類型轉換,提高效能
? 提供編譯時間類型檢查,具有型別安全
-
C# code
-
// 在三角符號裡寫入型別參數T
public class GenericList<T>
{
// Node為非泛型類,作為GenericList<T>的嵌套類
private class Node
{
// 在非泛型建構函式中使用T
public Node(T t)
{
next = null;
data = t;
}
private Node next;
public Node Next
{
get { return next; }
set { next = value; }
}
// T作為私人成員的資料類型
private T data;
// T作為屬性的傳回型別
public T Data
{
get { return data; }
set { data = value; }
}
}
private Node head;
// 建構函式
public GenericList()
{
head = null;
}
// T 作為方法的參數類型
public void AddHead(T t)
{
Node n = new Node(t);
n.Next = head;
head = n;
}
public IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}
}
下面的程式碼範例示範用戶端代碼如何使用泛型 GenericList <T> 類來建立整數列表。只需更改型別參數,即可方便地修改下面的程式碼範例,建立字串或任何其他自訂類型的列表:
-
C# code
-
class TestGenericList
{
static void Main()
{
// int 是類型變數
GenericList<int> list = new GenericList<int>();
for (int x = 0; x < 10; x++)
{
list.AddHead(x);
}
foreach (int i in list)
{
System.Console.Write(i + " ");
}
System.Console.WriteLine("\n完成");
}
}
泛型也在用在類裡,可以對參數進行約束而對於new約束而言有點特殊
public class Dictionary<K,V> where K: IComparable
{
public void Add(K key, V value)
{
if (key.CompareTo(x) < 0) {}
}
}
這樣就保證了任何為K型別參數提供的類型都實現了IComparable介面。所以我們的key就可以使用CompareTo方法了。
如果我們在使用時提供了沒有實現IComparable介面的類型,就會出現編譯時間錯誤。
-
對於new()約束,大家可能有一個誤解,以為使用了new約束之後,在建立對象時與非泛型的版本是一致的:
publicclassTester<T>
whereT:new()
{
publicTester()
{
t=newT();//等同於非泛型版本的new?例如objecto=newobject();?
}
privateTt;
}
事實上,使用new關鍵字的作用只是讓編譯器在泛型執行個體化之處,檢查所綁定的泛型參數是否具有無參建構函式:
Tester<SomeType>t=newTester<SomeType>();
//此處編譯器會檢查SomeType是否具有無參建構函式。若沒有則會有compileerror