轉(zhuǎn)載自:https://www.cnblogs.com/dotnet261010/p/9034594.html
作者:.NET開發(fā)菜鳥
這篇文章主要講解C#中的泛型,泛型在C#中有很重要的地位屯掖,尤其是在搭建項(xiàng)目框架的時(shí)候筑悴。
一育苟、什么是泛型
泛型是C#2.0推出的新語法,不是語法糖发钝,而是2.0由框架升級提供的功能摸恍。
我們在編程程序時(shí),經(jīng)常會遇到功能非常相似的模塊璧南,只是它們處理的數(shù)據(jù)不一樣。但我們沒有辦法师逸,只能分別寫多個(gè)方法來處理不同的數(shù)據(jù)類型司倚。這個(gè)時(shí)候,那么問題來了篓像,有沒有一種辦法动知,用同一個(gè)方法來處理傳入不同種類型參數(shù)的辦法呢?泛型的出現(xiàn)就是專門來解決這個(gè)問題的员辩。
二盒粮、為什么使用泛型
先來看下面一個(gè)例子:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace MyGeneric
{
? ? publicclass CommonMethod
? ? {
? ? ? ? ///<summary>/// 打印個(gè)int值
? ? ? ? ////// 因?yàn)榉椒暶鞯臅r(shí)候,寫死了參數(shù)類型
? ? ? ? /// 已婚的男人 Eleven San
? ? ? ? ///</summary>///<param name="iParameter"></param>publicstaticvoidShowInt(int iParameter)
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("This is {0},parameter={1},type={2}",
? ? ? ? ? ? ? ? typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter);
? ? ? ? }
? ? ? ? ///<summary>/// 打印個(gè)string值
? ? ? ? ///</summary>///<param name="sParameter"></param>publicstaticvoidShowString(string sParameter)
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("This is {0},parameter={1},type={2}",
? ? ? ? ? ? ? ? typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter);
? ? ? ? }
? ? ? ? ///<summary>/// 打印個(gè)DateTime值
? ? ? ? ///</summary>///<param name="oParameter"></param>publicstaticvoid ShowDateTime(DateTime dtParameter)
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("This is {0},parameter={1},type={2}",
? ? ? ? ? ? ? ? typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter);
? ? ? ? }
? ? }
}
?結(jié)果:
從上面的結(jié)果中我們可以看出這三個(gè)方法奠滑,除了傳入的參數(shù)不同外丹皱,其里面實(shí)現(xiàn)的功能都是一樣的。在1.0版的時(shí)候宋税,還沒有泛型這個(gè)概念摊崭,那么怎么辦呢。相信很多人會想到了OOP三大特性之一的繼承杰赛,我們知道呢簸,C#語言中,object是所有類型的基類乏屯,將上面的代碼進(jìn)行以下優(yōu)化:
publicstaticvoidShowObject(object oParameter)
{
? ? ? Console.WriteLine("This is {0},parameter={1},type={2}",
? ? ? ? typeof(CommonMethod), oParameter.GetType().Name, oParameter);
}
?結(jié)果:
從上面的結(jié)果中我們可以看出根时,使用Object類型達(dá)到了我們的要求,解決了代碼的可復(fù)用辰晕「蛴可能有人會問定義的是object類型的,為什么可以傳入int含友、string等類型呢忘苛?原因有二:
1蝉娜、object類型是一切類型的父類。
2扎唾、通過繼承召川,子類擁有父類的一切屬性和行為,任何父類出現(xiàn)的地方胸遇,都可以用子類來代替荧呐。
但是上面object類型的方法又會帶來另外一個(gè)問題:裝箱和拆箱,會損耗程序的性能纸镊。
微軟在C#2.0的時(shí)候推出了泛型倍阐,可以很好的解決上面的問題。
三逗威、泛型類型參數(shù)
在泛型類型或方法定義中峰搪,類型參數(shù)是在其實(shí)例化泛型類型的一個(gè)變量時(shí),客戶端指定的特定類型的占位符凯旭。 泛型類(GenericList<T>)無法按原樣使用概耻,因?yàn)樗皇钦嬲念愋停凰袷穷愋偷乃{(lán)圖罐呼。 若要使用GenericList<T>鞠柄,客戶端代碼必須通過指定尖括號內(nèi)的類型參數(shù)來聲明并實(shí)例化構(gòu)造類型。 此特定類的類型參數(shù)可以是編譯器可識別的任何類型嫉柴。 可創(chuàng)建任意數(shù)量的構(gòu)造類型實(shí)例厌杜,其中每個(gè)使用不同的類型參數(shù)。
上面例子中的代碼可以修改如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? public class GenericMethod
? ? {
? ? ? ? /// <summary>
? ? ? ? /// 泛型方法
? ? ? ? /// </summary>
? ? ? ? /// <typeparam name="T"></typeparam>
? ? ? ? /// <param name="tParameter"></param>
? ? ? ? public static void Show<T>(T tParameter)
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("This is {0},parameter={1},type={2}",
? ? ? ? ? ? ? ? typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString());
? ? ? ? }
? ? }
}
調(diào)用:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? class Program
? ? {
? ? ? ? static void Main(string[] args)
? ? ? ? {
? ? ? ? ? ? int iValue = 123;
? ? ? ? ? ? string sValue = "456";
? ? ? ? ? ? DateTime dtValue = DateTime.Now;
? ? ? ? ? ? Console.WriteLine("***********CommonMethod***************");
? ? ? ? ? ? CommonMethod.ShowInt(iValue);
? ? ? ? ? ? CommonMethod.ShowString(sValue);
? ? ? ? ? ? CommonMethod.ShowDateTime(dtValue);
? ? ? ? ? ? Console.WriteLine("***********Object***************");
? ? ? ? ? ? CommonMethod.ShowObject(iValue);
? ? ? ? ? ? CommonMethod.ShowObject(sValue);
? ? ? ? ? ? CommonMethod.ShowObject(dtValue);
? ? ? ? ? ? Console.WriteLine("***********Generic***************");
? ? ? ? ? ? GenericMethod.Show<int>(iValue);
? ? ? ? ? ? GenericMethod.Show<string>(sValue);
? ? ? ? ? ? GenericMethod.Show<DateTime>(dtValue);
? ? ? ? ? ? Console.ReadKey();
? ? ? ? }
? ? }
}
顯示結(jié)果:
為什么泛型可以解決上面的問題呢计螺?
泛型是延遲聲明的:即定義的時(shí)候沒有指定具體的參數(shù)類型夯尽,把參數(shù)類型的聲明推遲到了調(diào)用的時(shí)候才指定參數(shù)類型。 延遲思想在程序架構(gòu)設(shè)計(jì)的時(shí)候很受歡迎登馒。例如:分布式緩存隊(duì)列呐萌、EF的延遲加載等等。
泛型究竟是如何工作的呢谊娇?
控制臺程序最終會編譯成一個(gè)exe程序肺孤,exe被點(diǎn)擊的時(shí)候,會經(jīng)過JIT(即時(shí)編譯器)的編譯济欢,最終生成二進(jìn)制代碼赠堵,才能被計(jì)算機(jī)執(zhí)行。泛型加入到語法以后法褥,VS自帶的編譯器又做了升級茫叭,升級之后編譯時(shí)遇到泛型,會做特殊的處理:生成占位符半等。再次經(jīng)過JIT編譯的時(shí)候揍愁,會把上面編譯生成的占位符替換成具體的數(shù)據(jù)類型呐萨。請看下面一個(gè)例子:
Console.WriteLine(typeof(List<>));
Console.WriteLine(typeof(Dictionary<,>));
從上面的截圖中可以看出:泛型在編譯之后會生成占位符。
注意:占位符需要在英文輸入法狀態(tài)下才能輸入莽囤,只需要按一次波浪線(數(shù)字1左邊的鍵位)的鍵位即可谬擦,不需要按Shift鍵。
1朽缎、泛型性能問題
請看一下的一個(gè)例子惨远,比較普通方法、Object參數(shù)類型的方法话肖、泛型方法的性能北秽。
添加一個(gè)Monitor類,讓三種方法執(zhí)行同樣的操作最筒,比較用時(shí)長短:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? public class Monitor
? ? {
? ? ? ? public static void Show()
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("****************Monitor******************");
? ? ? ? ? ? {
? ? ? ? ? ? ? ? int iValue = 12345;
? ? ? ? ? ? ? ? long commonSecond = 0;
? ? ? ? ? ? ? ? long objectSecond = 0;
? ? ? ? ? ? ? ? long genericSecond = 0;
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Stopwatch watch = new Stopwatch();
? ? ? ? ? ? ? ? ? ? watch.Start();
? ? ? ? ? ? ? ? ? ? for (int i = 0; i < 100000000; i++)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ShowInt(iValue);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? watch.Stop();
? ? ? ? ? ? ? ? ? ? commonSecond = watch.ElapsedMilliseconds;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Stopwatch watch = new Stopwatch();
? ? ? ? ? ? ? ? ? ? watch.Start();
? ? ? ? ? ? ? ? ? ? for (int i = 0; i < 100000000; i++)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ShowObject(iValue);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? watch.Stop();
? ? ? ? ? ? ? ? ? ? objectSecond = watch.ElapsedMilliseconds;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Stopwatch watch = new Stopwatch();
? ? ? ? ? ? ? ? ? ? watch.Start();
? ? ? ? ? ? ? ? ? ? for (int i = 0; i < 100000000; i++)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? Show<int>(iValue);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? watch.Stop();
? ? ? ? ? ? ? ? ? ? genericSecond = watch.ElapsedMilliseconds;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}"
? ? ? ? ? ? ? ? ? ? , commonSecond, objectSecond, genericSecond);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? #region PrivateMethod
? ? ? ? private static void ShowInt(int iParameter)
? ? ? ? {
? ? ? ? ? ? //do nothing
? ? ? ? }
? ? ? ? private static void ShowObject(object oParameter)
? ? ? ? {
? ? ? ? ? ? //do nothing
? ? ? ? }
? ? ? ? private static void Show<T>(T tParameter)
? ? ? ? {
? ? ? ? ? ? //do nothing
? ? ? ? }
? ? ? ? #endregion
? ? }
}
Main()方法調(diào)用:
Monitor.Show();
結(jié)果:
從結(jié)果中可以看出:泛型方法的性能最高贺氓,其次是普通方法,object方法的性能最低床蜘。
四辙培、泛型類
除了方法可以是泛型以外,類也可以是泛型的悄泥,例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? /// <summary>
? ? /// 泛型類
? ? /// </summary>
? ? /// <typeparam name="T"></typeparam>
? ? public class GenericClass<T>
? ? {
? ? ? ? public T _T;
? ? }
}
Main()方法中調(diào)用:
// T是int類型
GenericClass<int> genericInt = new GenericClass<int>();
genericInt._T = 123;
// T是string類型
GenericClass<string> genericString = new GenericClass<string>();
genericString._T = "123";
除了可以有泛型類虏冻,也可以有泛型接口肤粱,例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? /// <summary>
? ? /// 泛型接口
? ? /// </summary>
? ? public interface IGenericInterface<T>
? ? {
? ? ? ? //泛型類型的返回值
? ? ? ? T GetT(T t);
? ? }
}
也可以有泛型委托:
publicdelegatevoidSayHi(T t);//泛型委托
注意:
1弹囚、泛型在聲明的時(shí)候可以不指定具體的類型,但是在使用的時(shí)候必須指定具體類型领曼,例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? /// <summary>
? ? /// 使用泛型的時(shí)候必須指定具體類型鸥鹉,
? ? /// 這里的具體類型是int
? ? /// </summary>
? ? public class CommonClass :GenericClass<int>
? ? {
? ? }
}
如果子類也是泛型的,那么繼承的時(shí)候可以不指定具體類型庶骄,例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? /// <summary>
? ? /// 使用泛型的時(shí)候必須指定具體類型毁渗,
? ? /// 這里的具體類型是int
? ? /// </summary>
? ? public class CommonClass :GenericClass<int>
? ? {
? ? }
? ? /// <summary>
? ? /// 子類也是泛型的,繼承的時(shí)候可以不指定具體類型
? ? /// </summary>
? ? /// <typeparam name="T"></typeparam>
? ? public class CommonClassChild<T>:GenericClass<T>
? ? {
? ? }
}
2单刁、類實(shí)現(xiàn)泛型接口也是這種情況灸异,例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? /// <summary>
? ? /// 必須指定具體類型
? ? /// </summary>
? ? public class Common : IGenericInterface<string>
? ? {
? ? ? ? public string GetT(string t)
? ? ? ? {
? ? ? ? ? ? throw new NotImplementedException();
? ? ? ? }
? ? }
? ? /// <summary>
? ? /// 可以不知道具體類型,但是子類也必須是泛型的
? ? /// </summary>
? ? /// <typeparam name="T"></typeparam>
? ? public class CommonChild<T> : IGenericInterface<T>
? ? {
? ? ? ? public T GetT(T t)
? ? ? ? {
? ? ? ? ? ? throw new NotImplementedException();
? ? ? ? }
? ? }
}
五羔飞、泛型約束
先來看看下面的一個(gè)例子:
定義一個(gè)People類肺樟,里面有屬性和方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? public interface ISports
? ? {
? ? ? ? void Pingpang();
? ? }
? ? public interface IWork
? ? {
? ? ? ? void Work();
? ? }
? ? public class People
? ? {
? ? ? ? public int Id { get; set; }
? ? ? ? public string Name { get; set; }
? ? ? ? public void Hi()
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("Hi");
? ? ? ? }
? ? }
? ? public class Chinese : People, ISports, IWork
? ? {
? ? ? ? public void Tradition()
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("仁義禮智信,溫良恭儉讓");
? ? ? ? }
? ? ? ? public void SayHi()
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("吃了么逻淌?");
? ? ? ? }
? ? ? ? public void Pingpang()
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("打乒乓球...");
? ? ? ? }
? ? ? ? public void Work()
? ? ? ? {
? ? ? ? ? ? throw new NotImplementedException();
? ? ? ? }
? ? }
? ? public class Hubei : Chinese
? ? {
? ? ? ? public Hubei(int version)
? ? ? ? { }
? ? ? ? public string Changjiang { get; set; }
? ? ? ? public void Majiang()
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("打麻將啦么伯。。");
? ? ? ? }
? ? }
? ? public class Japanese : ISports
? ? {
? ? ? ? public int Id { get; set; }
? ? ? ? public string Name { get; set; }
? ? ? ? public void Hi()
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("Hi");
? ? ? ? }
? ? ? ? public void Pingpang()
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("打乒乓球...");
? ? ? ? }
? ? }
}
在Main()方法里面實(shí)例化:
People people = new People()
{
? ? ? ? Id = 123,
? ? ? ? Name = "走自己的路"
};
Chinese chinese = new Chinese()
{
? ? ? ? Id = 234,
? ? ? ? Name = "晴天"
};
Hubei hubei = new Hubei(123)
{
? ? ? ? Id = 345,
? ? ? ? Name = "流年"
};
Japanese japanese = new Japanese()
{
? ? ? ? Id = 7654,
? ? ? ? Name = "werwer"
};
這時(shí)有一個(gè)需求:需要打印出Id和Name屬性的值卡儒,將ShowObject()方法修改如下:
但是這樣修改報(bào)錯(cuò)了:object類里面沒有Id和Name屬性田柔,可能會有人說俐巴,強(qiáng)制類型轉(zhuǎn)換一下就行了啊:
public static void ShowObject(object oParameter)
{
? ? ? ? Console.WriteLine("This is {0},parameter={1},type={2}",
? ? ? ? typeof(CommonMethod), oParameter.GetType().Name, oParameter);
? ? ? ? Console.WriteLine($"{((People)oParameter).Id}_{((People)oParameter).Name}");
}
這樣修改以后硬爆,代碼不會報(bào)錯(cuò)了欣舵,這時(shí)我們在Main()方法里面調(diào)用:
CommonMethod.ShowObject(people);
CommonMethod.ShowObject(chinese);
CommonMethod.ShowObject(hubei);
CommonMethod.ShowObject(japanese);
結(jié)果:
可以看出程序報(bào)錯(cuò)了,因?yàn)镴apanese沒有繼承自People摆屯,這里類型轉(zhuǎn)換的時(shí)候失敗了邻遏。這樣會造成類型不安全的問題。那么怎么解決類型不安全的問題呢虐骑?那就是使用泛型約束准验。
所謂的泛型約束,實(shí)際上就是約束的類型T廷没。使T必須遵循一定的規(guī)則糊饱。比如T必須繼承自某個(gè)類,或者T必須實(shí)現(xiàn)某個(gè)接口等等颠黎。那么怎么給泛型指定約束另锋?其實(shí)也很簡單,只需要where關(guān)鍵字狭归,加上約束的條件夭坪。
泛型約束總共有五種。
1过椎、基類約束
上面打印的方法約束T類型必須是People類型室梅。
/// <summary>
/// 基類約束:約束T必須是People類型或者是People的子類
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tParameter"></param>
public static void Show<T>(T tParameter) where T : People
{
? ? ? Console.WriteLine($"{tParameter.Id}_{tParameter.Name}");
? ? ? tParameter.Hi();
}
注意:
基類約束時(shí),基類不能是密封類疚宇,即不能是sealed類亡鼠。sealed類表示該類不能被繼承,在這里用作約束就無任何意義敷待,因?yàn)閟ealed類沒有子類间涵。
2、接口約束
/// <summary>
/// 接口約束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T Get<T>(T t) where T : ISports
{
? ? ? t.Pingpang();
? ? ? return t;
}
3榜揖、引用類型約束 class
引用類型約束保證T一定是引用類型的勾哩。
/// <summary>
/// 引用類型約束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T Get<T>(T t) where T : class
{
? ? ? return t;
}
4、值類型約束? struct
值類型約束保證T一定是值類型的举哟。
/// <summary>
/// 值類型類型約束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T Get<T>(T t) where T : struct
{
? ? ? return t;
}
5思劳、無參數(shù)構(gòu)造函數(shù)約束? new()
/// <summary>
/// new()約束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T Get<T>(T t) where T : new()
{
? ? return t;
}
泛型約束也可以同時(shí)約束多個(gè),例如:
public static void Show<T>(T tParameter)
? ? ? ? ? ? where T : People, ISports, IWork, new()
{
? ? ? Console.WriteLine($"{tParameter.Id}_{tParameter.Name}");
? ? ? tParameter.Hi();
? ? ? tParameter.Pingpang();
? ? ? tParameter.Work();
}
注意:有多個(gè)泛型約束時(shí)炎滞,new()約束一定是在最后敢艰。
六、泛型的協(xié)變和逆變
協(xié)變和逆變是在.NET 4.0的時(shí)候出現(xiàn)的册赛,只能放在接口或者委托的泛型參數(shù)前面钠导,out 協(xié)變covariant震嫉,用來修飾返回值;in:逆變contravariant牡属,用來修飾傳入?yún)?shù)票堵。
先看下面的一個(gè)例子:
定義一個(gè)Animal類:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? public class Animal
? ? {
? ? ? ? public int Id { get; set; }
? ? }
}
然后在定義一個(gè)Cat類繼承自Animal類:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? public class Cat :Animal
? ? {
? ? ? ? public string Name { get; set; }
? ? }
}
在Main()方法可以這樣調(diào)用:
// 直接聲明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>();
那么問題來了:下面的一句代碼是不是正確的呢?
List list =newList();
?可能有人會認(rèn)為是正確的:因?yàn)橐恢籆at屬于Animal逮栅,那么一群Cat也應(yīng)該屬于Animal啊悴势。但是實(shí)際上這樣聲明是錯(cuò)誤的:因?yàn)長ist<Cat>和List<Animal>之間沒有父子關(guān)系。
這時(shí)就可以用到協(xié)變和逆變了措伐。
// 協(xié)變
IEnumerable List1 =newList();
IEnumerable List2 =newList();
?F12查看定義:
可以看到特纤,在泛型接口的T前面有一個(gè)out關(guān)鍵字修飾,而且T只能是返回值類型侥加,不能作為參數(shù)類型捧存,這就是協(xié)變。使用了協(xié)變以后担败,左邊聲明的是基類昔穴,右邊可以聲明基類或者基類的子類。
協(xié)變除了可以用在接口上面提前,也可以用在委托上面:
Func func =newFunc(() =>null);
?除了使用.NET框架定義好的以為吗货,我們還可以自定義協(xié)變,例如:
/// <summary>
/// out 協(xié)變 只能是返回結(jié)果
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListOut<out T>
{
? ? T Get();
}
public class CustomerListOut<T> : ICustomerListOut<T>
{
? ? public T Get()
? ? {
? ? ? ? return default(T);
? ? }
}
使用自定義的協(xié)變:
// 使用自定義協(xié)變
ICustomerListOut customerList1 =newCustomerListOut();
ICustomerListOut customerList2 =newCustomerListOut();
在來看看逆變狈网。
在泛型接口的T前面有一個(gè)In關(guān)鍵字修飾宙搬,而且T只能方法參數(shù),不能作為返回值類型孙援,這就是逆變害淤。請看下面的自定義逆變:
/// <summary>
/// 逆變 只能是方法參數(shù)
/// </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 customerListCat1 =newCustomerListIn();
ICustomerListIn customerListCat2 =newCustomerListIn();
協(xié)變和逆變也可以同時(shí)使用扇雕,看看下面的例子:
/// <summary>
/// inT 逆變
/// outT 協(xié)變
/// </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);
? ? ? }
}
使用:
IMyList myList1 =newMyList();
IMyList myList2 =newMyList();//協(xié)變
IMyList myList3 =newMyList();//逆變
IMyList myList4 =newMyList();//逆變+協(xié)變
七拓售、泛型緩存
在前面我們學(xué)習(xí)過,類中的靜態(tài)類型無論實(shí)例化多少次镶奉,在內(nèi)存中只會有一個(gè)础淤。靜態(tài)構(gòu)造函數(shù)只會執(zhí)行一次。在泛型類中哨苛,T類型不同鸽凶,每個(gè)不同的T類型,都會產(chǎn)生一個(gè)不同的副本建峭,所以會產(chǎn)生不同的靜態(tài)屬性玻侥、不同的靜態(tài)構(gòu)造函數(shù),請看下面的例子:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? public class GenericCache<T>
? ? {
? ? ? ? static GenericCache()
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("This is GenericCache 靜態(tài)構(gòu)造函數(shù)");
? ? ? ? ? ? _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
? ? ? ? }
? ? ? ? private static string _TypeTime = "";
? ? ? ? public static string GetCache()
? ? ? ? {
? ? ? ? ? ? return _TypeTime;
? ? ? ? }
? ? }
}
然后新建一個(gè)測試類亿蒸,用來測試GenericCache類的執(zhí)行順序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MyGeneric
{
? ? 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);
? ? ? ? ? ? }
? ? ? ? }
? ? }
}
Main()方法里面調(diào)用:
GenericCacheTest.Show();
結(jié)果:
從上面的截圖中可以看出凑兰,泛型會為不同的類型都創(chuàng)建一個(gè)副本掌桩,所以靜態(tài)構(gòu)造函數(shù)會執(zhí)行5次。 而且每次靜態(tài)屬性的值都是一樣的姑食。利用泛型的這一特性波岛,可以實(shí)現(xiàn)緩存。
注意:只能為不同的類型緩存一次音半。泛型緩存比字典緩存效率高则拷。泛型緩存不能主動釋放。
轉(zhuǎn)載自:https://www.cnblogs.com/dotnet261010/p/9034594.html
作者:.NET開發(fā)菜鳥