[.NET] 什麼是泛型 (Generics)


[.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> 預留參數為TKeyTValue

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 替換 intTValue 替換 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)

先簡單有個共變數與反變數概念 :

  1. 共變數 (Covariance): 子型別可以指派給父型別
  2. 反變數 (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)

假設情境是今天我是動物之家,想收留狗跟貓的情況,如果沒有泛型情況下我就必需要 DogHouseCatHouse 來進行,如果是泛型就可以用 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}");

Not generic vs generic

參考資料

[食譜好菜] 能不能講一下什麼是泛型(Generics)

Microsoft - Generics in .NET

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

[Reflection] 執行時期決定泛型類別的型別(一)


作者: PuTaoNi
版權聲明: 本站所有文章除特別聲明外,均採用 CC BY 4.0 許可協議。轉載請註明來源 PuTaoNi !
 上一篇
[.NET] [深入淺出物件導向分析與設計] 心得1 : 偉大軟體由此開始 [.NET] [深入淺出物件導向分析與設計] 心得1 : 偉大軟體由此開始
閱讀深入淺出物件導向分析與設計(Head First Object-Oriented Analysis and Design)後,此書將OOA&D以簡單與詼諧的方式進行介紹,並以偉大軟體由此開始的方式,詳述偉大軟體的設計過程。
2022-03-27
下一篇 
[.NET] OOP 三本柱(封裝、繼承、多型) [.NET] OOP 三本柱(封裝、繼承、多型)
物件導向設計(Object-Oriented Programming, OOP),三本柱分別是 封裝(Encapsulation)、繼承(Inheritance)、多型(Polymorphism),除了三大特性外還有一個很重要的特性哪就是抽象(Abstraction)。
2022-02-08
  目錄