[.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