C#泛型

泛型

​ 對於泛型的解釋我們先看下百科的解釋:泛型是程序設計語言的一種特性。允許程序員在強類型程序設計語言中編寫代碼時定義一些可變部分,那些部分在使用前必須作出指明。各種程序設計語言和其編譯器、運行環境對泛型的支持均不一樣。將類型參數化以達到代碼複用提高軟件開發工作效率的一種數據類型。泛型類是引用類型,是堆對象,主要是引入了類型參數這個概念。

​ 泛型是 .Net Framework 2.0提出的,我對他的理解就是 泛型的出現就是爲了解決用一個方法,滿足不同的參數類型,做相同的事的需求。

1、沒有泛型的年代

我們只能看類型寫方法,儘管他們的功能是一樣的參數是不同類型的,那時候我們沒有辦法,例如:

​ 當然,我們承認一點就是說,ShowString() 和ShowInt()這兩個方法絕對是最快的,因爲它就是自己類型的操作一點多餘的損耗也沒有, 但是沒用啊 !! 你看着這麼多重複的代碼你不煩嗎?

我們是這樣重複代碼的,那時候能夠解決不同參數的辦法只有一個,就是用基類 Object

2、泛型的出現

泛型的出現上面說了是.Net Framework 2.0出現的,他的作用就是爲了解決相同功能不同參數的需求。

先說名一下,代碼中的 T 不僅僅可以是任意東西,中文應該也可以,主要不是系統關鍵字都可。

我們對比一下我們上面寫的ShowObject方法,這兩者有什麼區別呢?

區別就在於 Object是所有類型的父類,它可以轉換成任何類型,但是在轉換的時候Objcet 會有裝箱和拆箱兩個過程,這樣會多一倍的損耗。

我們可能疑問了,爲什麼Object 會有裝箱拆箱 而泛型卻沒有?

這個就要說到了泛型的機制了,泛型的運行方法是 推遲聲明,推遲一切能推遲的東西。泛型沒有寫死參數類型,推遲到調用的時候纔去指定參數類型

3、泛型的調用

很簡單吧,用一對尖括號裏面指定參數類型,後面的括號裏面指定實參就行,只要注意好一點就可以,尖括號裏面的別和後面小括號的類型一致

當然要說的就是 沒有尖括號也行,因爲我們說了泛型是延遲聲明,系統會 也不應該是說系統 應該是說你的編譯器會幫你自動推算這個類型應該是什麼。

4、泛型是如何工作的呢?

​ 控制檯程序最終會編譯成一個exe程序,exe被點擊的時候,會經過JIT(即時編譯器)的編譯,最終會生成二進制代碼,才能被計算機執行,泛型加入到語法以後,VS自帶的編譯器又做了升級,升級之後編譯時遇到泛型,會做特殊的處理:生成佔位符。再次經過JIT編譯的時候,會把上面編譯成的佔位符替換成具體的數據類型。

請看下面一個例子:

Console.WriteLine(typeof(List<>));
Console.WriteLine(typeof(Dictionary<,>));

結果:

​ 從上面的截圖中可以看出:泛型在編譯之後會生成佔位符。佔位符以後如果有時間的話,可以仔細研究下。

注意:佔位符需要在英文輸入法狀態下才能輸入,只需要按一次波浪線(數字1左邊的鍵位)的鍵位即可,不需要按Shift鍵。

5、 泛型類

​ 泛型類封裝了不針對任何特定數據類型的操作。泛型類常用於容器類,如鏈表、哈希表、棧、隊列、樹等等。這些類中的操作,如對容器添加、刪除元素,不論所存儲的數據是何種類型,都執行幾乎同樣的操作。

6、泛型接口

​ 不論是爲泛型容器類,還是表示容器中元素的泛型類,定義接口是很有用的。把泛型接口與泛型類結合使用是更好的用法,比如用IComparable而非IComparable,以避免值類型上的裝箱和拆箱操作。

7、泛型委託

8、普通類可以繼承泛型類

在C#中申明的普通類也可以繼承泛型類,但是我沒有遇到過這樣的需求,感覺碰到的可能性不是很大。注意泛型在聲明的時候可以不指定具體的類型,但是在使用的時候必須指定具體類型

如果子類也是泛型的,那麼繼承的時候可以不指定具體類型。

9、泛型約束

​ 要檢查表中的一個元素,以確定它是否合法或是否可以與其他元素相比較,那麼編譯器必須保證:客戶代碼中可能出現的所有類型參數,都要支持所需調用的操作或方法。這種保證是通過在泛型類的定義中,應用一個或多個約束而得到的。一個約束類型是一種基類約束,它通知編譯器,只有這個類型的對象或從這個類型派生的對象,可被用作類型參數。一旦編譯器得到這樣的保證,它就允許在泛型類中調用這個類型的方法。上下文關鍵字where用以實現約束。同一個類型參數可應用多個約束。約束自身也可以是泛型類。並且泛型的約束可以是多個且可以多種類型。

