[.net 面向對象編程基礎] (18) 泛型html
上一節咱們說到了兩種數據類型數組和集合,數組是指包含同一類型的多個元素,集合是指.net中提供數據存儲和檢索的專用類。編程
數組使用前須要先指定大小,而且檢索不方便。集合檢索和聲明方便,可是存在類型安全問題,原本使一個類型安全的C#變得不安全了。數組
集合爲了解決數組預設大小的問題,採起了一種自動擴容的辦法,這樣當大小不夠時,他就建立一個新的存儲區域,把原有集合的元素複製過來。如此又對性能上也是有很大的影響。安全
上節咱們說到解決這些缺陷的方法,那就是.NET 2.0之後,微軟程序猿們推出來的新特性——泛型。ide
1.什麼是泛型?函數
泛型是具備佔位符(類型參數)的類、結構、接口和方法,這些佔位符是類、結構、接口和方法所存儲或使用的一個或多個佔位符。性能
這個概念聽起來比較繞,其實理解起來也不難,個人理解是類、接口、委託、結構或方法中有類型參數就是泛型類型,這樣就有類型參數的概念。泛型集合類能夠將類型參數用做它存儲對象的點位符;類型參數做爲其字段或方法的參數類型出現(這是MSDN中的描述)。測試
泛型集合所在的命我空間爲:System.Collections.Genericthis
而List類是ArrayList的泛型等效類。該類使用大小按需動態增長的數組實現IList接口。使用方法就是IList<T>和List<T>,這個T就是你要指定的集合的數據或對象類型。spa
2.泛型聲明
泛型類: class Name<t>{}
泛型方法: void Name(T t){}
泛型接口:interface IName<T>{}
泛型結構:struct Name<T>{}
泛型委託:public delegate void Name<T>(T param);
3.泛型方法
泛型咱們在定義的時候,說明了他是可使用佔位符來佔位類、結構、接口和方法的。咱們先看一下方法使用泛型的例子。
咱們仍是使用前面的例子來看一下使用泛型:
類之間的關係UML圖以下:
咱們調用假如要實現,讓每一個動物都叫幾聲。該如何寫呢?
1 /// <summary> 2 /// 動物類(父類 抽象類) 3 /// </summary> 4 abstract class Animal 5 { 6 /// <summary> 7 /// 名字 8 /// 說明:類和子類可訪問 9 /// </summary> 10 protected string name; 11 12 /// <summary> 13 /// 構造函數 14 /// </summary> 15 /// <param name="name"></param> 16 public Animal(string name) 17 { 18 this.name = name; 19 } 20 21 private int shoutNum = 8; 22 public int ShoutNum 23 { 24 get { return shoutNum; } 25 set { shoutNum = value; } 26 } 27 28 /// <summary> 29 /// 名字(虛屬性) 30 /// </summary> 31 public virtual string MyName 32 { 33 get { return this.name; } 34 } 35 36 /// <summary> 37 /// 叫聲,這個方法去掉虛方法,把循環寫在這裏 38 /// </summary> 39 public void Shout() 40 { 41 string result = ""; 42 for (int i = 0; i < ShoutNum; i++) 43 result += getShoutSound() + "!"; 44 45 Console.WriteLine(MyName); 46 Console.WriteLine(result); 47 } 48 49 /// <summary> 50 /// 建立一個叫聲的虛方法,子類重寫 51 /// </summary> 52 /// <returns></returns> 53 public virtual string getShoutSound() 54 { 55 return ""; 56 } 57 58 /// <summary> 59 /// 讓全部動集合類的動物叫三次並報名字 (泛型) 60 /// </summary> 61 /// <param name="animal"></param> 62 public static void AnimalShout(IList<Animal> animal) 63 { 64 DateTime dt = System.DateTime.Now; 65 foreach (Animal anm in animal) 66 { 67 anm.Shout(); 68 } 69 Console.WriteLine("使用泛型讓全部動物叫一遍所用時間爲:" + (System.DateTime.Now - dt).TotalMilliseconds +"毫秒"); 70 } 71 /// <summary> 72 /// 讓全部動集合類的動物叫三次並報名字 (重載方法 集合) 73 /// </summary> 74 /// <param name="animal"></param> 75 public static void AnimalShout(ArrayList animal) 76 { 77 DateTime dt = System.DateTime.Now; 78 foreach (Animal anm in animal) 79 { 80 anm.Shout(); 81 } 82 Console.WriteLine("使用集合讓全部動物叫一遍所用時間爲:" + (System.DateTime.Now - dt).TotalMilliseconds + "毫秒"); 83 } 84 85 /// <summary> 86 /// 讓全部動集合類的動物叫三次並報名字 (重載方法 數組) 87 /// </summary> 88 /// <param name="animal"></param> 89 public static void AnimalShout(Animal[] animal) 90 { 91 DateTime dt = System.DateTime.Now; 92 foreach (Animal anm in animal) 93 { 94 anm.Shout(); 95 } 96 Console.WriteLine("使用數組讓全部動物叫一遍所用時間爲:" + (System.DateTime.Now - dt).TotalMilliseconds + "毫秒"); 97 } 98 } 99 100 101 /// <summary> 102 /// 狗(子類) 103 /// </summary> 104 class Dog : Animal 105 { 106 string myName; 107 public Dog(string name) 108 : base(name) 109 { 110 myName = name; 111 } 112 113 /// <summary> 114 /// 名字(重寫父類屬性) 115 /// </summary> 116 public override string MyName 117 { 118 get { return "我是:狗狗,我叫:" + this.name; } 119 } 120 121 /// <summary> 122 /// 叫(重寫父類方法) 123 /// </summary> 124 public override string getShoutSound() 125 { 126 return "汪!"; 127 } 128 } 129 130 /// <summary> 131 /// 狗(子類) 132 /// </summary> 133 class ShepherdDog : Dog 134 { 135 string myName; 136 public ShepherdDog(string name) 137 : base(name) 138 { 139 myName = name; 140 } 141 142 /// <summary> 143 /// 名字(重寫父類屬性) 144 /// </summary> 145 public override string MyName 146 { 147 get { return "我是:牧羊犬,我叫:" + this.name; } 148 } 149 150 /// <summary> 151 /// 叫(重寫父類方法) 152 /// </summary> 153 public override string getShoutSound() 154 { 155 return "汪~嗚!"; 156 } 157 } 158 159 /// <summary> 160 /// 貓(子類) 161 /// </summary> 162 class Cat : Animal 163 { 164 string myName; 165 public Cat(string name) 166 : base(name) 167 { 168 myName = name; 169 } 170 /// <summary> 171 /// 名字(重寫父類屬性) 172 /// </summary> 173 public override string MyName 174 { 175 get { return "我是:貓咪,我叫:" + this.name; } 176 177 } 178 179 /// <summary> 180 /// 叫(重寫父類方法) 181 /// </summary> 182 public override string getShoutSound() 183 { 184 return "喵!"; 185 } 186 } 187 188 /// <summary> 189 /// 貓(子類) 190 /// </summary> 191 class PersianCat : Cat 192 { 193 string myName; 194 public PersianCat(string name) 195 : base(name) 196 { 197 myName = name; 198 } 199 /// <summary> 200 /// 名字(重寫父類屬性) 201 /// </summary> 202 public override string MyName 203 { 204 get { return "我是:波斯貓,我叫:" + this.name; } 205 206 } 207 208 /// <summary> 209 /// 叫(重寫父類方法) 210 /// </summary> 211 public override string getShoutSound() 212 { 213 return "喵~嗚!"; 214 } 215 } 216 217 /// <summary> 218 /// 羊(子類) 219 /// </summary> 220 class Sheep : Animal 221 { 222 string myName; 223 public Sheep(string name) 224 : base(name) 225 { 226 myName = name; 227 } 228 /// <summary> 229 /// 名字(重寫父類屬性) 230 /// </summary> 231 public override string MyName 232 { 233 get { return "我是:羊羊,我叫:" + this.name; } 234 235 } 236 /// <summary> 237 /// 叫(重寫父類方法) 238 /// </summary> 239 public override string getShoutSound() 240 { 241 return "咩!"; 242 } 243 }
調用方法:
1 //數組 2 Animal[] animalArray = new Animal[] { new Dog("旺財"), new Cat("小花"), new Cat("阿狸"), new Sheep("純羊"), new Dog("小白"), new ShepherdDog("汪羊"), new PersianCat("機貓") }; 3 4 //泛型 5 IList<Animal> animal = new List<Animal>(); 6 animal.Add(new Dog("旺財")); 7 animal.Add(new Cat("小花")); 8 animal.Add(new Cat("阿狸")); 9 animal.Add(new Sheep("純羊")); 10 animal.Add(new Dog("小白")); 11 animal.Add(new ShepherdDog("汪羊")); 12 animal.Add(new PersianCat("機貓")); 13 14 //集合 15 ArrayList animalArrayList = new ArrayList(); 16 animalArrayList.Add(new Dog("旺財")); 17 animalArrayList.Add(new Cat("小花")); 18 animalArrayList.Add(new Cat("阿狸")); 19 animalArrayList.Add(new Sheep("純羊")); 20 animalArrayList.Add(new Dog("小白")); 21 animalArrayList.Add(new ShepherdDog("汪羊")); 22 animalArrayList.Add(new PersianCat("機貓")); 23 24 25 //調用重載方法看它們的執行叫8次並報名字所需時間 26 Animal.AnimalShout(animalArray); 27 Animal.AnimalShout(animal); 28 Animal.AnimalShout(animalArrayList); 29 Console.ReadLine();
執行結果以下:
以上的實例並無模擬出能客觀測試效率的環境,由於根據咱們的經驗數組並不能接受不一樣類型的元素,而集合和泛型能夠,若是使用不一樣數據測試,也不是客觀的。以上實例主要反映了泛型、數組、集合的使用方法,小夥伴們不要太糾結測試時間,不過泛型的時間確實是比較快的。有了泛型小夥伴們就不要再使用ArrayList這個不安全類型了。
數組、List和ArrayList的區別:
上節說了數組和集合ArrayList的區別,這節咱們使用了泛型,再說一下他們三者的區別
數組:
(1)在內存中是連續存儲的,因此它的索引速度是很是的快,並且賦值與修改元素也很簡單。
(2)可是數組也存在一些不足的地方。好比在數組的兩個數據間插入數據也是很麻煩的,還有咱們在聲明數組的時候,必須同時指明數組的長度,數組的長度過長,會形成內存浪費,數組和長度太短,會形成數據溢出的錯誤。這樣若是在聲明數組時咱們並不清楚數組的長度,就變的很麻煩了.
集合ArrayList:
集合的出現就是爲了解決數組的缺陷,但他自己也有缺陷,直到.NET 2.0之後出現泛型,咱們能夠說這是微軟設計上的失誤。
(1).ArrayList並不是類型安全
ArrayList不論什麼類型都接受,實際是接受一個object類型。
好比以下操做:
ArrayList ar = new ArrayList();
ar.Add(111);
ar.Add("bbb");
咱們使用foreach遍歷的時候 foreach(int array in ar){}那麼遇到」bbb」則程度報錯,所以咱們說他是非安全類型。
(2).遍歷ArrayList資源消耗大
所以類型的非安全,咱們在使用ArrayList的時候,就意味着增長一個元素,就須要值類型轉換爲Object對象。遍歷的時候,又須要將Object轉爲值類型。
就是裝箱(boxing,指將值類型轉換爲引用類型) 和
拆箱(unboxing,指將引用類型轉換爲值類型)
因爲裝箱了拆箱頻繁進行,須要大量計算,所以開銷很大。
泛型List:
List和AraayList都是繼承於IList,屬於等效類,他們之間的區別也是對集合ArrayList侷限性的修改
(1)類型安全,經過容許指定泛型類或方法操做的特定類型,泛型功能將類型安全的任務從您轉移給了編譯器。不須要編寫代碼來檢測數據類型是否正確,由於會在編譯時強制使用正確的數據類型。減小了類型強制轉換的須要和運行時錯誤的可能性。
(2)減小開銷,泛型提供了類型安全但沒有增長多個實現的開銷。
3.泛型類
對於泛型類,咱們先解決一下實際例子:假如咱們有一個泛型類,不知道是什麼類型,在初始化的時候再指定類型。類裏面有一個方法,能夠接受初始化的參數類型,也就是一個泛型方法。
/// <summary> /// 泛型有一個泛型方法,該方法有一個泛型參數 /// </summary> /// <typeparam name="T"></typeparam> class MyClass<T> { public static T F(T param) { return param; } }
調用:
//泛型類調用 int param = 2, param2= 3; string result1 = (MyClass<string>.F(param.ToString()) + param2).ToString(); string result2 = (MyClass<int>.F(param) + param2).ToString(); Console.WriteLine(result1); Console.WriteLine(result2); Console.ReadLine();
能夠看到,輸出結果爲: 23 和 5 ,使用泛型,咱們不會由於數據類型不一樣,就須要去複製一個方法去處理了。
4.類型約束
咱們上面定義了泛型,他們默認沒有類型約束,就是說能夠是任意類型。既然定義了泛型,爲什麼還要約束,其實咱們這麼理解,泛型首先是一種安全類型,約束他只是讓他的範圍更小一點而已。
好比某些狀況下,咱們須要約束類型
主要有如下六種類型的約束:
約束 |
說明 |
T:結構 |
類型參數必須是值類型。能夠指定除 Nullable 之外的任何值類型。有關更多信息,請參見使用可空類型(C# 編程指南)。 |
T:類 |
類型參數必須是引用類型,包括任何類、接口、委託或數組類型。 |
T:new() |
類型參數必須具備無參數的公共構造函數。當與其餘約束一塊兒使用時,new() 約束必須最後指定。 |
T:<基類名> |
類型參數必須是指定的基類或派生自指定的基類。 |
T:<接口名稱> |
類型參數必須是指定的接口或實現指定的接口。能夠指定多個接口約束。約束接口也能夠是泛型的。 |
T:U |
爲 T 提供的類型參數必須是爲 U 提供的參數或派生自爲 U 提供的參數。這稱爲裸類型約束。 |
類型約束的特色:
A.指定的就是類型參數必須是引用類型,
B.包括任何類、接口、委託或數組類型,若是除了這幾樣就是非法的了
C.能夠輸入任何的引用類型同時也肯定了範圍,防止了輸入值類型引起的不安全
D.約束的類型必須是非封閉的類型
E.建議不要對類型參數使用 == 和 != 運算符,由於這些運算符僅測試引用同一性而不測試值相等性
F.若是有new(),則必定要放在最後
未綁定的類型參數
沒有約束的類型參數(如公共類 SampleClass<T>{} 中的 T)稱爲未綁定的類型參數。未綁定的類型參數具備如下規則:
·不能使用 != 和 == 運算符,由於沒法保證具體類型參數能支持這些運算符。
·能夠在它們與 System.Object 之間來回轉換,或將它們顯式轉換爲任何接口類型。
·能夠將它們與 null 進行比較。將未綁定的參數與 null 進行比較時,若是類型參數爲值類型,則該比較將始終返回 false。
裸類型約束
·用做約束的泛型類型參數稱爲裸類型約束。
·當具備本身的類型參數的成員函數須要將該參數約束爲包含類型的類型參數時,裸類型約束頗有用,泛型類的裸類型約束的做用很是有限,由於編譯器除了假設某個裸類型約束派生自 System.Object 之外,不會作其餘任何假設。
·在但願強制兩個類型參數之間的繼承關係的狀況下,可對泛型類使用裸類型約束。
5.約束舉例說明
下面對這幾種約束舉例說明
仍是以這個動物系列爲例,下面是實現代碼:
1 /// <summary> 2 /// 動物類(父類 抽象類) 3 /// </summary> 4 abstract class Animal 5 { 6 /// <summary> 7 /// 名字 8 /// 說明:類和子類可訪問 9 /// </summary> 10 protected string name; 11 12 /// <summary> 13 /// 構造函數 14 /// </summary> 15 /// <param name="name"></param> 16 public Animal(string name) 17 { 18 this.name = name; 19 } 20 21 private int shoutNum = 3; 22 public int ShoutNum 23 { 24 get { return shoutNum; } 25 set { shoutNum = value; } 26 } 27 28 /// <summary> 29 /// 名字(虛屬性) 30 /// </summary> 31 public virtual string MyName 32 { 33 get { return this.name; } 34 } 35 36 /// <summary> 37 /// 叫聲,這個方法去掉虛方法,把循環寫在這裏 38 /// </summary> 39 public void Shout() 40 { 41 string result = ""; 42 for (int i = 0; i < ShoutNum; i++) 43 result += getShoutSound() + "!"; 44 45 Console.WriteLine(MyName); 46 Console.WriteLine(result); 47 } 48 49 /// <summary> 50 /// 建立一個叫聲的虛方法,子類重寫 51 /// </summary> 52 /// <returns></returns> 53 public virtual string getShoutSound() 54 { 55 return ""; 56 } 57 } 58 59 /// <summary> 60 /// 聲明一個接口 ISpeak(講話) 61 /// </summary> 62 interface ISpeak 63 { 64 void Speak(); 65 } 66 67 /// <summary> 68 /// 狗(子類) 69 /// </summary> 70 class Dog : Animal 71 { 72 string myName; 73 public Dog(string name): base(name) 74 { 75 myName = name; 76 } 77 /// <summary> 78 /// 名字(重寫父類屬性) 79 /// </summary> 80 public override string MyName 81 { 82 get { return "我是:狗狗,我叫:" + this.name; } 83 } 84 /// <summary> 85 /// 叫(重寫父類方法) 86 /// </summary> 87 public override string getShoutSound() 88 { 89 return "汪!"; 90 } 91 } 92 93 /// <summary> 94 /// 貓(子類) 95 /// </summary> 96 class Cat : Animal 97 { 98 string myName; 99 public Cat(string name): base(name) 100 { 101 myName = name; 102 } 103 /// <summary> 104 /// 名字(重寫父類屬性) 105 /// </summary> 106 public override string MyName 107 { 108 get { return "我是:貓咪,我叫:" + this.name; } 109 } 110 /// <summary> 111 /// 叫(重寫父類方法) 112 /// </summary> 113 public override string getShoutSound() 114 { 115 return "喵!"; 116 } 117 } 118 119 /// <summary> 120 /// 藍貓(子類) 121 /// 繼承 Cat和接口ISpeak 122 /// </summary> 123 class BlueCat : Cat,ISpeak 124 { 125 string myName; 126 public BlueCat(string name) : base(name) 127 { 128 myName = name; 129 } 130 /// <summary> 131 /// 名字(重寫父類屬性) 132 /// </summary> 133 public override string MyName 134 { 135 get { return "我是:藍貓,我叫:" + this.name; } 136 } 137 138 /// <summary> 139 /// 實現接口ISpeak的成員Speak 140 /// </summary> 141 public void Speak() 142 { 143 Console.WriteLine("我會說人話:「你好,我叫:" + this.name + "~~~」"); 144 } 145 } 146 147 /// <summary> 148 /// 羊(子類) 149 /// </summary> 150 class Sheep : Animal 151 { 152 string myName; 153 public Sheep(string name): base(name) 154 { 155 myName = name; 156 } 157 /// <summary> 158 /// 名字(重寫父類屬性) 159 /// </summary> 160 public override string MyName 161 { 162 get { return "我是:羊羊,我叫:" + this.name; } 163 } 164 /// <summary> 165 /// 叫(重寫父類方法) 166 /// </summary> 167 public override string getShoutSound() 168 { 169 return "咩!"; 170 } 171 } 172 173 /// <summary> 174 /// 喜羊羊(子類) 175 /// 繼承 Sheep和接口ISpeak 176 /// </summary> 177 class PleasantSheep : Sheep, ISpeak 178 { 179 string myName; 180 public PleasantSheep(string name) : base(name) 181 { 182 myName = name; 183 } 184 /// <summary> 185 /// 名字(重寫父類屬性) 186 /// </summary> 187 public override string MyName 188 { 189 get { return "我是:喜羊羊,我叫:" + this.name; } 190 } 191 /// <summary> 192 /// 實現接口ISpeak的成員Speak 193 /// </summary> 194 public void Speak() 195 { 196 Console.WriteLine("我會說人話:「你好,我叫:" + this.name + "~~~」"); 197 } 198 }
5.1基類約束
這個比較常見,就是約束類型的實參都必須繼承同一基類
咱們新建一個泛型類CatShout,約束它的基類實參爲Cat類
//泛型約束 - 基類約束 class CatShout<T> where T : Cat { public void Shout(T cat) { cat.Shout(); } }
調用及結果:
//泛型約束- 基類約束 CatShout<Cat> cat = new CatShout<Cat>(); cat.Shout(new BlueCat("蘭喵")); Console.ReadLine(); //CatShout<Dog> dog = new CatShout<Dog>(); 假如使用 Dog類實例化,約束生效,程序報錯
5.2 接口約束
此次咱們創建一個泛型類AnimalSpeak類,約束它派生自接口ISpeak
//泛型約束 - 接口約束 class AnimalSpeak<T> where T : ISpeak { public void Speak(T t) { t.Speak(); } }
調用及結果:
//泛型約束- 接口約束 AnimalSpeak<BlueCat> animalSpeak = new AnimalSpeak<BlueCat>(); animalSpeak.Speak(new BlueCat("蘭喵")); AnimalSpeak<PleasantSheep> animalSpeak2 = new AnimalSpeak<PleasantSheep>(); animalSpeak2.Speak(new PleasantSheep("肥咩")); //假如使用如下調用,約束生效,程序報錯,由於Cat並不繼承接口ISpeak,不符合接口約束 //AnimalSpeak<Cat> animalSpeak3 = new AnimalSpeak<Cat>(); //animalSpeak2.Speak(new Cat("阿狸"));
說明:能夠同時約束爲類和接口,類在前面,接口在後。接口能夠有多個,類只能一個,不能夠繼承多個類。
6.0 要點:
最後總結一下
A.泛型基本的使用比較簡單,簡單說就是一個取代數組和集合的安全類型
B.泛型包括,泛型類、方法、接口、委託、結構。
C.泛型在某些狀況下爲了調用更加安全,即在編譯階段就進行校驗,常用約束
D.泛型的類型約束有幾個方面:類約束,接口,構造函數,結構等。基類約束、接口約束和構造函數約束較爲常見
E.類的約束使用where關鍵詞,約束的幾個方面有前後順序,一般類,接口,構造函數,這樣的順序。
若是感受約束這塊兒有點難理解,小夥伴們先掌握好基本泛型使用方法。
==============================================================================================
返回目錄
<若是對你有幫助,記得點一下推薦哦,有不明白的地方或寫的不對的地方,請多交流>
==============================================================================================