泛型

2020-08-08 20:25:46

泛型

csharp 有兩種不同的機制 機製來編寫跨型別複用的程式碼:繼承和泛型。但繼承的複用性來自基礎類別,而泛型的複用性是通過帶有「佔位符」的「模板」型別實現的。和繼承相比,泛型能夠提供型別的安全性,並減少型別的轉換和裝箱。

1 泛型型別 Generic Types

泛型型別中宣告的型別參數(佔位符型別)需要有泛型型別的消費者(即提供型別參數的一方)填充。下面 下麪是一個存放型別 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
            ...
        }
    }
    

2 爲什麼需要泛型

泛型是爲了程式碼能夠跨型別複用而設計的。假定我們需要一個整數棧,如果不使用泛型:

  • 方案一:爲每一個需要的元素型別寫死不同版本的類如 IntStackStringStack等導致大量的程式碼重複。

  • 方案二:用 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