csharp 有兩種不同的機制 機製來編寫跨型別複用的程式碼:繼承和泛型。但繼承的複用性來自基礎類別,而泛型的複用性是通過帶有「佔位符」的「模板」型別實現的。和繼承相比,泛型能夠提供型別的安全性,並減少型別的轉換和裝箱。
泛型型別中宣告的型別參數(佔位符型別)需要有泛型型別的消費者(即提供型別參數的一方)填充。下面 下麪是一個存放型別 T
範例的泛型棧型別 Stack<T>
。Stack<T>
宣告瞭單個型別參數 T
:
public class Stack<T>
{
int position;
T[] data = new T[100];
public void Push (T obj) => data[position++] = obj;
public T Pop() => data[--position];
}
使用 Stack<T>
的方式如下:
var stack = new Stack<int>();
stack.Push (5);
stack.Push (10);
int x = stack.Pop(); // x is 10
int y = stack.Pop(); // y is 5
Stack<T>
用參數型別 int
填充 T
,這會在執行時隱式建立一個型別:Stack<int>
。若試圖將一個字串加入 Stack<int>
中則會產生一個編譯時錯誤。Stack<int>
具有如下定義:
public class 類名
{
int position;
int[] data = new int[100];
public void Push (int obj) => data[position++] = obj;
public int Pop() => data[--position];
}
Stack<T>
開放型別 Open Type
Stack<int>
封閉型別 Closed Type
在執行時,所有的泛型範例都是封閉的,佔位符已經被型別填充,意味着:
var stack = new Stack<T>(); // Illegal: What is T?
只有在類或者方法的內部,T
纔可以定義爲型別參數:
public class Stack<T>
{
...
public Stack<T> Clone()
{
Stack<T> clone = new Stack<T>(); // Legal
...
}
}
泛型是爲了程式碼能夠跨型別複用而設計的。假定我們需要一個整數棧,如果不使用泛型:
方案一:爲每一個需要的元素型別寫死不同版本的類如 IntStack
、StringStack
等導致大量的程式碼重複。
方案二:用 object
作爲元素型別的棧:
public class ObjectStack
{
int position;
object[] data = new object[10];
public void Push (object obj) => data[position++] = obj;
public object Pop() => data[--position];
}
但 ObjectStack
不會像寫死的 IntStack
類一樣只處理整數元素。而且 ObjectStack
需要用到裝箱和向下型別轉換,而這些都不能在編譯時進行檢查:
// Suppose we just want to store integers here:
ObjectStack stack = new ObjectStack();
stack.Push ("s"); // Wrong type, but no error! 編譯時不會報錯,但在執行時會報錯
int i = (int)stack.Pop(); // Downcast - runtime error