class MyList<T> where T: Employee, IEmployee,  IComparable<T>,  new() {…}

下面是五種約束

約束 描述
where T: struct 類型參數必須爲值類型。
where T : class 類型參數必須爲引用類型。
where T : new() 類型參數必須有一個公有、無參的構造函數。當於其它約束聯合使用時,new()約束必須放在最後。
**where T : ** 類型參數必須是指定的基類型或是派生自指定的基類型。
**where T : ** 類型參數必須是指定的接口或是指定接口的實現。可以指定多個接口約束。接口約束也可以是泛型的。

我們一個一個來

 public   class Person
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public void Hello()
        { 
        }
    }
    public class Chinese : Person, IWork, ISports
    {
        public void WeShouldDo()
        {
            Console.WriteLine("武漢加油!!湖北加油!!");
        }


        public void Walk()
        {
            Console.WriteLine("特殊時期不要出去!");
        }

        public void Work()
        {
            Console.WriteLine("去工作的路上記得帶口罩!");
        }
    }

    public class WuHan:Person
    {
        public void BeHappy()
        {
            Console.WriteLine("等我們病好了,請大家來看櫻花");
        }
           
    }

    public interface ISports
    {
        void Walk();
    }
    public interface IWork
    {
        void Work();
    }

代碼中我們寫了一個Person類 Chinese 和WuHan的實體類,並且寫了兩個接口,也給他們實現了一下。

下面我們來看泛型的實例化

    Console.WriteLine("***** 泛型實例化 *******");
    Person person = new Person()
    {
        ID = 1,
        Name = "person"
    };
    Chinese chinese = new Chinese()
    {
        ID = 2,
        Name = "Chinese"
    };
    WuHan wuHan = new WuHan()
    {
        ID = 3,
        Name = "WuHan"
    };
    Console.Read();

下面如果說我們有個需求是要打印參數的ID和Name ,我們可以這樣寫

  public static void Show<T> (T value)
  {
      Console.WriteLine($"{value.ID}--- {value.Name}");
  }

但是這樣寫他會報錯,

​ 我們看錯也知道了爲什麼會報錯,因爲我們說了,泛型是任何參數類型都能傳進來,他不知道你傳進來的是誰啊,所以他不可能知道你有ID和Name,所以傳遞的之後我們要做一下約束,約束一下 傳遞進來的只能是Person 類型的。

當然了,我們也可以進行多個約束,也可以進行多種約束。

在其中我們看到了 多種約束直接在後面寫Where 條件預計就可以,而且我們還可以看出 泛型的參數是可以是中文的。

其他的約束我就不講了,上面的表格中列出了的那五種約束可以看一下。

奧,對了要說一點,約束不可以約束密封類,也不可以約束值類型,因爲值類型是密封的,就相當於我們不能約束 Int String這樣的類型

我們反編譯看到了 string類型的源碼是 sealed密封類的

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-klNk3sYn-1583628546409)(%E6%B3%9B%E5%9E%8B%E5%89%AF%E6%9C%AC.assets/image-20200130114545246.png)]

而 Int 是值類型的

所以不可以約束他們

10、爲什麼要用泛型約束而不用基類約束呢?

既然我們知道了傳進來的一定是個Person類型的,爲什麼不直接用一個Person 接收呢?

​ 這個的話,我只能說,看需求吧,如果你以後的需求要變,給Person加一些條件的話,用泛型約束最好,因爲條件我們可以隨意加啊,但是如果是確定這個功能不變了,就只是傳進來一個Person而已的話,基類的會稍微好一點。

11、逆變 斜邊(Out In)

我們看代碼

我們聲明瞭一個Bird類和一個Swallow 的燕子類,我們看下面四種實例化方式,沒啥說的,唯一要說的就聲明的時候可以用父類作爲主類實例化子類,但是子類不可以作爲主類實例化父類。

我們照着這個邏輯就可以推出來泛型應該也符合這個機制。

但是我們發現報錯了,但是按照我們前面的邏輯應該是可以啊, 一堆燕子就是一堆鳥啊 ,爲什麼會報錯呢,鳥不是燕子的父類嗎?

這個報錯的原因是 在這裏面他們沒有父子關係 記住偶 這是泛型,雖然裏面的元素是相同的,但是泛型只認數據不認人 ,就說說這兩個東西放到了兩個不同的List集合裏面,他們沒有父子關係。

當然,我們想實現的話也可以實現,解決辦法就是要把類型轉換一下 這是泛型發佈之後最開始時候的坑

下面進入正題,講到了我們的 out 和 In

out 和 In是 4.0出現的

這個T必須是返回值類型,不能出現在參數裏面

斜邊只能用在接口或者委託之中,他的特點就是

也可以自己寫

逆變:左邊子類 右邊可以是子類也可以是父類

協變 逆變一起來

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-G5igYILQ-1583628546413)(%E6%B3%9B%E5%9E%8B%E5%89%AF%E6%9C%AC.assets/image-20200130001755254.png)]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章