來自Hauk的文章 C# 泛型編程之泛型類、泛型方法、泛型約束
所謂泛型,即通過參數化類型來實現在同一份代碼上操作多種數據類型。
泛型編程是一種編程范式,它利用“參數化類型”將類型抽象化,從而實現更為靈活的復用。在定義泛型類時,在對客戶端代碼能夠在實例化類時,可以用類型參數的類型種類施加限制。
泛型方法
在C# 2.0中,方法可以定義特定於其執行范圍的泛型參數,如下所示:
public class MyClass<T> { //指定MyMethod方法用以執行類型為X的參數 public void MyMethod<X>(X x) { // } //此方法也可不指定方法參數 public void MyMethod<X>() { // } }
即使包含類不適用泛型參數,你也可以定義方法特定的泛型參數,如下所示:
public class MyClass { //指定MyMethod方法用以執行類型為X的參數 public void MyMethod<X>(X x) { // } //此方法也可不指定方法參數 public void MyMethod<X>() { // } }
注意:屬性和索引器不能指定自己的泛型參數,它們只能使用所屬類中定義的泛型參數進行操作。
在調用泛型方法的時候,你可以提供要在調用場所使用的類型,如下所示:
MyClass myClass = new MyClass();
myClass.MyMethod<int>(3);
//泛型推理機制調用泛型方法
MyClass myClass = new MyClass();
myClass.MyMethod(3);
注意:泛型方法無法只根據返回值的類型推斷出類型,代碼如下:
public GenericMethodDemo() { MyClass myClass = new MyClass(); /**************************************************** 無法從用法中推理出方法“GenericMethodDemo.MyClass.MyMethod<T>()”的類型參數。 請嘗試顯式指定類型參數。 ***************************************************/ int number = myClass.MyMethod(); } public class MyClass { public T MyMethod<T>() { // } }
泛型方法中泛型參數的約束,如下:
public class MyClass { public void MyMethod<X>(X x) where X:IComparable<X> { // } }
泛型類
無法為類級別的泛型參數提供方法級別的約束。類級別泛型參數的所有約束都必須在類作用范圍中定義,代碼如下所示
public class MyClass<T> { public void MyMethod<X>(X x,T t) where X:IComparable<X> where T:IComparer<T> { // } }
而下面的代碼是正確的:
public class MyClass<T> where T:IComparable<T> { public void MyMethod<X>(X x,T t) where X:IComparable<X> { // } }
泛型參數虛方法的重寫:子類方法必須重新定義該方法特定的泛型參數,代碼如下
public class MyBaseClass { public virtual void SomeMethod<T>(T t) { // } } public class MyClass :MyBaseClass { public override void SomeMethod<X>(X x) { } }
同時子類中的泛型方法不能重復基類泛型方法的約束,這一點和泛型類中的虛方法重寫是有區別的,代碼如下
public class MyBaseClass { public virtual void SomeMethod<T>(T t) where T:new() { // } } public class MyClass :MyBaseClass { //正確寫法 public override void SomeMethod<X>(X x) { } ////錯誤 重寫和顯式接口實現方法的約束是從基方法繼承的,因此不能直接指定這些約束 //public override void SomeMethod<X>(X x) where X:new() //{ //} }
子類方法調用虛擬方法的基類實現:它必須指定要代替泛型基礎方法類型所使用的類型實參。你可以自己顯式的指定它,也可以依靠類型推理(如果可能的話)代碼如下:
public class MyBaseClass { public virtual void SomeMethod<T>(T t) where T:new() { // } } public class MyClass :MyBaseClass { //正確寫法 public override void SomeMethod<X>(X x) { base.SomeMethod<X>(x); base.SomeMethod(x); } }
泛型委托
在某個類中定義的委托可以使用該類的泛型參數,代碼如下
public class MyClass<T> { public delegate void GenericDelegate(T t); public void SomeMethod(T t) { } } public GenericMethodDemo() { MyClass<int> obj = new MyClass<int>(); MyClass<int>.GenericDelegate del; del = new MyClass<int>.GenericDelegate(obj.SomeMethod); del(3); }
委托推理:C#2.0使你可以將方法引用的直接分配轉變為委托變量。將上面的代碼改造如下
public class MyClass<T> { public delegate void GenericDelegate(T t); public void SomeMethod(T t) { } } public GenericMethodDemo() { MyClass<int> obj = new MyClass<int>(); MyClass<int>.GenericDelegate del; //委托推理 del = obj.SomeMethod; del(3); }
泛型委托的約束:
委托級別的約束只在聲明委托變量和實例化委托時使用,類似於在類型和方法的作用范圍中實施的其他任何約束。
泛型和反射
在Net2.0當中,擴展了反射以支持泛型參數。類型Type現在可以表示帶有特定類型的實參(或綁定類型)或未指定類型的泛型(或稱未綁定類型)。像C#1.1中那樣,您可以通過使用typeof運算符或通過調用每個類型支持的GetType()來獲得任何類型的Type。代碼如下:
LinkedList<int> list = new LinkedList<int>(); Type type1 = typeof(LinkedList<int>); Type type2 = list.GetType(); Response.Write(type1 == type2); typeof和GetType()也可以對泛型參數進行操作,如下 public class MyClass<T> { public void SomeMethod(T t) { Type type = typeof(T); HttpContext.Current.Response.Write(type==t.GetType()); } }
typeof還可以對未綁定的泛型進行操作,代碼如下
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { Type unboundType = typeof(MyClass<>); Response.Write(unboundType.ToString()); } } public class MyClass<T> { public void SomeMethod(T t) { Type type = typeof(T); HttpContext.Current.Response.Write(type==t.GetType()); } }
請注意"<>"的用法。要對帶有多個類型參數的未綁定泛型類進行操作,請在"<>"中使用","
Type類中添加了新的方法和屬性,用於提供有關該類型的泛型方面的反射信息,見MSDN。
.net泛型約束
一、 約束
| 約束 | 說明 |
|---|---|
| T:struct |
類型參數必須是值類型。可以指定除 Nullable 以外的任何值類型。 |
| T:class |
類型參數必須是引用類型,包括任何類、接口、委托或數組類型。 |
| T:new() |
類型參數必須具有無參數的公共構造函數。當與其他約束一起使用時,new() 約束必須最后指定。 |
| T:<基類名> |
類型參數必須是指定的基類或派生自指定的基類。 |
| T:<接口名稱> |
類型參數必須是指定的接口或實現指定的接口。可以指定多個接口約束。約束接口也可以是泛型的。 |
| T:U |
為 T 提供的類型參數必須是為 U 提供的參數或派生自為 U 提供的參數。這稱為裸類型約束. |
派生約束
1.常見的
public class MyClass5<T> where T :IComparable { }
2.約束放在類的實際派生之后
public class B { }
public class MyClass6<T> : B where T : IComparable { }
3.可以繼承一個基類和多個接口,且基類在接口前面
public class B { }
public class MyClass7<T> where T : B, IComparable, ICloneable { }
構造函數約束
1.常見的
public class MyClass8<T> where T : new() { }
2.可以將構造函數約束和派生約束組合起來,前提是構造函數約束出現在約束列表的最后
public class MyClass8<T> where T : IComparable, new() { }
值約束
1.常見的
public class MyClass9<T> where T : struct { }
2.與接口約束同時使用,在最前面(不能與基類約束,構造函數約束一起使用)
public class MyClass11<T> where T : struct, IComparable { }
引用約束
常見的
public class MyClass10<T> where T : class { }
多個泛型參數
public class MyClass12<T, U> where T : IComparable where U : class { }
二、 繼承和泛型
public class B<T>{ }
1. 在從泛型基類派生時,可以提供類型實參,而不是基類泛型參數
public class SubClass11 : B<int>
{ }
2.如果子類是泛型,而非具體的類型實參,則可以使用子類泛型參數作為泛型基類的指定類型
public class SubClass12<R> : B<R>
{ }
3.在子類重復基類的約束(在使用子類泛型參數時,必須在子類級別重復在基類級別規定的任何約束)
public class B<T> where T : ISomeInterface { }
public class SubClass2<T> : B<T> where T : ISomeInterface { }
4.構造函數約束
public class B<T> where T : new()
{
public T SomeMethod()
{
return new T();
}
}
public class SubClass3<T> : B<T> where T : new(){ }
三、泛型方法
(C#2.0泛型機制支持在"方法聲名上包含類型參數",這就是泛型方法)
1.泛型方法既可以包含在泛型類型中,又可以包含在非泛型類型中
public class MyClass5
{
public void MyMethod<T>(T t){ }
}
2.泛型方法的聲明與調用
public class MyClass5 { public void MyMethod<T>(T t){ } } public class App5 { public void CallMethod() { MyClass5 myclass5 = new MyClass5(); myclass5.MyMethod<int>(3); } }
3.泛型方法的重載
//第一組重載 void MyMethod1<T>(T t, int i){ } void MyMethod1<U>(U u, int i){ } //第二組重載 void MyMethod2<T>(int i){ } void MyMethod2(int i){ } //第三組重載,假設有兩個泛型參數 void MyMethod3<T>(T t) where T : A { } void MyMethod3<T>(T t) where T : B { } //第四組重載 public class MyClass8<T,U> { public T MyMothed(T a, U b) { return a; } public T MyMothed(U a, T b) { return b; } public int MyMothed(int a, int b) { return a + b; } }
4.泛型方法的覆寫
(1)public class MyBaseClass1
{
public virtual void MyMothed<T>(T t) where T : new() { }
}
public class MySubClass1:MyBaseClass1
{
public override void MyMothed<T>(T t) //不能重復任何約束
{ }
}
(2)public class MyBaseClass2
{
public virtual void MyMothed<T>(T t)
{ }
}
public class MySubClass2 : MyBaseClass2
{
public override void MyMothed<T>(T t) //重新定義泛型參數T
{ }
}
四、虛擬方法
public class BaseClass4<T> { public virtual T SomeMethod() { return default(T); } } public class SubClass4 : BaseClass4<int> //使用實參繼承的時候方法要使用實參的類型 { public override int SomeMethod() { return 0; } } public class SubClass5<T> : BaseClass4<T> //使用泛型繼承時,方法也是泛型 { public override T SomeMethod() { return default(T); } }
五、泛型參數隱式強制轉換
編譯器只允許將泛型參數隱式強制轉換到 Object 或約束指定的類型。
class MyClass<T> where T : BaseClass, ISomeInterface { void SomeMethod(T t) { ISomeInterface obj1 = t; BaseClass obj2 = t; object obj3 = t; } }
變通方法:使用臨時的 Object 變量,將泛型參數強制轉換到其他任何類型
class MyClass2<T>
{
void SomeMethod(T t)
{
object temp = t;
BaseClass obj = (BaseClass)temp;
}
}
六、 泛型參數顯式強制轉換
編譯器允許您將泛型參數顯式強制轉換到其它任何接口,但不能將其轉換到類
class MyClass1<T> { void SomeMethod(T t) { ISomeInterface obj1 = (ISomeInterface)t; //BaseClass obj2 = (BaseClass)t; //不能通過編譯 } }
七、 泛型參數強制轉換到其他任何類型
使用臨時的 Object 變量,將泛型參數強制轉換到其他任何類型
class MyClass2<T> { void SomeMethod(T t) { object temp = t; BaseClass obj = (BaseClass)temp; } }
八、使用is和as運算符
public class MyClass3<T> { public void SomeMethod(T t) { if (t is int) { } if (t is LinkedList<int>) { } string str = t as string; if (str != null) { } LinkedList<int> list = t as LinkedList<int>; if (list != null) { } } }
