Generics

泛型、裝箱(Boxing)與拆箱(Unboxing)

前提概要

在開始講泛型之前,先說明裝箱(boxing)拆箱(unboxing)的原理,這跟泛型的推出,有很大的原因。

型別主要分成兩大類,數值型別與參考型別,所有數值型別都是繼承System.ValueType,而System.ValueType繼承自System.Object,這代表所有數值型別都可以隱含轉換成參考型別System.Object,此轉換的過程就稱之為裝箱, 反之則為拆箱。

接下來看一下,裝箱的過程
  1. 在累堆(heap)上分配一個記憶體空間,大小等於需要裝箱的數值型別物件的大小加上兩個參考型別都擁有的成員:型別物件指標和同步區塊索引參考(sync block index)
  2. 把堆疊(stack)上的數值型別物件複製到累堆(heap)上新分配的物件
  3. 回傳一個指向累堆上新物件的參考位址,並且儲存到堆疊上,那個被裝箱的數值型別物件裡

也因為這些過程,其實裝箱跟拆箱,對效能是有一定的影響,值得注意。

拆箱裝箱程式碼:
object o = new object();

int i = 1;

// 裝箱
o = i;

// 拆箱
int j = (int)o;

而在泛型還沒有推出之前,會一直觸發裝箱與拆箱最典型的例子就是ArrayList,因為ArrayList是一個物件的容器,只能存object,而且因為無法知道object內容物存的是什麼型別,所以使用上也非常麻煩。

var array = new ArrayList();
// 裝箱
array.Add(1);
// 裝箱
array.Add(2);
array.Add("hi");

// 拆箱
int i = (int)array[0];
// 拆箱
int i2 = (int)array[1];
所以泛型的推出,就解決了以上所說的問題。

 

泛型

  • 設計一個類別時或方法時,先不定義參數的型別,而是宣告類別或使用方法時,才指定型別的一種概念。

  • 最常使用在集合的情境 (List<T>)

 

就先以最常用的list<T>為例子,在我使用的集合的時候,才指定要傳入的型別,這就是第一點所說,在使用時,才指定型別。
List<int> list = new List<int>();

list.Add(1);

list.Add(10);

// 無法傳入,因為指定為string
list.Add("hi");
簡單的泛型範例
public class GenericsSimpleSample<T>
{
    public T Value { private get; set; }
    
    public GenericsSimpleSample(T t)
    {
        this.Value = t;
    }

    public T GetValue() 
    {
        return this.Value;
    }
}

static void Main(string[] args)
{
    // 泛型指定為string
    GenericsSimpleSample<string> g = new GenericsSimpleSample<string>("Say Hi");

    Console.WriteLine(g.GetValue()); // output : Say Hi

    // 泛型指定為int
    GenericsSimpleSample<int> g2 = new GenericsSimpleSample<int>(100);

    Console.WriteLine(g2.GetValue()); // output : 100
}

泛型約束

泛型沒有進行約束,它可以進行的操作是非常有限的,以剛剛的sample為例,泛型沒有約束,則只能有幾種方法可以使用。

接下來在此範例,加上泛型約束,讓泛型更靈活

我先建立兩個有繼承關係的型別

// 父類別 Dog
public class Dog
{
    public string Name { get; set; }

    public void Speak() 
    {
        Console.WriteLine("Woo!!");
    }
}

// 衍生類別繼承自 Dog
public class Poodle : Dog
{
    public string Color { get; set; }
}

修改泛型類別,讓類型約束只能指定Dog類別,或其衍生類別

// 使用 where 指定泛型型別
public class GenericsSimpleSample<T> where T: Dog
{
    public T Value { private get; set; }
    
    public GenericsSimpleSample(T t)
    {
        this.Value = t;
    }

    public T GetValue() 
    {
        return this.Value;
    }
}

因為加入約束,泛型就多了Name屬性以及Speak方法可以使用,使用上更為靈活

static void Main(string[] args)
{
    // 泛型指定為Dog
    GenericsSimpleSample<Dog> g = new GenericsSimpleSample<Dog>(new Dog());

    Console.WriteLine(g.GetValue().ToString()); // output : Generics.Dog

    // 泛型指定為Poodle
    GenericsSimpleSample<Poodle> g2 = new GenericsSimpleSample<Poodle>(new Poodle());

    Console.WriteLine(g2.GetValue().ToString()); // output : Generics.Poodle
    
    // 無法指定約束以外的型別,此行編譯無法通過
    GenericsSimpleSample<int> g3 = new GenericsSimpleSample<int>(100);
}

泛型的運用非常的廣泛,能會使用泛型是一件很重要的事情,而且泛型會讓你程式碼變得乾淨,更好維護。

 

 

一天一分享,身體好健康。

該追究的不是過去的原因,而是現在的目的。