C#泛型基礎知識點總結


  1.0  什么是泛型

      泛型是C#2.0CLR(公共語言運行時)升級的一個新特性,泛型為.NET 框架引入了一個叫 type parameters(類型參數)的概念,type parameters 使得程序在設計的時候,不必設計其具體的參數,其具體的參數可以延遲到需要的時候聲明或調用。使用泛型代碼運行時避免了類型轉換的裝箱和拆箱操作。

  2.0 泛型的延遲聲明:把參數類型的聲明推遲到調用,不是語法糖,而是由框架升級提供的功能

 1  1 using System;  2  2 using System.Collections.Generic;  3  3 using System.Linq;  4  4 using System.Text;  5  5 using System.Threading.Tasks;  6  6 
 7  7 namespace _20171010Generic  8  8 {  9  9     /// <summary>
10 10     /// 泛型方法相關類
11 11     /// </summary>
12 12     public class GenericMethod 13 13 { 14 14         /// <summary>
15 15         /// 泛型方法:方法帶<>和type parameters(類型參數 T)的
16 16         /// </summary>
17 17         /// <typeparam name="T"></typeparam>
18 18         /// <param name="tParameters"></param>
19 19         public static void Show<T>(T tParameters) 20 20 { 21 21             Console.WriteLine("{0}方法,parameter={1}參數,type={2}類型", typeof(GenericMethod).Name, tParameters, tParameters.GetType().Name); 22 22 } 23 23 } 24 24 }

