<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>


      一、為什么使用泛型?


      泛型其實就是一個不確定的類型,可以用在類和方法上,泛型在聲明期間沒有明確的定義類型,編譯完成之后會生成一個占位符,只有在調(diào)用者調(diào)用時,傳入指定的類型,才會用確切的類型將占位符替換掉。

      首先我們要明白,泛型是泛型,集合是集合,泛型集合就是帶泛型的集合。下面我們來模仿這List集合看一下下面這個例子:

      我們的目的是要寫一個可以存放任何動物的集合,首先抽象出一個動物類:
      //動物類 public class Animal { //隨便定義出一個屬性和方法 public String SkinColor { get; set;
      }//皮膚顏色 //會跑的方法 public virtual void CanRun() { Console.WriteLine("Animal Run Can
      "); } }
      然后創(chuàng)建Dog類和Pig類
      //動物子類 Dog public class Dog : Animal { //重寫父類方法 public override void CanRun()
      { Console.WriteLine("Dog Can Run"); } } //動物子類 Pig public class Pig : Animal {
      //重寫父類方法 public override void CanRun() { Console.WriteLine("Pig Can Run"); } }
      因為我們的目的是存放所有的動物,然后我們來寫一個AnimalHouse用來存放所有動物:
      //存放所有動物 public class AnimalHouse { //由于自己寫線性表需要考慮很多東西,而且我們是要講泛型的,所以內(nèi)部就用List來實現(xiàn)
      private List<Animal> animal = new List<Animal>(); //添加方法 public void
      AddAnimal(Animal a) { animal.Add(a); }//移除方法,并返回是否成功 public bool
      RemoveAnimal(Animal a) {return animal.Remove(a); } }

      AnimalHouse類型可以存放所有的動物,存放起來很方便。但是每次取出的話,使用起來會很不方便,因為只能用一些動物的特征,而無法使用子類的特征,例如Dog子類有CanSwim()方法(會游泳的方法),而動物中是沒有這個方法的,所以就無法進行調(diào)用,必須將Animal類型轉(zhuǎn)換為Dog類型才可以使用,不僅會增加額外的開銷而且還有很大的不確定性,可能轉(zhuǎn)換失敗,因為AnimalHouse中是存放了很多種動物子類。

      如果我們有方法可以做到,讓調(diào)用者來決定添加什么類型(具體的類型,例如Dog、Pig),然后我們創(chuàng)建什么類型,是不是這些問題就不存在了?泛型就可以做到。

      我們看一下泛型是如何定義的:
      //用在類中 public class ClassName<CName> { //用在方法中 public void Mothed<MName>() { }
      //泛型類中具體使用CName //返回值為CName并且接受一個類型為CName類型的對象 public CName GetC(CName c) { //
      default關(guān)鍵字的作用就是返回類型的默認值 return default(CName); } }
      其中CName和MName是可變的類型(名字也是可變的),用法的話就和類型用法一樣,用的時候就把它當成具體的類型來用。

      了解過泛型,接下來我們使用泛型把AnimalHouse類更改一下,將所有類型Animal更改為泛型,如下:
      public class AnimalHouse<T> { private List<T> animal = new List<T>(); public
      void AddAnimal(T a) { animal.Add(a); } public bool RemoveAnimal(T a) { return
      animal.Remove(a); } }
      AnimalHouse類型想要存儲什么樣的動物,就可以完全交由調(diào)用者來決定:
      //聲明存放所有Dog類型的集合 AnimalHouse<Dog> dog = new AnimalHouse<Dog>(); //
      聲明存放所有Pig類型的集合 AnimalHouse<Pig> pig = new AnimalHouse<Pig>();
      調(diào)用方法的時候,原本寫的是T類型,當聲明的時候傳入具體的類型之后,類中所有的T都會變成具體的類型,例如Dog類型,Pig類型



      ?

      這樣我們的問題就解決了,當調(diào)用者傳入什么類型,我們就構(gòu)造什么類型的集合來存放動物。


      但是還有一個問題,就是調(diào)用者也可以不傳入動物,調(diào)用者可以傳入一個桌子(Desk類)、電腦(Computer),但是這些都不是我們想要的。比如我們需要調(diào)用動物的CanRun方法,讓動物跑一下再放入集合里(z),因為我們知道動物都是繼承自Animal類,所有動物都會有CanRun方法,但是如果傳入過來一個飛Desk類我們還能使用CanRun方法嗎?答案是未知的,所以為了確保安全,我們需要對傳入的類型進行約束。

      二、泛型約束

      泛型約束就是對泛型(傳入的類型)進行約束,約束就是指定該類型必須滿足某些特定的特征,例如:可以被實例化、比如實現(xiàn)Animal類等等

      我們來看一下官方文檔上都有那些泛型約束:

      約束 說明
      where T : struct 類型參數(shù)必須是值類型。?可以指定除?Nullable<T>
      <https://docs.microsoft.com/zh-cn/dotnet/api/system.nullable-1>?以外的任何值類型。?有關(guān)可以為
      null 的類型的詳細信息,請參閱可以為 null 的類型
      <https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/nullable-types/index>
      。
      where T : class 類型參數(shù)必須是引用類型。?此約束還應用于任何類、接口、委托或數(shù)組類型。
      where T : unmanaged 類型參數(shù)必須是非托管類型
      <https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/unmanaged-types>

      where T : new() 類型參數(shù)必須具有公共無參數(shù)構(gòu)造函數(shù)。?與其他約束一起使用時,new()?約束必須最后指定。
      where T :?<基類名> 類型參數(shù)必須是指定的基類或派生自指定的基類。
      where T :?<接口名稱> 類型參數(shù)必須是指定的接口或?qū)崿F(xiàn)指定的接口。?可指定多個接口約束。?約束接口也可以是泛型。
      where T : U 為 T 提供的類型參數(shù)必須是為 U 提供的參數(shù)或派生自為 U 提供的參數(shù)。
      ?

      ?

      ?

      ?

      ?

      ?

      ?

      ?

      對多個參數(shù)應用約束:
      //微軟官方例子
      class Base { } class Test<T, U> where U : struct where T : Base, new() { }
      使用的話只需要在泛型后面添加?where 泛型 : 泛型約束1、泛型約束2....,如果有new()約束的話則必須放在最后,說明都有很詳細的介紹。

      然后我們來為AnimalHouse添加泛型約束為:必須包含公共無參構(gòu)造函數(shù)和基類必須是Animal
      //Animal約束T必須是Animal的子類或者本身,new()約束放在最后 public class AnimalHouse<T> where T :
      Animal,new() { private List<T> animal = new List<T>(); public void AddAnimal(T
      a) {//調(diào)用CanRun方法 //如果不加Animal泛型約束是無法調(diào)用.CanRun方法的,因為類型是不確定的 a.CanRun(); //添加
      animal.Add(a); } public bool RemoveAnimal(T a) { return animal.Remove(a); } }
      然后調(diào)用的時候我們傳入Object試一下



      提示Object類型不能傳入AnimalHouse<T>中,因為無法轉(zhuǎn)換為Animal類型。

      我們在寫一個繼承Animal類的Tiger子類,然后私有化構(gòu)造函數(shù)
      //動物子類 Tiger public class Tiger : Animal { //私有化構(gòu)造函數(shù) private Tiger() { } public
      override void CanRun() { Console.WriteLine("Tiger Can Run"); } }
      然后創(chuàng)建AnimalHouse類型對象,傳入Tiger類試一下:



      提示必須是公共無參的非抽象類型構(gòu)造函數(shù)?,F(xiàn)在我們的AnimalHouse類就很完善了,可以存入所有的動物,而且只能存入動物

      三、逆變和協(xié)變

      先來看一個問題
      Dog dog = new Dog(); Animal animal = dog;
      這樣寫編譯是不會報錯的,因為Dog繼承了Animal,默認會進行一個隱式轉(zhuǎn)換,但是下面這樣寫
      AnimalHouse<Dog> dogHouse = new AnimalHouse<Dog>(); AnimalHouse<Animal>
      animalHouse = dogHouse;


      這樣寫的話會報一個無法轉(zhuǎn)換類型的錯誤。

      強轉(zhuǎn)的話,會轉(zhuǎn)換失敗,我們設個斷點在后一句,然后監(jiān)視一下animalHouse的值,可以看到值為null
      //強轉(zhuǎn)編譯會通過,強轉(zhuǎn)的話會轉(zhuǎn)換失敗,值為null IAnimalHouse<Animal> animalHouse = dogHouse as
      IAnimalHouse<Animal>;




      協(xié)變就是為了解決這一問題的,這樣做其實也是為了解決類型安全
      <https://baike.baidu.com/item/%E7%B1%BB%E5%9E%8B%E5%AE%89%E5%85%A8/7308285?fr=aladdin>
      問題(百度百科):例如類型安全代碼不能從其他對象的私有字段讀取值。它只從定義完善的允許方式訪問類型才能讀取。

      因為協(xié)變只能用在接口或者委托類型中,所以我們將AnimalHouse抽象抽來一個空接口IAnimalHouse,然后實現(xiàn)該接口:
      //動物房子接口(所有動物的房子必須繼承該接口,例如紅磚動物房子,別墅動物房) public interface IAnimalHouse<T> where
      T : Animal,new() { }//實現(xiàn)IAnimalHouse接口 public class AnimalHouse<T> :
      IAnimalHouse<T>where T : Animal,new() { private List<T> animal = new List<T>();
      public void AddAnimal(T a) { a.CanRun(); animal.Add(a); } public bool
      RemoveAnimal(T a) {return animal.Remove(a); } }
      協(xié)變是在T泛型前使用out關(guān)鍵字,其他不需要做修改
      public interface IAnimalHouse<out T> where T : Animal,new() { }
      接下來我們用接口來調(diào)用一下,現(xiàn)在一切ok了,編譯也可以通過
      IAnimalHouse<Dog> dogHouse = new AnimalHouse<Dog>(); IAnimalHouse<Animal>
      animalHouse = dogHouse;
      協(xié)變的作用就是可以將子類泛型隱式轉(zhuǎn)換為父類泛型,而逆變就是將父類泛型隱式轉(zhuǎn)換為子類泛型

      將接口類型改為使用in關(guān)鍵字
      public interface IAnimalHouse<in T> where T : Animal,new() { }
      逆變就完成了:
      IAnimalHouse<Animal> animalHouse = new AnimalHouse<Animal>(); IAnimalHouse
      <Dog> dogHouse = animalHouse;
      逆變和協(xié)變還有兩點:協(xié)變時泛型無法作為參數(shù)、逆變時泛型無法作為返回值。

      逆變:



      協(xié)變:



      語法都是一些 非常粗糙的東西,重要的是思想、思想、思想。然后我們來看一下為什么要有逆變和協(xié)變?


      什么叫做類型安全?C#中的類型安全個人理解大致就是:一個對象向父類轉(zhuǎn)換時,會隱式安全的轉(zhuǎn)換,而兩種不確定可以成功轉(zhuǎn)換的類型(父類轉(zhuǎn)子類),轉(zhuǎn)換時必須顯式轉(zhuǎn)換。解決了類型安全大致就是,這兩種類型一定可以轉(zhuǎn)換成功。(如果有錯誤,歡迎指正)。

      ?

      協(xié)變的話我相信應該很好理解,將子類轉(zhuǎn)換為父類,兼容性好,解決了類型安全(因為子類轉(zhuǎn)父類是肯定可以轉(zhuǎn)換成功的);而協(xié)變作為返回值是百分百的類型安全

      ?

      “逆變?yōu)槭裁从质墙鉀Q了類型安全呢?子類轉(zhuǎn)父類也安全嗎?不是有可能存在失敗嗎?”

      其實逆變的內(nèi)部也是實現(xiàn)子類轉(zhuǎn)換為父類,所以說也是安全的。

      “可是我明明看到的是IAnimalHouse<Dog> dogHouse = animalHouse;將父類對象賦值給了子類,你還想騙人?”


      這樣寫確實是將父類轉(zhuǎn)換為子類,不過逆變是用在作為參數(shù)傳遞的。這是因為寫代碼的“視角”原因,為什么協(xié)變這么好理解,因為子類轉(zhuǎn)換父類很明顯可一看出來“IAnimalHouse<Animal>
      animalHouse = dogHouse;”,然后我們換個“視角”,將逆變作為參數(shù)傳遞一下,看這個例子:

      ?

      先將IAnimalHouse接口修改一下:
      public interface IAnimalHouse<in T> where T : Animal,new() { //添加方法 void
      AddAnimal(T a);//移除方法 bool RemoveAnimal(T a); }
      然后我們在主類(Main函數(shù)所在的類)中添加一個TestIn方法來說明為什么逆變是安全的:
      //需要一個IAnimalHouse<Dog>類型的參數(shù) public void TestIn(IAnimalHouse<Dog> dog) { }
      接下來我們將“視角”切到TestIn中,作為第一視角,我們正在寫這個方法,至于其他人如何調(diào)用我們都是不得而知的

      我們就隨便在當前方法中添加一個操作:為dog變量添加一個Dog對象,TestIn方法改為如下:
      //需要一個IAnimalHouse<Dog>類型的參數(shù) public static void TestIn(IAnimalHouse<Dog> dog)
      { Dog d= new Dog(); dog.AddAnimal(d); }
      我們將“視角”調(diào)用者視角,如果我們想調(diào)用當前方法,只有兩種方法:
      //第一種 AnimalHouse<Dog> dogHouse = new AnimalHouse<Dog>(); TestIn(dogHouse); //
      第二種 AnimalHouse<Animal> animalHouse = new AnimalHouse<Animal>(); //
      因為使用了in關(guān)鍵字所以可以傳入父類對象 TestIn(animalHouse);
      第一種的話我們就不看了,很正常也很合理,我們主要來看第二種,那第二種類型安全又在哪兒呢?


      可能有人已經(jīng)反應過來了,我們再來看一下TestIn方法,有一個需要傳遞過來的IAnimalHouse<Dog>類型的dog對象,如果調(diào)用者是使用第二種方法調(diào)用的,那這個所謂的IAnimalHouse<Dog>類型的dog對象是不是其實就是AnimalHouse<Animal>類型的對象?而dog.AddAnimal(參數(shù)類型);的參數(shù)類型是不是就是需要一個Animal類型的對象?那傳入一個Dog類型的d對象是不是最終也是轉(zhuǎn)換為Animal類型放入dog對象中?所以當逆變作為參數(shù)傳遞時,類型是安全的。

      思考:那么,現(xiàn)在你能明白上面那個錯誤,為什么“協(xié)變時泛型無法作為參數(shù)、逆變時泛型無法作為返回值”了嗎?
      public interface IAnimalHouse<in T> where T : Animal,new() { //如果這樣寫逆變成立的話 //
      我們實現(xiàn)該接口,實現(xiàn)In方法,return(返回)一個默認值default(T)或者new T()//
      此時使用第二種方法調(diào)用TestIn,并在TestIn中調(diào)用In方法//
      注意,在TestIn中In方法的顯示返回值肯定是Dog,但是實際上要返回的類型是Animal//所以就存在Animal類型轉(zhuǎn)換為Dog類型,所以就有可能失敗
      //所以逆變時泛型無法作為返回值 T In(); void AddAnimal(T a); bool RemoveAnimal(T a); }
      逆變思考答案,建議自己認真思考過后再看 //在主類(Main類)中添加一個out協(xié)變測試方法 public static
      IAnimalHouse<Animal> TestOut() { //返回一個子類 return new AnimalHouse<Dog>(); } //
      回到接口 public interface IAnimalHouse<out T> where T : Animal,new() { //如果這樣寫協(xié)變成立的話
      //我們在Main方法中調(diào)用TestOut()方法,使用house變量接收一下 //IAnimalHouse<Animal> house =
      TestOut();//然后調(diào)用house的AddAnimal()方法 //注意,此時AddAnimal方法需要的是一個Animal,但是實際類型卻是Dog類型
      //因為我們的TestOut方法返回的是一個Dog類型的對象 //所以當我們在AddAnimal()中傳入new
      Animal()時,會存在Animal父類到Dog子類的轉(zhuǎn)換//類型是不安全的,所以協(xié)變時泛型無法作為參數(shù) void AddAnimal(T a); bool
      RemoveAnimal(T a); } 協(xié)變思考答案,建議自己認真思考過后再看
      如果我哪點講的有誤或者那點不是太明白都可以留言指正或提問。

      ?

      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          欧美女人做爱视频 | 精品色色色| 九七伦理97伦理手机版最新章节 | 免费的操逼网站 | 国产日韩欧美一区二区 | 小说图片视频乱伦亚洲欧美 | 又大又粗又黄又爽的视频 | 国产最新自拍 | 国产又爽又黄免费视频 | 4438最新网址 |