C#泛型基礎知識點總結
1.0 什麽是泛型
泛型是C#2.0和CLR(公共語言運行時)升級的一個新特性,泛型為.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 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>();
關於泛型的知識點還有很多,比如還有泛型的緩存,這個就有點難理解了。以上知識點是我平常通過各種途徑學習總結的幾點。如有不對歡迎指正。歡迎轉載和分享,轉載分享時請註明原創出處:如此拉風的女人
C#泛型基礎知識點總結