從簡單的例子理解泛型
吳劍 2012-05-21
原創文章,轉載必需注明出處:http://www.cnblogs.com/wu-jian/
前言
.Net開發基礎系列文章,對自己之前寫過的代碼備忘,如能給人予幫助,不甚榮幸。個人能力有限,如有差錯或不足,請及時指正。
從簡單的例子開始
話說有家影視公司選拔偶像派男主角,導演說了,男演員,身高是王道。於是有下面代碼:
//男演員實體類 public class Boy { //姓名 private string mName; //身高 private int mHeight; public string Name { get { return this.mName; } } public int Height { get { return this.mHeight; } } public Boy(string name, int height) { this.mName = name; this.mHeight = height; } }
//演員選拔類 public class Compare { //導演導超女出生,喜歡一對一PK public Boy WhoIsBetter(Boy boy1, Boy boy2) { if (boy1.Height > boy2.Height) { return boy1; } else { return boy2; } } }
//測試 static void Main(string[] args) { Boy boy1 = new Boy("潘長江", 165); Boy boy2 = new Boy("劉德華", 175); Console.WriteLine(new Compare().WhoIsBetter(boy1, boy2).Name); Console.ReadLine(); }
代碼很簡單,Boy為男演員實體類,包含姓名和身高兩個字段屬性;Compare類中的WhoIsBetter為選拔邏輯方法,負責選出兩個男演員中較高的那個;測試結果:劉德華完勝。
任何行業都是一樣,需求變更無處不在。第二天,需要選女主角,導演說了,女演員,苗條是王道。於是代碼變更,添加了女演員實體類,添加了女演員的選拔方法:
//添加女演員實體 public class Girl { //姓名 private string mName; //體重 private int mWeight; public string Name { get { return this.mName; } } public int Weight { get { return this.mWeight; } } public Girl(string name, int weight){ this.mName = name; this.mWeight = weight; } }
//演員選拔類中添加一個女演員方法 public class Compare { //男演員身高是王道 public Boy WhoIsBetter(Boy boy1, Boy boy2) { if (boy1.Height > boy2.Height) { return boy1; } else { return boy2; } } //女演員苗條是王道 public Girl WhoIsBetter(Girl girl1, Girl girl2) { if (girl1.Weight < girl2.Weight) { return girl1; } else { return girl2; } } }
//測試 static void Main(string[] args) { Boy boy1 = new Boy("潘長江", 165); Boy boy2 = new Boy("劉德華", 175); Girl girl1 = new Girl("鞏俐", 120); Girl girl2 = new Girl("周迅", 80); Console.WriteLine(new Compare().WhoIsBetter(boy1, boy2).Name); Console.WriteLine(new Compare().WhoIsBetter(girl1, girl2).Name); Console.ReadLine(); }
結果選出了身高更高的劉德華,選出了體重更輕的周迅,導演很滿意。但從程序設計角度,這段代碼顯然不夠完美,第一天選男主角,第二天選女主角,往后還要選男配角,選女配角,選群眾......按目前方式,只有往Compare類里不斷添加方法才能滿足導演需求,方法會越來越多,代碼會越來越長。於是,我決定修改WhoIsBetter方法,讓它以后可以支持男主,女主,男配,女配,男群眾,女群眾甚至支持所有兩個對象之間的比較:
/// <summary> /// 男演員:實現IComparable接口 /// </summary> public class Boy : IComparable { //姓名 private string mName; //身高 private int mHeight; public string Name { get { return this.mName; } } public int Height { get { return this.mHeight; } } public Boy(string name, int height) { this.mName = name; this.mHeight = height; } public int CompareTo(object obj) { //比較身高 return this.mHeight - ((Boy)obj).Height; } } /// <summary> /// 女演員:實現IComparable接口 /// </summary> public class Girl : IComparable { //姓名 private string mName; //體重 private int mWeight; public string Name { get { return this.mName; } } public int Weight { get { return this.mWeight; } } public Girl(string name, int weight){ this.mName = name; this.mWeight = weight; } public int CompareTo(object obj) { //比較體重 return ((Girl)obj).Weight - this.mWeight; } }
首先讓實體類支持自定義的比較,男演員比較身高,女演員比較體重。自定義比較是通過實現IComparable接口完成的,在C#里但凡可以比較的類型,比如int、double、char等都實現了IComparable接口。關於IComparable接口此處不作詳述,請讀者自行查閱相關資料。
public class Compare
{ //萬物皆object public object WhoIsBetter(object obj1, object obj2) { object result = obj2; //判斷比較類型必須相同 if (obj1.GetType() == obj2.GetType()) { switch (obj1.GetType().ToString()) { //男演員選拔 case "Generic.Boy": if (((Boy)obj1).CompareTo(obj2) > 0) { result = obj1; } break; //女演員選拔 case "Generic.Girl": if (((Girl)obj1).CompareTo(obj2) > 0) { result = obj1; } break; //擴展int類型比較 case "System.Int32": if (((System.Int32)obj1).CompareTo(obj2) > 0) { result = obj1; } break; } } return result; } }
修改WhoIsBetter方法,除了支持對男演員、女演員的比較,為了展示其擴展性,還新增了int類型的比較。
//測試 static void Main(string[] args) { Boy boy1 = new Boy("潘長江", 165); Boy boy2 = new Boy("劉德華", 175); Girl girl1 = new Girl("鞏俐", 120); Girl girl2 = new Girl("周迅", 80); Console.WriteLine(((Boy)new Compare().WhoIsBetter(boy1, boy2)).Name); Console.WriteLine(((Girl)new Compare().WhoIsBetter(girl1, girl2)).Name); Console.WriteLine(new Compare().WhoIsBetter(boy1.Height, boy2.Height)); Console.WriteLine(new Compare().WhoIsBetter(girl1.Weight, girl2.Weight)); Console.ReadLine(); }
測試結果:
劉德華
周迅
175
120
OK,截止目前,似乎比較完美了,男演員比身高,女演員比體重,還支持int類型比大小,WhoIsBetter方法具有了重用性,如果有需要,往后還能擴展,拿來比較任意兩個對象。在泛型出現以前,似乎確實比較完美,但這也只是相對的,我們來看看目前代碼的弱點:
弱點1:方法的重用性
假設我們要讓WhoIsBetter方法支持更多類型,比如支持基本的double,char,bool類型,支持以后導演可能提出的配角比較,群眾比較,那么就必須不斷的擴展方法內部代碼,這帶來極大的維護成本。
弱點2:類型安全問題
//測試 static void Main(string[] args) { Boy boy1 = new Boy("潘長江", 165); Boy boy2 = new Boy("劉德華", 175); Girl girl1 = new Girl("鞏俐", 120); Girl girl2 = new Girl("周迅", 80); Console.WriteLine(((Boy)new Compare().WhoIsBetter(boy1, girl1)).Name); Console.ReadLine(); }
如上代碼我拿潘長江跟鞏俐去比較。雖然萬能的object給我們帶來了便捷,同時也帶來了風險,這段代碼編譯完全可以通過,但運行時會出現異常,girl對象是沒法轉換為Boy類型的,現實里去韓國可以變性,但代碼里絕對不行。所以這個方法就像顆定時炸彈,一不小心傳錯了參數,就會導致嚴重后果,並且編譯階段完全不被發現。
弱點3:裝箱拆箱導致的性能問題
當向WhoIsBetter方法中傳遞int參數時,object轉換為int導致了拆箱操作:
if (((System.Int32)obj1).CompareTo(obj2) > 0)
反編譯獲取MSIL:
IL_0093: unbox.any [mscorlib]System.Int32
C#是強類型語言,但只要引用類型與值類型的相互轉換,就避免不了Box與Unbox。有關裝箱與拆箱的知識請讀者自行查閱相關資料,此處不作詳述。
理解泛型
OK,到泛型登場了,摘錄了一段MSDN中對泛型的描述:泛型類和泛型方法同時具備可重用性、類型安全和效率,這是非泛型類和非泛型方法無法具備的。這三點,跟我們上面的例子相吻合。
看看使用泛型的解決方案:
public class Compare<T> where T : IComparable { public T WhoIsBetter(T t1, T t2) { if (t1.CompareTo(t2) > 0) { return t1; } else { return t2; } } }
//測試 static void Main(string[] args) { Boy boy1 = new Boy("潘長江", 165); Boy boy2 = new Boy("劉德華", 175); Girl girl1 = new Girl("鞏俐", 120); Girl girl2 = new Girl("周迅", 80); Console.WriteLine((new Compare<Boy>().WhoIsBetter(boy1, boy2)).Name); Console.WriteLine((new Compare<Girl>().WhoIsBetter(girl1, girl2)).Name); Console.WriteLine(new Compare<int>().WhoIsBetter(boy1.Height, boy2.Height)); Console.WriteLine(new Compare<string>().WhoIsBetter(boy1.Name, girl1.Name)); Console.ReadLine(); }
這段代碼在優雅度上完勝非泛型,並且可重用性大大提升,可以說它支持所有類型的比較,只要這個類型實現了IComparable接口,同時一勞永逸,不再需要在方法內部作任何擴展。
public class Compare<T> where T : IComparable{ //... }
泛型類的定義是在類名后面跟上<T>,這個是泛型專用語法,T表示傳遞進來的類型,你也可以用別的字母代替。
where T : IComparable ,從字面上就能理解,這段表示對T的類型約束。程序是遵循人的意志來執行的,按前面的例子,如果莫名其妙的讓程序比較兩個object,它沒辦法知道該去怎么比較。所以我們必須告訴程序,T必須是可比較的類型,T必須實現了IComparable接口。
關於泛型參數約束,MSDN提供了一張表格:
| 約束 | 說明 |
|---|---|
| T:結構 | 類型參數必須是值類型。可以指定除 Nullable 以外的任何值類型。 |
| T:類 | 類型參數必須是引用類型;這一點也適用於任何類、接口、委托或數組類型。 |
| T:new() | 類型參數必須具有無參數的公共構造函數。當與其他約束一起使用時,new() 約束必須最后指定。 |
| T:<基類名> | 類型參數必須是指定的基類或派生自指定的基類。 |
| T:<接口名稱> | 類型參數必須是指定的接口或實現指定的接口。可以指定多個接口約束。約束接口也可以是泛型的。 |
| T:U | 為 T 提供的類型參數必須是為 U 提供的參數或派生自為 U 提供的參數。 |
泛型應用廣泛,除了接口,類,還可以使用泛型方法:
public T Demo<T>(T para) { //... return default(T); }
DEMO
開發環境:Visual Studio 2010, .Net Framwork 4.0
<全文完>
![]() |
如果您覺得本文對您有所幫助,可用微信掃描左側二維碼向作者捐贈。您的支 持是原創的源動力! 作者:吳劍 出處:http://www.cnblogs.com/wu-jian/ 本文版權歸作者所有,歡迎轉載,但必需注明出處,並且在轉載頁面明顯位置給出原文連接,否則保留追究法律責任的權利。 |

