[.NET] 什麼是泛型 (Generics)
某一天在跟同事討論技術時,突然被問說你知道什麼是泛型(Generics),這時候小腦袋瓜只能想到 class 或是 method 上的哪個T,卻無法好好解釋什麼是泛型,突然覺得它離我好遙遠,秉持著持續學習稱這個機會好好瞭解一下。
什麼是泛型?
泛型是在 C# 2.0 才被加入的新功能,主要是將類別參數化T,讓設計類別(Class)、結構(Struct)、介面(Interface)與方法(Method)時可以使用一個或多個參數,這樣就可以增加重用性(Reusability)、類型安全(Type safety)與效率(Efficiency),下面的例子就是簡單的泛型類別。
public class Generic<T>
{
    public T Field;
}
var generic = new Generic<string>() { Field = "Hello Generic" };
Console.WriteLine(generic.Field); // Hello Generic泛型術語
除了上述對泛型解釋外還有一些專業術語,這邊也會一起介紹,避免被詢問時回答不出來,未來也會針對泛型比較深入的東西進行介紹,如條件約束(Constraint)、共變數和反變數(Covariance and contravariance)和泛型的反射(Reflection)……等。
泛型類型定義 (generic type definition)
“泛型類型定義”只是範本,無法實體化泛型類別,例如 Dictionary<TKey,TValue> 就是個泛型類型定義。
var d1 = typeof(Dictionary<,>);
var isGeneric = d1.IsGenericType;
var isDefinition = d1.IsGenericTypeDefinition;
Console.WriteLine($"Is this a generic type? {isGeneric}"); 
// True 
Console.WriteLine($"Is this a generic type definition? {isDefinition}"); 
// True泛型類型參數 (generic type parameter)
“泛型類型參數”為泛型類型或方法預留類型參數T,例如Dictionary<TKey,TValue> 預留參數為TKey和TValue。
var d1 = typeof(Dictionary<,>);
var typeParameters1 = d1.GetGenericArguments();
var tKey = typeParameters1[0].Name;
var tValue = typeParameters1[1].Name;
Console.WriteLine($"TKey:{tKey}, TValue:{tValue}");
// TKey:TKey, TValue:TValue建構的泛型類型 (constructed generic type)
建構的泛型類型是將”泛型類型定義”內的類型參數指定對應類型,此類型可以被實體化,例如Dictionary<TKey,TValue> => Dictionary<int, string>。
var d2 = typeof(Dictionary<int, string>);
var isGeneric = d2.IsGenericType;
var isDefinition = d2.IsGenericTypeDefinition;
Console.WriteLine($"Is this a generic type? {isGeneric}"); 
// True 
Console.WriteLine($"Is this a generic type definition? {isDefinition}"); 
// False泛型類型引數 (generic type argument)
“泛型類型引數”為”泛型類型參數”替換對應類型的參數,例如 Dictionary<int, string>,TKey 替換 int 和 TValue 替換 string。
var d2 = typeof(Dictionary<,>);
var typeParameters2 = d2.GetGenericArguments();
var tKey = typeParameters2[0].Name;
var tValue = typeParameters2[1].Name;
Console.WriteLine($"TKey:{tKey}, TValue:{tValue}");
// TKey:Int32, TValue:String泛型 (generic type)
包含”泛型類型定義”和”建構的泛型類型”。
Type d1 = typeof(Dictionary<,>);
Type d2 = typeof(Dictionary<int, string>);
Console.WriteLine($"Is this a generic type? {d1.IsGenericType}"); 
// True   
Console.WriteLine($"Is this a generic type? {d2.IsGenericType}"); 
// True泛型型別參數的「共變數」和「反變數」(Covariance and contravariance)
先簡單有個共變數與反變數概念 :
- 共變數 (Covariance): 子型別可以指派給父型別
 - 反變數 (Contravariance) : 父型別可以指派子型別
 
// Covariance
List<string> stringList = new List<string>();
IEnumerable<object> objects = stringList;
// Contravariance
Action<object> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<string> d = b;
d(string.Empty);條件約束 (Constraint)
可以限制泛型類型參數,例如可以限制參數必需有建構函式 new(),如果不符合編譯器會提醒有錯誤。
public class ConstraintGeneric<T> where T : new()
{
    public T Field;
}
// Compiler Error CS0310
var constraintGeneric = new ConstraintGeneric<string>() 
{ 
    Field = "Hello Generic" 
};泛型方法定義 (generic method definition)
T Generic<T>(T arg)
{
    T temp = arg;
    return temp;
}為什麼要用泛型?
增加重用性(Reusability)
假設情境是今天我是動物之家,想收留狗跟貓的情況,如果沒有泛型情況下我就必需要 DogHouse 跟 CatHouse 來進行,如果是泛型就可以用 AnimalHouse<T> 來進行收留,這樣就可以提高代碼重用性。
public class Animal
{
    public int Name { get; set; }
}
public class Dog : Animal {}
public class Cat : Animal {}
public class DogHouse
{
    public List<Dog> House { get; set; }
}
public class CatHouse
{
    public List<Cat> House { get; set; }
}
public class AnimalHouse<T>
{
    public List<T> House { get; set; }
}類型安全(Type safety)
泛型會將類型安全交給編譯器處理,可以降低執行時因類型轉換因而產生錯誤,如下面範例編譯器馬上會告訴錯誤在哪裡。
var arrayList = new ArrayList();
arrayList.Add(1);
arrayList.Add("2");
var intList = new List<int>();
intList.Add(1);
intList.Add("2"); // Compiler Error CS1503效率(Efficiency)
泛型的集合類型在處理實質型別上,因為可以避免boxing跟Unboxing的產生(參考),可以大大提升效率,如下面範例處理時間就有數倍差異。
var count = 100000;
var stopwatch = new Stopwatch();
stopwatch.Restart();
stopwatch.Start();
var arrayList = new ArrayList();
var result = 0;
for (int i = 0; i < count; i++)
{
    arrayList.Add(i);
}
foreach (int item in arrayList)
{
    result += item;
}
Console.WriteLine($"Sum : {result}");
stopwatch.Stop();
// Total Milli Seconds : 74.8845
Console.WriteLine($"Total Milli Seconds : {stopwatch.Elapsed.TotalMilliseconds}");
stopwatch.Restart();
stopwatch.Start();
var intList = new List<int>();
result = 0;
for (int i = 0; i < count; i++)
{
    intList.Add(i);
}
foreach (int item in intList)
{
    result += item;
}
Console.WriteLine($"Sum : {result}");
stopwatch.Stop();
// Total Milli Seconds : 3.3834
Console.WriteLine($"Total Milli Seconds : {stopwatch.Elapsed.TotalMilliseconds}");
參考資料
Microsoft - Generic type parameters
Microsoft - Generic classes and methods
Microsoft - where (generic type constraint)
Microsoft - Constraints on type parameters
Microsoft - How to: Examine and Instantiate Generic Types with Reflection