C#泛型詳解

轉(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ā)菜鳥

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末曹鸠,一起剝皮案震驚了整個(gè)濱河市煌茬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彻桃,老刑警劉巖宣旱,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叛薯,居然都是意外死亡浑吟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門耗溜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來组力,“玉大人,你說我怎么就攤上這事抖拴×亲郑” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵阿宅,是天一觀的道長候衍。 經(jīng)常有香客問我,道長洒放,這世上最難降的妖魔是什么蛉鹿? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮往湿,結(jié)果婚禮上妖异,老公的妹妹穿的比我還像新娘。我一直安慰自己领追,他們只是感情好他膳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绒窑,像睡著了一般棕孙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天蟀俊,我揣著相機(jī)與錄音分歇,去河邊找鬼。 笑死欧漱,一個(gè)胖子當(dāng)著我的面吹牛职抡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播误甚,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼缚甩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了窑邦?” 一聲冷哼從身側(cè)響起擅威,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冈钦,沒想到半個(gè)月后郊丛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瞧筛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年厉熟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片较幌。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揍瑟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乍炉,到底是詐尸還是另有隱情绢片,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布岛琼,位于F島的核電站底循,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏槐瑞。R本人自食惡果不足惜熙涤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望随珠。 院中可真熱鬧灭袁,春花似錦猬错、人聲如沸窗看。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽显沈。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拉讯,已是汗流浹背涤浇。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留魔慷,地道東北人只锭。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像院尔,于是被迫代替她去往敵國和親蜻展。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容