跳轉到內容

C# 程式設計/泛型

來自華夏公益教科書,自由的教科書

泛型是 C# 語言和公共語言執行時 (CLR) 2.0 版本後引入的新特性。泛型在 .NET Framework 中引入了型別引數的概念,它使您可以設計在類或方法被客戶端程式碼宣告和例項化之前推遲一個或多個型別規範的類和方法。泛型的最常見用途是建立集合類。泛型型別的引入是為了 最大化程式碼重用最大化型別安全效能[1]

泛型類

[編輯 | 編輯原始碼]

在某些情況下,您需要建立一個類來管理某種型別的物件,而無需對其進行修改。在沒有泛型的情況下,建立這種類的通常方法(高度簡化)是這樣的

public class SomeObjectContainer
{
    private object _obj;

    public SomeObjectContainer(object obj)
    {
        this._obj = obj;
    }

    public object GetObject()
    {
        return this._obj;
    }
}

它的用法是

class Program
{
    static void Main(string[] args)
    {
        SomeObjectContainer container = new SomeObjectContainer(25);
        SomeObjectContainer container2 = new SomeObjectContainer(5);

        Console.WriteLine((int) container.GetObject() + (int) container2.GetObject());
        Console.ReadKey(); // wait for user to press any key, so we could see results
    }
}

注意,我們必須每次想要從這樣的容器中獲取物件時都強制轉換為我們選擇的原始資料型別(在本例中為 int)。在像這樣的小型程式中,一切都清楚。但在更復雜的程式中,如果程式的不同部分存在多個容器,我們必須注意容器應該是 int 型別,而不是其他資料型別,因為在這種情況下,將丟擲 InvalidCastException

此外,如果我們選擇的原始資料型別是值型別,例如 int,那麼每次訪問集合的元素時,我們都會因 C# 的 自動裝箱 特性而造成效能損失。

但是,我們可以用 try - catch 塊包圍每個不安全區域,或者我們可以為需要的每個資料型別建立一個單獨的“容器”,以避免強制轉換。雖然這兩種方法都可以工作(並且多年來一直有效),但現在已經沒有必要了,因為泛型提供了更優雅的解決方案。

為了使我們的“容器”類支援任何物件並避免強制轉換,我們將每個以前的 object 型別替換為一個新名稱,在本例中為 T,並在類名後新增 <T> 標記以指示此 T 型別是泛型/任何型別。

注意:您可以選擇任何名稱,併為類使用多個泛型型別,例如 <genKey, genVal>
public class GenericObjectContainer<T>
{
    private T _obj;

    public GenericObjectContainer(T obj)
    {
        this._obj = obj;
    }

    public T getObject()
    {
        return this._obj;
    }
}

沒有太大的區別,這導致了簡單而安全的用法

class Program
{
    static void Main(string[] args)
    {
        GenericObjectContainer<int> container = new GenericObjectContainer<int>(25);
        GenericObjectContainer<int> container2 = new GenericObjectContainer<int>(5);
        Console.WriteLine(container.getObject() + container2.getObject());

        Console.ReadKey(); // wait for user to press any key, so we could see results
    }
}

泛型確保您只為“容器”指定一次型別,避免了前面提到的問題,以及對 struct 的自動裝箱。

雖然這個例子遠非實用,但它確實說明了泛型在某些情況下很有用

  • 您需要在一個類中保留單一型別的物件
  • 您不需要修改物件
  • 您需要以某種方式操作物件
  • 您希望將“值型別”(如 intshortstring 或任何自定義 struct)儲存在集合類中,而不會在每次操作儲存的元素時都產生自動裝箱的效能損失。

泛型介面

[編輯 | 編輯原始碼]

泛型介面接受一個或多個型別引數,類似於泛型類

public interface IContainer<T>
{
    T GetObject();
    void SetObject(T value);
}

public class StringContainer : IContainer<string>
{
    private string _str;
    
    public string GetObject()
    {
        return _str;
    }
    
    public void SetObject(string value)
    {
        _str = value;
    }
}

public class FileWithString : IContainer<string>
{
    ...
}

class Program
{
    static void Main(string[] args)
    {
        IContainer<string> container = new StringContainer();
        
        container.SetObject("test");

        Console.WriteLine(container.GetObject());
        container = new FileWithString();

        container.SetObject("another test");

        Console.WriteLine(container.GetObject());
        Console.ReadKey();
    }
}

泛型介面在可能存在某個類的多個實現時很有用。例如,來自 System.Collections.Generic 名稱空間的 List<T> 類(將在下面討論)和 LinkedList<T> 類都實現了 IEnumerable<T> 介面。List<T> 有一個建構函式,它根據實現 IEnumerable<T> 的現有物件建立一個新列表,因此我們可以編寫以下程式碼

LinkedList<int> linkedList = new LinkedList<int>();

linkedList.AddLast(1);
linkedList.AddLast(2);
linkedList.AddLast(3);
// linkedList now contains 1, 2 and 3.

List<int> list = new List<int>(linkedList);

// now list contains 1, 2 and 3 as well!

泛型方法

[編輯 | 編輯原始碼]

泛型方法與泛型類和介面非常相似

using System;
using System.Collections.Generic;

public static bool ArrayContains<T>(T[] array, T element)
{
    foreach (T e in array)
    {
        if (e.Equals(element))
        {
            return true;
        }
    }

    return false;
}

此方法可用於搜尋任何型別的陣列

using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        string[] strArray = { "string one", "string two", "string three" };
        int[] intArray = { 123, 456, 789 };
        
        Console.WriteLine(ArrayContains<string>(strArray, "string one")); // True
        Console.WriteLine(ArrayContains<int>(intArray, 135)); // False
    }
}

型別約束

[編輯 | 編輯原始碼]

可以使用 where 關鍵字在任何泛型類、介面或方法中指定一個或多個型別約束。以下示例顯示了所有可能的型別約束

public class MyClass<T, U, V, W>
    where T : class,        // T should be a reference type (array, class, delegate, interface)
        new()               // T should have a public constructor with no parameters
    where U : struct        // U should be a value type (byte, double, float, int, long, struct, uint, etc.)
    where V : MyOtherClass, // V should be derived from MyOtherClass
        IEnumerable<U>      // V should implement IEnumerable<U>
    where W : T,            // W should be derived from T
        IDisposable         // W should implement IDisposable
{
    ...
}

這些型別約束通常是必要的,以便

  1. 建立一個新的泛型型別例項(new())約束
  2. 對泛型型別的變數使用 foreachIEnumerable<T> 約束)
  3. 對泛型型別的變數使用 usingIDisposable 約束)
  1. "Generics (C# Programming Guide)". http://msdn.microsoft.com/en-us/: msdn. Retrieved 2011-08-09. {{cite web}}: External link in |location= (help)
華夏公益教科書