如代碼所示,在聲明泛型方法的時候沒有指定具體的參數類型,等到需要調用的時候再指定,這就叫做延遲聲明。泛型的設計思想(延遲思想,推遲一切可以推遲的)

 1  1 using System;  2  2 using System.Collections.Generic;  3  3 using System.Linq;  4  4 using System.Text;  5  5 using System.Threading.Tasks;  6  6 
 7  7 namespace _20171010Generic  8  8 {  9  9     class Program 10 10 { 11 11         static void Main(string[] args) 12 12 { 13 13 
14 14             int iValue = 123; 15 15             string sValue = "TestName"; 16 16             DateTime dtValue = DateTime.Now; 17 17             object oValue = new object(); 18 18 
19 19 GenericMethod.Show(iValue); 20 20 GenericMethod.Show(sValue); 21 21 GenericMethod.Show(oValue); 22 22 GenericMethod.Show(dtValue); 23 23             Console.WriteLine("———————我是華麗的分割線————————"); 24 24             GenericMethod.Show<int>(iValue); 25 25             GenericMethod.Show<string>(sValue); 26 26             GenericMethod.Show<object>(oValue); 27 27             GenericMethod.Show<DateTime>(dtValue); 28 28            
29 29             Console.WriteLine("———————我是華麗的分割線————————"); 30 30             Console.WriteLine(typeof(List<int>)); 31 31             Console.WriteLine(typeof(Dictionary<,>)); 32 32             Console.WriteLine("———————我是華麗的分割線————————"); 33 33 } 34 34 } 35 35 }

 泛型方法的調用,第一種  GenericMethod.Show(iValue);調用方法不指定類型參數,在編譯的時候編譯器自動編譯推算(語法糖),第二種  GenericMethod.Show<int>(iValue);調用方法指定類型參數,類型參數和參數類型須一致,否則編譯不通過。VS2017鼠標移上去會提示可以簡化方法名稱。編譯的時候,類型參數編譯為占位符,程序運行的時候,JIT(即時編譯(Just In-Time compile)即時編譯為真實類型。所以使用泛型性能會比使用object作為參數的方法好,(ps:經過測試)。 Console.WriteLine(typeof(List<int>)); 和Console.WriteLine(typeof(Dictionary<,>));的運行結果中有個~1,和~2就表示類型參數的占位符。

  3.0 泛型主要的四種:泛型類, 泛型方法,泛型接口,泛型委托  

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _20171010Generic
{
    /// <summary>
    /// 動物類
    /// </summary>
    public class AnimalModel
    {
        public int Id { get; set; }
        public String Name { get; set; }
        public virtual void Cry()
        { }
    }
    public interface IEat
    {
        void Eat();
    }
    public interface ISleep
    {
        void Sleep();
    }

    /// <summary>
    /// 狗類
    /// </summary>
    public class Dog:AnimalModel
    {
        public override void Cry()
        {
            Console.WriteLine("旺旺旺。。。。。");
        }
    }
    /// <summary>
    /// 貓類
    /// </summary>
    public class Cat : AnimalModel
    {
        public override void Cry()
        {
            Console.WriteLine("喵喵瞄。。。。。。。");
        }
    }

    /// <summary>
    /// 玫瑰花類
    /// </summary>
    public class Rose
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

}

 首先先新建了一個AnimalModel類,里面定義了一個動物類,動物類里有個虛方法Cry,一個狗類,狗類繼承了動物類,一個貓類,重寫了虛方法Cry。一個IEat接口和ISleep接口,

 1 using System;  2 using System.Collections.Generic;  3 using System.Linq;  4 using System.Text;  5 using System.Threading.Tasks;  6 
 7 namespace _20171010Generic  8 {  9     /// <summary>
10     /// 泛型類 11     /// </summary>
12     /// <typeparam name="T">類型參數</typeparam>
13     /// <typeparam name="S">類型參數</typeparam>
14     /// <typeparam name="K">類型參數</typeparam>
15     public class GenericClass<T, S, K>
16  { 17         /// <summary>
18         /// 無返回值的泛型方法 19         /// </summary>
20         /// <typeparam name="T"></typeparam>
21         public void Show(T t) 22  { 23 
24  } 25         /// <summary>
26         /// 有返回值的泛型方法 27         /// </summary>
28         /// <typeparam name="T"></typeparam>
29         /// <returns></returns>
30         public T Get() 31  { 32             return default(T); 33  } 34  } 35 
36     /// <summary>
37     /// 泛型接口 38     /// </summary>
39     /// <typeparam name="W"></typeparam>
40     public interface ISleep<W>
41  { 42  W Sleep(W t); 43  } 44 
45     /// <summary>
46     /// 有返回值的泛型委托 47     /// </summary>
48     /// <typeparam name="Y"></typeparam>
49     /// <returns></returns>
50     public delegate Y DlgYFun<Y>(); 51 
52     public delegate int DlgIntFun(); 53 
54     /// <summary>
55     /// 泛型類 56     /// </summary>
57     /// <typeparam name="W"></typeparam>
58     /// <typeparam name="Y"></typeparam>
59     /// <typeparam name="M"></typeparam>
60     public class GenericChild<T, S, K>
61         //: GenericClass<T, S, K>直接繼承泛型類 62         //: GenericClass<T, S, string>//類型參數可直接指定 63         //: ISleep<string>
64         : ISleep<T>//實現泛型接口
65  { 66         T ISleep<T>.Sleep(T t) 67  { 68             return default(T); 69  } 70  } 71 
72     /// <summary>
73     /// 普通類 74     /// </summary>
75     public class Child 76        // :GenericClass<T,S,K>錯誤的繼承,普通類不能直接繼承泛型類 77        //: GenericClass<string, int, double>//必須指定全部確定的類型參數后可繼承泛型 78        //:ISleep<W>錯誤的實現泛型接口,普通類不能直接實現泛型接口,
79        : ISleep<string>
80  { 81         public string Sleep(string t) 82  { 83             Console.WriteLine("實現了sleep泛型接口,返回參數是:{0}", t); 84             return t; 85  } 86  } 87 }

泛型類就在普通類名字后面加上<>和多個類型參數,需要注意的是 1.普通類不能直接繼承泛型類和泛型接口,因為泛型的類型參數不確定,但是泛型類或泛型接口指定類型后可以繼承泛型類或實現泛型接口,2.泛型類可以直接繼承泛型類,也可以直接實現泛型接口,其子類的類型參數相當於聲明了局部參數。

  4.0泛型的約束(基類約束,接口約束,引用類型約束,值類型約束,無參構造函數約束)

    回到上面寫的那個GenericMethod類里的show方法,new 一個cat對象,Cat cat=new Cat(){ Id=1,Name="小黑貓"}; 然后調用genericMentod.show(cat)方法。但是如果想要在show方法里訪問Id,或者Name卻不行。T是個不明確類型參數,所以無法訪問,如圖

使用泛型約束解決方法:

 1 using System;  2 using System.Collections.Generic;  3 using System.Linq;  4 using System.Text;  5 using System.Threading.Tasks;  6 
 7 namespace _20171010Generic  8 {  9     /// <summary>
10     /// 泛型約束 11     /// </summary>
12     public class Constraint 13  { 14        
15         public static void Show<T>(T tParameter) 16             //where T: AnimalModel 基類約束,就可以訪問該類的方法或屬性
17             where T:Cat //或者該子類 
18  { 19             Console.WriteLine("泛型約束show方法--------id={0},name={1}",tParameter.Id,tParameter.Name); 20  } 21 
22         public static void Show(AnimalModel model) 23  { 24             Console.WriteLine("普通show方法--------id={0},name={1}", model.Id, model.Name); 25  } 26 
27         public static void ShowInterface<T>(T tParameter) 28            //where T: AnimalModel 基類約束,就可以訪問該類的方法或屬性
29            where T : Cat,ISleep,IEat//或者該子類約束,多個接口約束 
30             
31  { 32             Console.WriteLine("泛型約束ShowInterface方法--------id={0},name={1}", tParameter.Id, tParameter.Name); 33             tParameter.Sleep();//接口的方法
34  tParameter.Eat(); 35  } 36  } 37 
Constraint類里的第一個show方法中在后面帶個 where關鍵字 約束類型,泛型方法里就能訪問Id和Name,第二個show方法是作為對比,雖然第二個方法也能實現同樣的效果,但是相對泛型方法不靈活,泛型方法可以同時約束多個,比如第三個方法約束多個接口,和類,多個約束的關系是&&關系
泛型約束除了基類約束和接口約束幾種,還有值類型約束,無參構造約束,引用類型約束等這幾種。
基類約束:
1帶來權利,可以使用基類里面的屬性和方法。
2帶來義務,類型參數必須是基類或者其子類。
1  public static T TestFun<T>() 2             // where T:class //引用類型約束 3             // where T:struct //值類型約束
4            where T : new()      //無參構造函數約束
5  { 6            T t = new T(); 7             return default(T); 8         }

   5.0協變和逆變

 out 協變(covariant) 修飾返回值,in 逆變(contravariant) 修飾傳入參數。out和in只能放在接口或者泛型委托的的參數前面,類沒有協變和逆變。在.NET Framework里面,IEnumerable<T>轉到定義去看,其實就是個帶out參數的泛型接口,Action<T>轉到定義去看就是個帶in參數的泛型委托。還有一個逆變+協變的Func<T>

像平常一樣寫代碼:  AnimalModel animal = new AnimalModel();//實例化一個動物。

                   Dog dog = new Dog();//實例化一個條單身狗

          AnimalModel dog2 = new Dog();//實例化一條單身狗(狗繼承了動物父類,父類出現的地方都可以用子類代替,對的,狗一定是個動物),左邊父類,右邊子類。

         // Dog dog3 = new AnimalModel();動物不一定是條單身狗,程序編譯不通過

new一條單身狗沒問題,new 一群單身狗試試看。

        List<Dog> dogList = new List<Dog>();//實例化一群單身狗(編譯通過)

        List<AnimalModel> animalDog = new List<Dog>();//實例化一群單身狗(語法上不通過)

理論上來說第二種實例化一群狗的方式是沒毛病的,一群狗也一定是一群動物,但是程序上是不通過是因為Listt<T>是個泛型  List<Dog>不是繼承List<AnimalModel>,沒有父子關系,程序只認關系。。。

PS:寫到這里我就快寫不下去了,狗快被我自己玩壞了。

要使上面那句代碼編譯通過,可以通過lambda表達式轉化  List<AnimalModel> animalDog = new List<Dog>().Select(x => (AnimalModel)x).ToList();把每條狗都轉換一遍

使用IEnumerable:IEnumerable<AnimalModel> animalDog= new List<Dog>();   //這就叫協變。IEnumerable<out  T>在編譯的時候就通過轉化了,我個人理解為out 是表示轉化后的T返回標識。平常在工作中,有用過out 關鍵字作為標識的返回參數,會用,但是不其所以然。原理明白后自己也可以定義一個協變的泛型接口。

 

 1  public interface IMyTest<out T>
 2     {
 3 
 4     }
 5     public class Test<T> : IMyTest<T>
 6     {
 7 
 8     }
 9 
10 
11    IMyTest<Animal> test3 = new Test<Dog>();

 

逆變就和協變相反。逆變的in 的參數只能作為傳入值,不能作為返回值。說白了,也是一種約束。協變和逆變的關鍵作用就是讓編譯器在運行時不報錯。

 1  1  public interface IMyTest<inT>
 2  2     {
 3  3 
 4  4     }
 5  5     public class Test<T> : IMyTest<T>
 6  6     {
 7  7 
 8  8     }
 9  9 
10 10 
11 11    IMyTest<Dog> test3 = new Test<Animal>();

為什么要用泛型:泛型就是為了滿足不同類型,相同代碼的重用 

關於泛型的知識點還有很多,比如還有泛型的緩存,這個就有點難理解了。以上知識點是我平常通過各種途徑學習總結的幾點。如有不對歡迎指正。歡迎轉載和分享,轉載分享時請注明原創出處:如此拉風的女人

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM