第五节:泛型(泛型类、接口、方法、委托、泛型约束、泛型缓存、逆变和协变)
一. 泛型誕生的背景
在介紹背景之前,先來看一個案例,要求:分別輸出實體model1、model2、model3的id和name值,這三個實體有相同的屬性名字id和name。
1 public class myUtils2 {3 //要求:分別輸出實體model1、model2、model3的id和name值,這三個實體有相同的屬性名字id和name4 5 //傳統(tǒng)的解決方案(一):由于三個不同的實體,所以要聲明三個不同的方法來輸出6 /*7 缺點:該解決方案十分繁瑣,明明相同的屬性,不得不聲明三個不同的方法,造成代碼的大量冗余8 */9 10 public static void showM1(model1 m1) 11 { 12 Console.WriteLine("id值為:" + m1.id + " name值為:" + m1.name); 13 } 14 15 public static void showM2(model2 m2) 16 { 17 Console.WriteLine("id值為:" + m2.id + " name值為:" + m2.name); 18 } 19 20 public static void showM3(model3 m3) 21 { 22 Console.WriteLine("id值為:" + m3.id + " name值為:" + m3.name); 23 } 24 25 26 //傳統(tǒng)的解決方案(二):使用Object類型來解決該問題 27 //原理:object是所有類型的父類,所有用到父類的地方都可以用子類去代替,即:里氏替換原則 28 /* 29 缺點:需要記住可能會傳進來哪幾種類型;最大的問題是值類型和引用類型之間的互換(即拆箱和裝箱)嚴重影響性能 30 */ 31 32 public static void showObj(object obj) 33 { 34 if (obj.GetType() == typeof(model1)) 35 { 36 model1 m = (model1)obj; 37 Console.WriteLine("id值為:" + m.id + " name值為:" + m.name); 38 } 39 else if (obj.GetType() == typeof(model2)) 40 { 41 model2 m = (model2)obj; 42 Console.WriteLine("id值為:" + m.id + " name值為:" + m.name); 43 } 44 else if (obj.GetType() == typeof(model3)) 45 { 46 model3 m = (model3)obj; 47 Console.WriteLine("id值為:" + m.id + " name值為:" + m.name); 48 } 49 50 } 51 52 //基于以上兩種傳統(tǒng)的解決方案都缺點明顯,所以在 .Net 2.0的時候,推出了一個通用語言運行時(CRL)的新特性即:泛型。 53 /* 54 泛型為.NET框架引入了類型參數(shù)(type parameters)的概念。類型參數(shù)使得設計類和方法時,不必確定一個或多個具體參數(shù),其的具體參數(shù)可延遲到客戶代碼中聲明、實現(xiàn)。 55 這意味著使用泛型的類型參數(shù)T,可以多種形式調用,運行時類型轉換避免了裝箱操作的代價和風險。 56 57 */ 58 59 }基于以上兩種傳統(tǒng)的解決方案都缺點明顯,所以在 .Net 2.0的時候,推出了一個通用語言運行時(CLR)的新特性即:泛型。泛型為.NET框架引入了類型參數(shù)(type parameters)的概念,它屬于System.Collections.Generic命名空間下。類型參數(shù)使得設計類和方法時,不必確定一個或多個具體參數(shù),其的具體參數(shù)可延遲到客戶代碼中聲明、實現(xiàn)。
這意味著使用泛型的類型參數(shù)T,可以多種形式調用,運行時類型轉換避免了裝箱操作的代價和風險。
二. 泛型的引入
? 為了解決上述問題,我們這里引入泛型的概念來解決代碼冗余的問題和性能低下的問題。
這里統(tǒng)一說明一下:model1、model2、model3實體均繼承與ModelFather實體,ModelFather實體中有兩個屬性,id和name,下同。
1 /// <summary>2 /// 泛型方法的引入3 /// </summary>4 /// <typeparam name="T">T為泛型的一個標記,也可以用別的字母代替</typeparam>5 /// <param name="t"></param>6 public static void showModel<T>(T t)7 {8 Console.WriteLine(t);9 } 10 11 /// <summary> 12 /// 泛型約束的引入,如果要輸出實體中的值,需要配合“基類約束”方可以實現(xiàn) 13 /// </summary> 14 /// <typeparam name="T"></typeparam> 15 /// <param name="t"></param> 16 public static void showModelDetils<T>(T t) where T : ModelFather 17 { 18 Console.WriteLine("id值為:" + t.id + " name值為:" + t.name); 19 } 20 21 22 //總結: 23 /* 24 1. 以上兩個方法均為泛型方法,T代表的類型在使用時才聲明,俗稱“延遲聲明”。 25 2. 如果要使用類中的屬性時,需要配合泛型的“基類約束”來實現(xiàn) 26 */總結:
1. 以上兩個方法均為泛型方法,T代表的類型在使用時才聲明,俗稱“延遲聲明”。
2. 如果要使用類中的屬性時,需要配合泛型的“基類約束”來實現(xiàn)。
? ? ? ??說明:除了泛型方法外,還有泛型接口、泛型委托等,同樣泛型約束除了“基類約束”外,還有其他約束。
三. 泛型的種類
泛型包括:泛型方法、泛型類、泛型接口、泛型委托四類。
這里主要介紹泛型方法,在后續(xù)會配合泛型的五種約束繼續(xù)深入介紹。
?View Code
?View Code
?View Code
?View Code
四.泛型約束
(一). 泛型約束主要分為以下五種:
1. 基類約束:where T:<基類名>
2. 接口約束:where T:<接口名稱> 約束的接口也可以是泛型的
3. 無參構造函數(shù)約束:where T: new() 提供的任何類型參數(shù)都必須具有可訪問的無參數(shù)(或默認)構造函數(shù)。當與其他約束一起使用時,new() 約束必須最后指定。
4. 值類型約束:where T:struct 類型參數(shù)必須是值類型,可以指定除 Nullable 以外的任何值類型。
5. 引用類型約束: where T:class 類型參數(shù)必須是引用類型,也適用于任何類、接口、委托或數(shù)組類型。當與其他約束一起使用時,class 約束必須最先指定。
(二). 多參數(shù)約束:
可以寫多個where條件
public static void ShowModel<T, A>(T model1, A model2)
where T : ModelFather
where A : model3
(三):多條件約束:
當有多個條件約束時,無參構造函數(shù)約束要寫到最后
public void ShowManyCon<T>(T t) where T : class, IWork, ISport, new()
(四):分別測試各種約束
A:各種約束的代碼
1 public class genericsConstraint2 {3 4 /// <summary>5 /// 1. 基類約束和接口約束6 /// </summary>7 /// <typeparam name="T"></typeparam>8 /// <param name="t"></param>9 public void Show<T>(T t) where T : People, IWork 10 { 11 Console.WriteLine("id值為:" + t.id + " name值為:" + t.name); 12 t.Say(); 13 t.Work(); 14 } 15 /// <summary> 16 /// 2. 無參構造函數(shù)約束 17 /// </summary> 18 /// <typeparam name="T"></typeparam> 19 /// <param name="t"></param> 20 public void ShowNo<T>(T t) where T : new() 21 { 22 T t1 = new T(); 23 Console.WriteLine(t1); 24 25 } 26 /// <summary> 27 /// 3. 值類型約束 28 /// C#值類型包括:結構體、數(shù)據(jù)類型(整型、字符型、浮點型、decimal型)、bool型、枚舉、可空類型 29 /// </summary> 30 /// <typeparam name="T"></typeparam> 31 /// <param name="t"></param> 32 public void ShowZhi<T>(T t) where T : struct 33 { 34 Console.WriteLine(t); 35 } 36 /// <summary> 37 /// 4. 引用類型約束 38 /// C#引用類型包括:數(shù)組、類、接口、委托、object、字符串 39 /// </summary> 40 /// <typeparam name="T"></typeparam> 41 /// <param name="t"></param> 42 public void ShowYin<T>(T t) where T : class 43 { 44 Console.WriteLine(t); 45 } 46 /// <summary> 47 /// 5. 多參數(shù)約束 48 /// </summary> 49 /// <typeparam name="T"></typeparam> 50 /// <typeparam name="G"></typeparam> 51 /// <param name="t"></param> 52 /// <param name="g"></param> 53 public void ShowMany<T, G>(T t, G g) 54 where T : People 55 where G : IWork 56 { 57 Console.WriteLine(t); 58 Console.WriteLine(g); 59 } 60 /// <summary> 61 /// 6. 多條件約束 62 /// </summary> 63 /// <typeparam name="T"></typeparam> 64 /// <param name="t"></param> 65 public void ShowManyCon<T>(T t) where T : class, IWork, ISport, new() 66 { 67 68 }B:各種實體代碼
1 namespace Generics._04_泛型約束2 {3 /// <summary>4 /// People類5 /// </summary>6 public class People7 {8 public string id { get; set; }9 10 public string name { get; set; } 11 12 public void Say() 13 { 14 Console.WriteLine("我會說話"); 15 } 16 17 } 18 /// <summary> 19 /// Chinese類 20 /// </summary> 21 public class Chinese : People, ISport, IWork 22 { 23 public void Sport() 24 { 25 Console.WriteLine("我在運動"); 26 } 27 28 public void Work() 29 { 30 Console.WriteLine("我在工作"); 31 } 32 } 33 /// <summary> 34 /// Janpanese類 35 /// </summary> 36 public class Janpanese : IWork 37 { 38 public string id { get; set; } 39 40 public string name { get; set; } 41 public void Work() 42 { 43 Console.WriteLine("我在工作"); 44 } 45 } 46 /// <summary> 47 /// YanTai 48 /// </summary> 49 public class YanTai : Chinese 50 { 51 52 } 53 /// <summary> 54 /// IWork接口 55 /// </summary> 56 public interface IWork 57 { 58 void Work(); 59 } 60 /// <summary> 61 /// ISport接口 62 /// </summary> 63 public interface ISport 64 { 65 void Sport(); 66 } 67 /// <summary> 68 /// 構造函數(shù)無參的 類 69 /// </summary> 70 public class Test1 71 { 72 public Test1() 73 { 74 75 } 76 } 77 /// <summary> 78 /// 構造函數(shù)有參的類 79 /// </summary> 80 public class Test2 81 { 82 public Test2(string id,string name) 83 { 84 85 } 86 } 87 88 89 }C:初始化代碼
1 //1.四個實體類2 People people = new People()3 {4 id = "p1",5 name = "mr1"6 };7 Chinese chinese = new Chinese()8 {9 id = "p2", 10 name = "mr2" 11 }; 12 Janpanese janpanese = new Janpanese() 13 { 14 }; 15 YanTai yanTai = new YanTai() 16 { 17 id = "p3", 18 name = "mr3" 19 }; 20 //2. 構造函數(shù)有參數(shù)和無參數(shù)的兩個類 21 Test1 test1 = new Test1(); 22 Test2 test2 = new Test2("pp","maru"); 23 //3. 聲明一個值類型和一個引用類型 24 int num = 12; //值類型 25 YanTai yt2 = new YanTai(); //引用類型D:代碼調用
1 Console.WriteLine("------------------------------------四 泛型約束------------------------------------");2 //4.1 基類約束和接口約束3 Console.WriteLine("------------------------------------4.1 基類約束和接口約束------------------------------------");4 genericsConstraint gc = new genericsConstraint();5 //gc.Show<People>(people); //People類沒有實現(xiàn)IWork接口6 gc.Show<Chinese>(chinese);7 //gc.Show<Janpanese>(janpanese); //Janpanese只實現(xiàn)了IWork接口,但不滿足基類約束(即使他有和基類相同的屬性也不行)8 gc.Show<YanTai>(yanTai);9 //4.2 無參構造函數(shù)約束 10 Console.WriteLine("------------------------------------4.2 無參構造函數(shù)約束------------------------------------"); 11 gc.ShowNo<Test1>(test1); 12 // gc.ShowNo<Test2>(test2); //Test2的構造函數(shù)有參數(shù),所以不滿足無參構造函數(shù)約束 13 //4.3 值類型約束 14 Console.WriteLine("------------------------------------4.3 值類型約束------------------------------------"); 15 gc.ShowZhi<int>(num); 16 //gc.ShowZhi<YanTai>(yt2); //YanTai類 是引用類型,不滿足值類型約束 17 //4.4 引用類型約束 18 Console.WriteLine("------------------------------------4.4 引用類型約束------------------------------------"); 19 //gc.ShowYin<int>(num); //int類型 是值類型,不滿足引用類型約束 20 gc.ShowYin<YanTai>(yt2); 21 //4.5 多參數(shù)約束 22 Console.WriteLine("------------------------------------4.5 多參數(shù)約束------------------------------------"); 23 gc.ShowMany<People, Chinese>(people, chinese); 24 //4.6 多條件約束 25 Console.WriteLine("------------------------------------4.6 多條件約束------------------------------------"); 26 Console.WriteLine("針對多條件約束,這里不做測試");D:測試結果
五. 泛型的原理和好處
(一). 泛型的原理:
延時聲明,在運行時進行編譯
(二).?泛型的好處:
1.減少代碼冗余量,精簡代碼
2.避免了拆箱和裝箱過程過程中代理的性能損失
3.結合IDE的只能提示,提高了開發(fā)效率
下面測試一下:普通方法、使用Object類型、使用泛型 三種情況對同一個數(shù)據(jù)進行處理,耗時情況
1 public class utils2 {3 /// <summary>4 /// 正常方法5 /// </summary>6 /// <param name="iParameter"></param>7 public static void ShowCommon(int iParameter)8 { }9 /// <summary> 10 /// object方法 11 /// </summary> 12 /// <param name="oParameter"></param> 13 public static void ShowObject(object oParameter) 14 { } 15 /// <summary> 16 /// 原型方法 17 /// </summary> 18 /// <typeparam name="T"></typeparam> 19 /// <param name="tParameter"></param> 20 public static void ShowGeneric<T>(T tParameter) 21 { } 22 } 1 Console.WriteLine("----------測試正常方法、Object方法、泛型方法各自耗時情況------------");2 long commonTime = 0;3 long objectTime = 0;4 long genericTime = 0;5 {6 Stopwatch stopwatch = new Stopwatch();7 stopwatch.Start();8 9 for (int i = 0; i < 1000000000; i++) 10 { 11 utils.ShowCommon(ivalue); 12 } 13 stopwatch.Stop(); 14 commonTime = stopwatch.ElapsedMilliseconds; 15 } 16 { 17 Stopwatch stopwatch = new Stopwatch(); 18 stopwatch.Start(); 19 20 for (int i = 0; i < 1000000000; i++) 21 { 22 utils.ShowObject(ivalue); 23 } 24 stopwatch.Stop(); 25 objectTime = stopwatch.ElapsedMilliseconds; 26 } 27 { 28 Stopwatch stopwatch = new Stopwatch(); 29 stopwatch.Start(); 30 31 for (int i = 0; i < 1000000000; i++) 32 { 33 utils.ShowGeneric<int>(ivalue); 34 } 35 stopwatch.Stop(); 36 genericTime = stopwatch.ElapsedMilliseconds; 37 } 38 Console.WriteLine("commonTime = {0} objectTime = {1} genericTime = {2}", commonTime, objectTime, genericTime);測試結果:
? ? ??結論:正常的方法和泛型方法耗時基本一致,泛型方法甚至用的時間更短,但是采用object類型轉換的方法,耗時明顯長于其他兩種
?
六. 補充default(T)
了解:
1. ?在泛型中如果需要返回泛型類型的默認值則會用到這個關鍵字。
2.?T是值類型而非結構的則default(T) 數(shù)值類型返回0,字符串返回空。
3.??是非引用類型是結構時候返回初始化為零或空的每個結構成員。
4. ?引用類型返回NULL
5.?其實就是為了返回默認值,比如int i =0;這樣是可以的,但是int i=null是不可以的,但是泛型的時候不知道是值類型還是引用類型所以不知道如何賦默認值。
用這個關鍵字就解決了這個問題
?
七. 泛型緩存
1. 知識普及:如果一個(普通)類中有靜態(tài)變量或者靜態(tài)構造函數(shù),當實例化的時候,CLR會優(yōu)先初始化靜態(tài)變量和靜態(tài)構造函數(shù),且只有在第一次實例化的時候進行初始化,后續(xù)都不在進行初始化。(PS:利用這兩個特性可以創(chuàng)建單例模式)
? 特別注意:如果是泛型類的話,不同類型的第一次實例化的時候都要初始化靜態(tài)變量和靜態(tài)構造函數(shù)(每個不同的T,都會生成一份不同的副本)。
2. 先用一普通例子來拋磚引玉:
新建一個CommonClass類,里面有靜態(tài)字段和靜態(tài)方法,然后調用4次,發(fā)現(xiàn)每次輸出的時間都是相同,證明靜態(tài)字段和靜態(tài)構造函數(shù)只有第一次實例化的時候才調用。
或者通過加斷點的形式,也可以發(fā)現(xiàn),只有在第一次實例化的時候才能進入靜態(tài)字段和靜態(tài)構造函數(shù),同樣證明了上述結論。
3. 下面建一個泛型類GenericsClass<T>,里面有靜態(tài)字段和靜態(tài)方法,然后分別傳入不同的類型,int、string、int、string,發(fā)現(xiàn)輸出的時間第2次比第1次時間晚約2s,但是第1次和第3次,第2次和第4次的時間是相同,這就是泛型緩存。
4. 最后自己總結一下泛型緩存的定義:
對于泛型類而言,不同類型都會生成不同的副本(即都要調用靜態(tài)的構造函數(shù)和靜態(tài)字段),但相同的類型,實例化一次后,再次實例化,將不會在生成副本(即不再調用靜態(tài)類和靜態(tài)構造函數(shù)),這就是泛型緩存。
代碼分享:
1 /// <summary>2 /// 一個普通類3 /// </summary>4 public class CommonClass5 {6 static CommonClass()7 {8 _InitTime = string.Format("調用構造函數(shù)的時間:{0}", DateTime.Now.ToString("yyyyMMddHHmmss.fff"));9 } 10 private static string _InitTime = ""; 11 public void show() 12 { 13 Console.WriteLine(_InitTime); 14 } 15 } 16 /// <summary> 17 /// 泛型類 18 /// </summary> 19 /// <typeparam name="T"></typeparam> 20 public class GenericsClass<T> 21 { 22 static GenericsClass() 23 { 24 _InitTime = string.Format("調用構造函數(shù)的時間:{0}", DateTime.Now.ToString("yyyyMMddHHmmss.fff")); 25 } 26 private static string _InitTime = ""; 27 public void show() 28 { 29 Console.WriteLine(_InitTime); 30 } 31 } 1 public static void ShowGenericsCacheDemo()2 {3 4 {5 Console.WriteLine("1. 測試普通類");6 CommonClass c1 = new CommonClass();7 c1.show();8 Thread.Sleep(2000);9 CommonClass c2 = new CommonClass(); 10 c2.show(); 11 Thread.Sleep(2000); 12 CommonClass c3 = new CommonClass(); 13 c3.show(); 14 } 15 { 16 Console.WriteLine("2. 測試泛型類"); 17 GenericsClass<int> c1 = new GenericsClass<int>(); 18 c1.show(); 19 Thread.Sleep(2000); 20 GenericsClass<string> c2 = new GenericsClass<string>(); 21 c2.show(); 22 Thread.Sleep(2000); 23 GenericsClass<int> c3 = new GenericsClass<int>(); 24 c3.show(); 25 Thread.Sleep(2000); 26 GenericsClass<string> c4 = new GenericsClass<string>(); 27 c4.show(); 28 }結果:
?
?
八. 逆變和協(xié)變
1. 目標:了解是什么即可,實際中用的很少。
2. 事前準備:BaseClass和DerivedClass兩個類,DerivedClass類繼承BaseClass類。
1 public class BaseClass2 {3 public string id { get; set; }4 5 public int age { get; set; }6 7 public string name { get; set; }8 }9 public class DerivedClass : BaseClass 10 { 11 12 }3. 逆變:借助Action<in T>,將父類參數(shù)的委托賦值給子類參數(shù)的委托
4. 協(xié)變:借助IEnumerable<out T>,可以將原本有繼承關系的兩個類的集合關聯(lián)起來
分享相關代碼:
1 {2 //里氏替換原則(也是多態(tài)的一種)3 BaseClass bClass = new DerivedClass();4 }5 {6 //推斷:List集合進行繼承(編譯不過,報錯)7 //List<BaseClass> bClassList = new List<DerivedClass>();8 } 9 { 10 //借助Action<in T>,將父類參數(shù)的委托賦值給子類參數(shù)的委托 11 //這就是 → “逆變” 12 Action<BaseClass> bAction = (a) => { Console.WriteLine(a.GetType().Name); }; 13 Action<DerivedClass> dAction = bAction; 14 dAction(new DerivedClass()); 15 } 16 { 17 //借助IEnumerable<out T>,可以將原本有繼承關系的兩個類的集合關聯(lián)起來 18 //這就是 → “協(xié)變” 19 IEnumerable<BaseClass> bClassList = new List<DerivedClass>(); 20 }總結
以上是生活随笔為你收集整理的第五节:泛型(泛型类、接口、方法、委托、泛型约束、泛型缓存、逆变和协变)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苏炳添起诉肖像侵权 得物致歉:会联系沟通
- 下一篇: 《仙剑奇侠传7》PS4/5版8月4日欧美