C#泛型入門學習泛型類、泛型集合、泛型方法、泛型約束、泛型委託

本章閱讀列表

  • 泛型很難理解?不然
  • 泛型集合和ArrayList的裝箱拆箱
  • 常見的泛型類型
  • 泛型類和泛型方法
  • 泛型約束
  • 泛型委託

    泛型很難理解?不然

    在接觸的一個新的概念的時候,總會感覺難以理解,當你掌握並能熟練地使用的時候,發現這個概念其實簡單的,我相信大多數碼農都會有這種似曾相識的感覺。可能大多數人剛學習泛型的時候覺得很難理解,當然我也是這樣的,所以便寫下這篇文章加深一下對泛型的印象。
    第一次接觸泛型那還是在大二上學期的時候,那會是學c#面向對象的時候接觸過泛型集合,但尷尬的是那會還沒有“泛型”這個概念,僅僅只停留在泛型集合的使用。關於泛型入門的文章csdn和博客園有很多,這裏我也寫一篇關於我對泛型學習的一個總結,如果出現錯誤表達不當的地方,還希望評論指出。

    泛型優點

    官方文檔:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/introduction-to-generics
    簡介:
    泛型是.NET Framework2.0新增的一個特性,在命名空間System.Collections.Generic,包含了幾個新的基於泛型的集合類,官方建議.net 2.0 及更高版本的應用程序使用心得泛型集合類,而不使用非泛型集合類,例如ArrayList。
    官方解釋:
    泛型是程序設計語言的一種特性。允許程序員在強類型程序設計語言中編寫代碼時定義一些可變部分,那些部分在使用前必須作出指明。各種程序設計語言和其編譯器、運行環境對泛型的支持均不一樣。將類型參數化以達到代碼複用提高軟件開發工作效率的一種數據類型。泛型類是引用類型,是堆對象,主要是引入了類型參數這個概念。
    泛型的定義主要有以下兩種:
    1.在程序編碼中一些包含類型參數的類型,也就是說泛型的參數只可以代表類,不能代表個別對象。(這是當今較常見的定義)
    2.在程序編碼中一些包含參數的類。其參數可以代表類或對象等等。(人們大多把這稱作模板)不論使用哪個定義,泛型的參數在真正使用泛型時都必須作出指明
    官方的解釋雖然很難理解,用我的話來解釋那就是,聲明類和方法時一般都需要定義是什麼類,class Brid ,Class Food…… 聲明泛型類和方法時只需要傳入類型的地方用 ,有點類似於佔位符的作用,用的時候傳入具體的類型。當針對不同類型具有相同行爲的時候,也就是泛型發揮作用的時候。
    優點:
    1.使用泛型類、方法,我們可以極大提高代碼的重用性,不需要對類型不同代碼相同(僅類型參數不同)的代碼寫多次。
    2.創建泛型類,可在編譯時創建類型安全的集合
    3.避免裝箱和拆箱操作降低性能,在大型集合中裝箱和拆箱的影響非常大.

泛型集合和ArrayList的裝箱拆箱

裝箱:是指從值類型轉換成引用類型
拆箱:是指從引用類型轉換成值類型
下面的例子是借鑑官方的一段代碼:

    System.Collections.ArrayList list1 = new System.Collections.ArrayList();
            list1.Add(3);
            list1.Add(105);

            System.Collections.ArrayList list2 = new System.Collections.ArrayList();
            list2.Add("科比");
            list2.Add("詹姆斯");

ArrayList是一個極爲方便的集合類,可以用於存儲任何引用或值類型。但是缺點也很明顯,第一個缺點是編譯的時候不會檢查類型,例如

  System.Collections.ArrayList list1 = new System.Collections.ArrayList();
            list1.Add(3);
            list1.Add(105);
            list1.Add("sd");
            foreach (int item in list1)
            {
                Console.WriteLine(item.ToString());
            }

編譯正常,運行的時候會出現轉換類型錯誤。
至於ArrayList第二個缺點就是裝箱拆箱的時候回造成性能的損失。我們看看ArrayList的Add方法的定義。
這裏寫圖片描述
參數是一個object類型,也就是說ArrayList添加任何引用類型或值類型都會隱士轉換成Object,這個時候便發生裝箱操作,在遍歷檢索它們時必須從object 類型轉換成指定的類型,這個時候便發生拆箱操作。這兩種操作會大大降低性能。所以.net 2.0的程序時應該放棄使用ArrayList,推薦使用使用List《T》 泛型集合。這也是我們爲什麼要使用泛型的原因之一。

常見的泛型類型

在泛型類型的定義中,出現的每個T(一個展位變量而已叫別的名字也行)在運行時都會被替換成實際的類型參數。下面是一些基礎的泛型類型
1.泛型類

           class MyGenericClass<T>
        {
          //......
        }

2.泛型接口

        interface  GenericInterface<T>
        {
           void  GenericMethod(T t);
        }

3.泛型方法

        public void MyGenericMethod<T>()
        {
          //......
        }

4.泛型數組

public T[] GenericArray;

5.泛型委託

 public delegate TOutput GenericDelagete<TInput, TOutput>(TInput input);

6.泛型結構

   struct MyGenericStruct<T>
        {

        }

在使用時所有的T的都要替換成具體的類型。
類型參數命名指南,參見官方文檔
這裏寫圖片描述

泛型類和泛型方法

我們先來看看泛型方法,這個方法的用途是來交換兩個變量的

        static void Main(string[] args)
        {
            int a = 1;
            int b = 2;
            SwapInt(ref a,ref b);
            Console.WriteLine($"a={a}b={b}");
        }
        public static void SwapInt(ref int a, ref int b)
        {
            int temp;
            temp = a;
            a = b;
            b = temp;
        }

