本筆記摘抄自:http://www.javashuo.com/article/p-tluiwrcw-do.html,記錄一下學習過程以備後續查用。html
1、什麼是泛型數組
泛型是C#2.0推出的新語法,不是語法糖,而是2.0由框架升級提供的功能。泛型類就相似於一個模板,能夠在須要時爲這個模板傳入任何咱們須要的類型。緩存
2、爲何使用泛型框架
下面代碼演示輸出幾種類型的相關信息:ide
class Program { /// <summary> /// 打印幫助類 /// </summary> public class ShowHelper { /// <summary> /// ShowInt /// </summary> /// <param name="intParam"></param> public static void ShowInt(int intParam) { Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={intParam.GetType().Name},Parameter={intParam}"); } /// <summary> /// ShowString /// </summary> /// <param name="strParam"></param> public static void ShowString(string strParam) { Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={strParam.GetType().Name},Parameter={strParam}"); } /// <summary> /// ShowDateTime /// </summary> /// <param name="dtParam"></param> public static void ShowDateTime(DateTime dtParam) { Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={dtParam.GetType().Name},Parameter={dtParam}"); } } static void Main(string[] args) { #region 非泛型打印方式一 ShowHelper.ShowInt(123); ShowHelper.ShowString("Hello World."); ShowHelper.ShowDateTime(DateTime.Now); Console.Read(); #endregion } }
運行結果以下:函數
上面3個方法很類似,除了參數類型不一樣外,實現的功能是同樣的,能夠稍做優化。性能
下面代碼演示使用繼承的方式輸出幾種類型的相關信息:學習
class Program { /// <summary> /// 打印幫助類 /// </summary> public class ShowHelper { /// <summary> /// ShowType /// </summary> /// <param name="obj"></param> public static void ShowType(object obj) { Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={obj.GetType().Name},Parameter={obj}"); } } static void Main(string[] args) { #region 非泛型打印方式二 ShowHelper.ShowType(123); ShowHelper.ShowType("Hello World."); ShowHelper.ShowType(DateTime.Now); Console.Read(); #endregion } }
功能實現沒有問題,只是object與其它類型的轉換,涉及到裝箱和拆箱的過程,這個是會損耗程序的性能的。測試
3、泛型類型參數優化
在泛型類型或方法的定義中,泛型類型參數可認爲是特定類型的佔位符。
下面代碼演示使用泛型的方式輸出幾種類型的相關信息:
class Program { /// <summary> /// 打印幫助類 /// </summary> public class ShowHelper { /// <summary> /// Show /// </summary> /// <param name="obj"></param> public static void Show<T>(T tParam) { Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={tParam.GetType().Name},Parameter={tParam}"); } } static void Main(string[] args) { #region 泛型打印方式 ShowHelper.Show(123); ShowHelper.Show("Hello World."); ShowHelper.Show(DateTime.Now); Console.Read(); #endregion } }
運行結果以下:
一、爲何泛型能夠解決上面的問題呢?
泛型是延遲聲明的:即定義的時候沒有指定具體的參數類型,把參數類型的聲明推遲到調用的時候纔給它指定。
二、泛型到底是如何工做的呢?
程序執行原理:控制檯程序最終會編譯成一個exe程序。當exe被點擊的時候,會通過JIT(即時編譯器)的編譯,最終生成二進制代碼才能被計算機執行。
泛型工做原理:泛型加入到語法之後,VS自帶的編譯器作了升級,升級以後編譯時若遇到泛型,會作特殊的處理:生成佔位符。而後通過JIT編譯的時候,
會把上面編譯生成的佔位符替換成具體的數據類型。
下面代碼演示泛型佔位符:
class Program { static void Main(string[] args) { #region 泛型佔位符 Console.WriteLine(typeof(List<>)); Console.WriteLine(typeof(Dictionary<,>)); Console.Read(); #endregion } }
運行結果以下:
三、泛型性能問題
下面代碼演示泛型性能測試:
class Program { static void Main(string[] args) { #region 泛型性能測試 long commonTime = 0; long objectTime = 0; long genericTime = 0; Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 10000; i++) { ShowHelper.ShowInt(123); } watch.Stop(); commonTime = watch.ElapsedMilliseconds; watch.Reset(); watch.Start(); for (int i = 0; i < 10000; i++) { ShowHelper.ShowType(123); } watch.Stop(); objectTime = watch.ElapsedMilliseconds; watch.Reset(); watch.Start(); for (int i = 0; i < 10000; i++) { ShowHelper.Show(123); } watch.Stop(); genericTime = watch.ElapsedMilliseconds; Console.Clear(); Console.WriteLine($"Common time={commonTime}ms"); Console.WriteLine($"Object time={objectTime}ms"); Console.WriteLine($"Generic time={genericTime}ms"); Console.Read(); #endregion } }
運行結果以下:
從結果能夠看出,泛型的性能是最高的。
4、泛型類
下面代碼演示泛型類:
class Program { /// <summary> /// 泛型類 /// </summary> /// <typeparam name="T"></typeparam> public class GenericClass<T> { public T varT; } static void Main(string[] args) { #region 泛型類 //T是int類型 GenericClass<int> genericInt = new GenericClass<int> { varT = 123 }; Console.WriteLine($"The value of T={genericInt.varT}"); //T是string類型 GenericClass<string> genericString = new GenericClass<string> { varT = "123" }; Console.WriteLine($"The value of T={genericString.varT}"); Console.Read(); #endregion } }
運行結果以下:
5、泛型接口
注:泛型在聲明的時候能夠不指定具體的類型,繼承的時候也能夠不指定具體類型,可是在使用的時候必須指定具體類型。
下面代碼演示泛型接口:
class Program { /// <summary> /// 泛型接口 /// </summary> public interface IGenericInterface<T> { T GetT(T t); } /// <summary> /// 泛型接口實現類 /// </summary> /// <param name="args"></param> public class GenericGet<T> : IGenericInterface<T> { T varT; public T GetT(T t) { varT = t; return varT; } } static void Main(string[] args) { #region 泛型接口 IGenericInterface<int> genericInterface = new GenericGet<int>(); var result = genericInterface.GetT(123); Console.WriteLine($"Result={result}"); Console.Read(); #endregion } }
運行結果以下:
6、泛型委託
下面代碼演示泛型委託:
class Program { /// <summary> /// 泛型委託 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> public delegate void SayHi<T>(T t); static void Main(string[] args) { #region 泛型委託 SayHi<string> sayHi = SayHello; sayHi("Hello World"); Console.Read(); #endregion } /// <summary> /// SayHello /// </summary> /// <param name="greeting"></param> public static void SayHello(string greeting) { Console.WriteLine($"{greeting}"); } }
運行結果以下:
7、泛型約束
泛型約束,實際上就是約束的類型T,使T必須遵循必定的規則。好比T必須繼承自某個類或者T必須實現某個接口等等。
怎樣給泛型指定約束?其實也很簡單,只須要where關鍵字,加上約束的條件。
泛型約束總共有五種:
約束 | s說明 |
T:結構 | 類型參數必須是值類型 |
T:類 | 類型參數必須是引用類型;這一點也適用於任何類、接口、委託或數組類型。 |
T:new() | 類型參數必須具備無參數的公共構造函數。 當與其餘約束一塊兒使用時,new() 約束必須最後指定。 |
T:<基類名> | 類型參數必須是指定的基類或派生自指定的基類。 |
T:<接口名稱> | 類型參數必須是指定的接口或實現指定的接口。 能夠指定多個接口約束。 約束接口也能夠是泛型的。 |
7.1基類約束
下面代碼演示基類約束:
/// <summary> /// 運動類接口 /// </summary> public interface ISports { void Pingpong(); } /// <summary> /// 人類基類 /// </summary> public class People { public string Name { get; set; } public virtual void Greeting() { Console.WriteLine("Hello World."); } } /// <summary> /// 中國人 /// </summary> public class Chinese : People, ISports { public void FineTradition() { Console.WriteLine("自古以來,中華民族就保持着勤勞的優良傳統。"); } public override void Greeting() { Console.WriteLine("吃飯了沒?"); } public void Pingpong() { Console.WriteLine("乒乓球是中國的國球。"); } } static void Main(string[] args) { #region 泛型約束:基類約束 Chinese chinese = new Chinese() { Name = "中國人" }; ShowPeople(chinese); Console.Read(); #endregion } /// <summary> /// 基類約束 /// </summary> /// <param name="obj"></param> public static void ShowPeople<T>(T tParam) where T:People { Console.WriteLine($"{((People)tParam).Name}"); } }
運行結果以下:
注:基類約束時,基類不能是密封類,即不能是sealed類。sealed類表示該類不能被繼承,在這裏用做約束就無任何意義了,由於sealed類沒有子類。
7.2接口約束
下面代碼演示接口約束:
class Program { /// <summary> /// 運動類接口 /// </summary> public interface ISports { void Pingpong(); } /// <summary> /// 人類基類 /// </summary> public class People { public string Name { get; set; } public virtual void Greeting() { Console.WriteLine("Hello World."); } } /// <summary> /// 中國人 /// </summary> public class Chinese : People, ISports { public void FineTradition() { Console.WriteLine("自古以來,中華民族就保持着勤勞的優良傳統。"); } public override void Greeting() { Console.WriteLine("吃飯了沒?"); } public void Pingpong() { Console.WriteLine("乒乓球是中國的國球。"); } } static void Main(string[] args) { #region 泛型約束:接口約束 Chinese chinese = new Chinese() { Name = "中國人" }; GetSportsByInterface(chinese); Console.Read(); #endregion } /// <summary> /// 接口約束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T GetSportsByInterface<T>(T t) where T : ISports { t.Pingpong(); return t; } }
運行結果以下:
7.3引用類型約束 class
引用類型約束保證T必定是引用類型的。
下面代碼演示引用類型約束:
class Program { /// <summary> /// 運動類接口 /// </summary> public interface ISports { void Pingpong(); } /// <summary> /// 人類基類 /// </summary> public class People { public string Name { get; set; } public virtual void Greeting() { Console.WriteLine("Hello World."); } } /// <summary> /// 中國人 /// </summary> public class Chinese : People, ISports { public void FineTradition() { Console.WriteLine("自古以來,中華民族就保持着勤勞的優良傳統。"); } public override void Greeting() { Console.WriteLine("吃飯了沒?"); } public void Pingpong() { Console.WriteLine("乒乓球是中國的國球。"); } } static void Main(string[] args) { #region 泛型約束:引用類型約束 Chinese chinese = new Chinese() { Name = "中國人" }; GetSportsByClass(chinese); Console.Read(); #endregion } /// <summary> /// 引用類型約束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T GetSportsByClass<T>(T t) where T : class { if (t is ISports) { (t as ISports).Pingpong(); } return t; } }
運行結果以下:
7.4值類型約束 struct
值類型約束保證T必定是值類型的。
下面代碼演示值類型約束:
class Program { /// <summary> /// 績效工資 /// </summary> public struct Achievement { public double MeritPay { get; set; } public string Level { get; set; } public double ReallyPay() { switch (Level) { case "A": MeritPay = MeritPay * 1.0; break; case "B": MeritPay = MeritPay * 0.8; break; case "C": MeritPay = MeritPay * 0.6; break; case "D": MeritPay = 0; break; default: MeritPay = 0; break; }; return MeritPay; } } static void Main(string[] args) { #region 泛型約束:值類型約束 Achievement achievement = new Achievement { MeritPay = 500, Level = "B" }; var result = GetReallyPay(achievement).ReallyPay(); Console.WriteLine($"ReallyPay={result}"); Console.Read(); #endregion } /// <summary> /// 值類型約束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T GetReallyPay<T>(T t) where T : struct { return t; } }
運行結果以下:
7.5無參數構造函數約束 new()
下面代碼演示無參數構造函數約束:
class Program { /// <summary> /// 運動類接口 /// </summary> public interface ISports { void Pingpong(); } /// <summary> /// 人類基類 /// </summary> public class People { public string Name { get; set; } public virtual void Greeting() { Console.WriteLine("Hello World."); } } /// <summary> /// 中國人 /// </summary> public class Chinese : People, ISports { public void FineTradition() { Console.WriteLine("自古以來,中華民族就保持着勤勞的優良傳統。"); } public override void Greeting() { Console.WriteLine("吃飯了沒?"); } public void Pingpong() { Console.WriteLine("乒乓球是中國的國球。"); } } /// <summary> /// 廣東人 /// </summary> public class Guangdong : Chinese { public Guangdong() { } public string Dialect { get; set; } public void Mahjong() { Console.WriteLine("這麻將上癮的時候,一我的也說是三缺一呀。"); } } static void Main(string[] args) { #region 泛型約束:無參數構造函數約束 Guangdong guangdong = new Guangdong() { Name = "廣東人" }; GetMahjong(guangdong); Console.Read(); #endregion } /// <summary> /// 無參數構造函數約束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T GetMahjong<T>(T t) where T : People, ISports, new() { if (t is Guangdong) { (t as Guangdong).Mahjong(); } return t; } }
運行結果以下:
從上面能夠看出,泛型約束能夠有多個,可是有多個泛型約束時,new()約束要放到最後。
八:泛型的協變和逆變
協變和逆變是在.NET 4.0的時候出現的,只能放在接口或者委託的泛型參數前面,out協變covariant,用來修飾返回值;in:逆變contravariant,用來修飾
傳入參數。
下面代碼演示父類與子類的聲明方式:
class Program { /// <summary> /// 動物基類 /// </summary> public class Animal { public int Breed { get; set; } } /// <summary> /// 貓類 /// </summary> public class Cat : Animal { public string Name { get; set; } } static void Main(string[] args) { #region 泛型的協變和逆變 //直接聲明Animal類 Animal animal = new Animal(); //直接聲明Cat類 Cat cat = new Cat(); //聲明子類對象指向父類 Animal animal2 = new Cat(); //聲明Animal類的集合 List<Animal> listAnimal = new List<Animal>(); //聲明Cat類的集合 List<Cat> listCat = new List<Cat>(); #endregion } }
以上代碼是能夠正常運行的。假如使用下面的聲明方式,是否正確呢?
List<Animal> list = new List<Cat>();
答案是錯誤的,由於List<Animal>和List<Cat>之間沒有父子關係。
解決方法是使用協變的方式:
IEnumerable<Animal> List1 = new List<Animal>(); IEnumerable<Animal> List2 = new List<Cat>();
按F12查看IEnumerable定義:
能夠看到,在泛型接口的T前面有一個out關鍵字修飾,並且T只能是返回值類型,不能做爲參數類型,這就是協變。使用協變之後,左邊聲明的是基類,
右邊的聲明能夠是基類或者基類的子類。
協變除了能夠用在接口上面外,還能夠用在委託上面:
Func<Animal> func = new Func<Cat>(() => null);
除了使用.NET框架定義好協變之外,咱們也能夠自定義協變:
//使用自定義協變 ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>(); ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();
再來看看逆變:
在泛型接口的T前面有一個In關鍵字修飾,並且T只能方法參數,不能做爲返回值類型,這就是逆變。
/// <summary> /// 逆變 只能是方法參數 /// </summary> /// <typeparam name="T"></typeparam> public interface ICustomerListIn<in T> { void Show(T t); } public class CustomerListIn<T> : ICustomerListIn<T> { public void Show(T t) { } }
使用自定義逆變:
//使用自定義逆變 ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>(); ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();
協變和逆變也能夠同時使用。
下面代碼演示自定義協變與逆變:
class Program { /// <summary> /// 動物基類 /// </summary> public class Animal { public int Breed { get; set; } } /// <summary> /// 貓類 /// </summary> public class Cat : Animal { public string Name { get; set; } } #region 泛型的自定義協變和逆變 /// <summary> /// inT-逆變 outT-協變 /// </summary> /// <typeparam name="inT"></typeparam> /// <typeparam name="outT"></typeparam> public interface IMyList<in inT, out outT> { void Show(inT t); outT Get(); outT Do(inT t); } public class MyList<T1, T2> : IMyList<T1, T2> { public void Show(T1 t) { Console.WriteLine(t.GetType().Name); } public T2 Get() { Console.WriteLine(typeof(T2).Name); return default(T2); } public T2 Do(T1 t) { Console.WriteLine(t.GetType().Name); Console.WriteLine(typeof(T2).Name); return default(T2); } } #endregion static void Main(string[] args) { #region 泛型的自定義協變與逆變 IMyList<Cat, Animal> myList1 = new MyList<Cat, Animal>(); IMyList<Cat, Animal> myList2 = new MyList<Cat, Cat>(); //協變 IMyList<Cat, Animal> myList3 = new MyList<Animal, Animal>(); //逆變 IMyList<Cat, Animal> myList4 = new MyList<Animal, Cat>(); //逆變+協變 myList1.Get(); myList2.Get(); myList3.Get(); myList4.Get(); Console.Read(); #endregion } }
運行結果以下:
9、泛型緩存
類中的靜態類型不管實例化多少次,在內存中只會有一個,靜態構造函數只會執行一次。在泛型類中,T類型不一樣,每一個不一樣的T類型,都會產生一個不一樣
的副本,因此會產生不一樣的靜態屬性、不一樣的靜態構造函數。
下面代碼演示泛型緩存:
class Program { /// <summary> /// 泛型緩存 /// </summary> /// <typeparam name="T"></typeparam> public class GenericCache<T> { private static readonly string TypeTime = ""; static GenericCache() { Console.WriteLine("這個是泛型緩存的靜態構造函數:"); TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff")); } public static string GetCache() { return TypeTime; } } /// <summary> /// 泛型緩存測試類 /// </summary> public class GenericCacheTest { public static void Show() { for (int i = 0; i < 5; i++) { Console.WriteLine(GenericCache<int>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<long>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<DateTime>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<string>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<GenericCacheTest>.GetCache()); Thread.Sleep(10); } } } static void Main(string[] args) { #region 泛型緩存 GenericCacheTest.Show(); Console.Read(); #endregion } }
運行結果以下:
從上面的截圖中能夠看出,泛型會爲不一樣的類型都建立一個副本,所以靜態構造函數會執行5次,另外每次靜態屬性的值都是同樣的。利用泛型的這一特性,能夠實現緩存。
注:只能爲不一樣的類型緩存一次;泛型緩存比字典緩存效率高;泛型緩存不能主動釋放。