使用C#的泛型時,在泛型類型的方法或者泛型方法中可能會使用到類型參數的類型的對象的方法、屬性或成員,這時候這個類型可能並不存在要使用的方法等。這種情況下就會缺少類型安全。爲了改變這種情況,可以對類型參數設置約束。
設置約束的另一個作用是在編輯及編譯時,使用約束後可以享受具體類型的智能感知及強類型支持。否則將只是object級的智能感知。這條說起來可能比較抽象,看一下下面這個例子:
如上圖所示 ,在不指定類型參數約束的情況下,只可以在object級提供智能感知,包括編譯時的類型安全保證。而實現類型約束後:
將獲得編輯時的智能感知支持及編譯時的強類型保證。不然寫成如下這樣就會出現編譯錯誤:
(錯誤示例)
class GenericType1 <K> where K { public void ShwoParam(K param) { Console .WriteLine(param.TtoString()); } } |
泛型類型參數的約束分爲3大類:派生約束、構造函數約束和引用/值類型約束。下文將逐一介紹着這幾類類型參數約束。類型參數約束的語法是在 泛型類型後加一個where,然後在後面書寫類型參數約束。如果有一個以上的類型參數,則每個類型參數約束之間使用" "分隔。形如:GenericType1 where TypeConstraint1 GenericType2 where TypeConstraint2 …
派生約束
常見的派生約束是類型參數派生自一個接口 ,或者一個類 ,形如:
T:<基類名> 類型參數必須是指定的基類或派生自指定的基類。(只可以指定一個。)
T:<接口名稱> 類型參數必須是指定的接口或實現指定的接口。可以指定多個接口約束。約束接口也可以是泛型的。
比較常見的例子:
public class MyCache <K, V>
where K : IComparable
where V : T
{
}
另一種比較特殊的派生約束的形式是裸類型約束。這種約束形如:
T:U
爲 T 提供的類型參數必須是爲 U 提供的參數或派生自爲 U 提供的參數。這稱爲裸類型約束。
裸類型約束 的應用場景如下:
場景1:
class List <T>
{
void Add<U>(List <U> items) where U : T
{
}
}
場景2:
public class SampleClass <T, U, V> where T : V
{
}
構造函數約束
這種約束形如:
T:new()
表示類型參數必須具有無參數的公共構造函數。當與其他約束一起使用時,new() 約束必須最後指定。
當你在代碼中需要聲明一個參數類型的新實例時,將給類型參數添加構造函數約束就很有用,如下代碼說明了其使用:
class GenericType1 <K> where K : new ()
{
// 聲明構造函數約束後可以使用如下語句
K k = new K();
}
引用/值類型約束
使用這種類型的約束可以將類型參數約束爲引用類型 或值類型 。
這種約束形如:
T:結構 類型參數必須是值類型。可以指定除 Nullable 以外的任何值類型。
T:類 類型參數必須是引用類型,包括任何類、接口、委託或數組類型。
這種類型參數約束最簡單,如下:
public class GenericClass <T> where T : struct
{
}
public class GenericClass <K> where K : class
{
}
注意:值類型約束不能與引用類型約束、派生約束聲明及構造約束聲明共同使用。
泛型方法
在說明泛型方法的用途之前首先來看如下實現:
public class GenericClass <T>
{
void PushToStack(Stack <T> stack, params T[] values)
{
foreach (T value in values)
{
stack.Push(value);
}
}
}
如果我們只需要在函數範圍內使用泛型而非在整個類範圍內使用,我們可以使用泛型方法,以上代碼可以改下爲如下情況:
public class GenericClass
{
void PushToStack<T>(Stack <T> stack, params T[] values)
{
foreach (T value in values)
{
stack.Push(value);
}
}
}
這個泛型函數的調用方法如下:
GenericClass gc = new GenericClass ();
gc.PushToStack<int >(new Stack <int >(), new int [] { 1, 2, 3 });
由於泛型方法可以根據參數的泛型類型推斷出函數的泛型類型(類型推斷特性),所以可以省略方法的類型參數,如下:
GenericClass gc = new GenericClass ();
gc.PushToStack(new Stack <int >(), new int [] { 1, 2, 3 });