結果是a=2,b=1,但是我們現在要換成string類型呢,是不是得再寫一個string參數的方法呢,如果是char、double………..,這每個不同類型的參數都要寫一個參數,的確太麻煩並且沒有這個必要,Object ?當然可以

        static void Main(string[] args)
        {
            object a = 1;
            object b = 2;
            SwapObject(ref a,ref b);
            Console.WriteLine($"a={a}b={b}");
        }
        public static void SwapObject(ref object a, ref object b)
        {
            object temp;
            temp = a;
            a = b;
            b = temp;
        }

這確實能解決代碼複用的需求,但是上面我們已經知道使用Object類型會發生裝箱拆箱的操作,會降低性能。所以我們可以使用泛型方法解決這個缺點。

    static void Main(string[] args)
        {
            int a = 1;
            int b = 2;
            SwapGeneric(ref a,ref b);
            Console.WriteLine($"a={a}b={b}");
        }
        //交換兩個變量的方法
        public static void SwapGeneric<T>(ref T a, ref T b)
        {
            T temp;
            temp = a;
            a = b;
            b = temp;
        }

泛型類:這個泛型類常用api通用接口的泛型類。

    class Program
    {
        static void Main(string[] args)
        {
            List<Product> data = new List<Client.Product>() {
                              new Client.Product() { Id=12,Name="土豆"},
                              new Client.Product() { Id=12,Name="茄子"},
                              new Client.Product() { Id=12,Name="黃瓜"}
           };
           var resultProduct = Result<Product>.Success(data);
            var resultUser = Result<User>.Error("沒有數據");
            foreach (var item in resultProduct.Data)
            {
                Console.WriteLine(item.Id+","+item.Name);
            }
            Console.WriteLine(resultUser.IsSuccess+resultUser.Message);
        }

    }
    public class Result<T> { //泛型類,聲明T變量類型
        public bool IsSuccess { get; set; }
        public List<T> Data { get; set;}//未定義具體類型的泛型集合
        public string Message { get; set; }
        public static Result<T> Error(string message) 
        {
            return new Client.Result<T> { IsSuccess = false, Message = message };
        }
        //泛型方法,初始化數據
        public static Result<T> Success(List<T> data)
        {
            return new Client.Result<T> { IsSuccess =true,Data =data}; //成功就沒有提示消息的原則
        }
    }
    public class Product {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    public class User {
        public int Age { get; set; }
        public string Name { get; set; }
    }

使用該通用的泛型類的好處在於,獲取不同的對象集合不需要寫多個方法,獲取Product數據集合、獲取User數據集………。只需要調用Success方法既可,使代碼變得可複用。

泛型類型參數約束

爲什麼要使用類型參數的約束呢,簡單點說就是篩選類型參數,在使用泛型的代碼中如果違反了某個約束不允許的類型來實例化則會產生編譯錯誤,類型參數的約束是使用關鍵字where。 下面列出了6中類型的約束

  1. where T: struct
    類型參數必須是值類型。可以指定除 Nullable 以外的任何值類型。有關更多信息,請參見使用可以爲 null 的類型(C# 編程指南)。
  2. where T : class
    類型參數必須是引用類型;這一點也適用於任何類、接口、委託或數組類型。
  3. where T:new()
    類型參數必須具有無參數的公共構造函數。當與其他約束一起使用時,new() 約束必須最後指定。
  4. where T:<基類名>
    類型參數必須是指定的基類或派生自指定的基類。
  5. where T:<接口名稱>
    類型參數必須是指定的接口或實現指定的接口。可以指定多個接口約束。約束接口也可以是泛型的。
    1. where T:U
      爲 T 提供的類型參數必須是爲 U 提供的參數或派生自爲 U 提供的參數。
      我們在看看上面那個交換兩個變量的方法SwapGeneric,加上必須是值類型的約束
  public static void SwapGeneric<T>(ref T a,ref T b) where T :struct 
        {
            T temp;
            temp = a;
            a = b;
            b = a;
        }
        //實例化
            Product p = new Product() { Id=1,Name="土豆"};
            Product p1 = new Product() { Id=2,Name ="茄子"};
            SwapGeneric<Product>(ref p,ref p1);

我們在使用的時候編譯給我們提示了以下的錯誤:
這裏寫圖片描述
“類型Product必須是不可以爲NUll值得類型”,引用類型的默認值就是NULL,所以該房型方法的類型參數不能是引用類型,這就是使用類型參數約束的好處。
約束多個參數

    class List<TLive,U> where TLive:User where U:struct
    {

    }

泛型委託

泛型委託可以自己定義自己的類型參數,聲明的時候還是和泛型類、泛型方法一樣加個<坑> 站個坑,其實泛型委託使用的時候不是很多,要慎用。,如以下事例

public delegate T DelegateGeneric<T>(T item);
DelegateGeneric<string> test = StringMethod;
        public static string StringMethod(string name)
        {
            return "你好" + name;
        }

將上面的交換兩個變量的方法改成委託是這樣的

        public delegate void DelegateGenericSwap<T>(ref T a,ref T b);
            string a = "張林";
            string b = "科比";
            DelegateGenericSwap<string> swap = GenericSwap;
            Console.WriteLine($"a:{a}b:{b}");
           public static void GenericSwap<T>(ref T a,ref T b)
        {
            T temp;
            temp = a;
            a = b;
            b = a;
        }

作者:張林
標題:C#泛型入門學習泛型類、泛型集合、泛型方法、泛型約束、泛型委託
原文地址:http://blog.csdn.net/kebi007/article/details/77800954
轉載隨意註明出處

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