新聞中心
【稿件】泛型是程序設(shè)計(jì)語(yǔ)言的一種風(fēng)格,允許程序員在強(qiáng)類(lèi)型程序設(shè)計(jì)語(yǔ)言中編寫(xiě)代碼時(shí)使用一些以后才指定的類(lèi)型,在實(shí)例化時(shí)作為參數(shù)指明這些類(lèi)型。泛型在 .NET 中應(yīng)用尤其廣泛,泛型是在 .NET 2.0 CLR 中的增加的一項(xiàng)新功能,類(lèi)似于 C++ 的模板但不如 C++ 的模板靈活,不過(guò)也有一些自己的特性。泛型為 .NET 引入了類(lèi)型參數(shù)的概念,這樣便可以把指定類(lèi)型的工作推遲到客戶(hù)端代碼聲明并實(shí)例化類(lèi)或方法的時(shí)候執(zhí)行。下面我們就來(lái)講解一下泛型的知識(shí)。

成都創(chuàng)新互聯(lián)公司服務(wù)項(xiàng)目包括開(kāi)魯網(wǎng)站建設(shè)、開(kāi)魯網(wǎng)站制作、開(kāi)魯網(wǎng)頁(yè)制作以及開(kāi)魯網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃等。多年來(lái),我們專(zhuān)注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,開(kāi)魯網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶(hù)以成都為中心已經(jīng)輻射到開(kāi)魯省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶(hù)的支持與信任!
一、當(dāng) C# 沒(méi)有泛型
在 .NET 2.0 以前沒(méi)有泛型的時(shí)候,開(kāi)發(fā)人員一直在使用 System.Collections.Stack 類(lèi),它是一個(gè)棧類(lèi)型的集合對(duì)象。 Stack 通過(guò) Push 和 Pop 方法向集合中添加和刪除數(shù)據(jù)。很多開(kāi)發(fā)人員通過(guò)前面的描述都會(huì)認(rèn)為使用 Stack 很簡(jiǎn)單,但是其中存在一個(gè)重大的缺陷。 Stack 類(lèi)所保存的是 object 類(lèi)型,這樣就導(dǎo)致了 CLR 無(wú)法驗(yàn)證 push 進(jìn)集合中的對(duì)象是不是想要的類(lèi)型。
此外當(dāng)我們使用 Pop 方法時(shí)需要將它的返回值轉(zhuǎn)換為我們需要的類(lèi)型,因此這里就存在一個(gè)問(wèn)題,如果 Pop 方法的返回值不是我們需要的類(lèi)型那么就有很大可能引發(fā)異常。這里的返回值轉(zhuǎn)換使用的是強(qiáng)制類(lèi)型轉(zhuǎn)換,由于使用了強(qiáng)制類(lèi)型轉(zhuǎn)換將類(lèi)型檢查放在了運(yùn)行時(shí)進(jìn)行,因此代碼就變得更加脆弱。
使用 Stack 類(lèi)還存在一個(gè)性能問(wèn)題,將值類(lèi)型的實(shí)例傳遞給 Push 方法,運(yùn)行時(shí)將會(huì)對(duì)它進(jìn)行裝箱操作,頻繁的執(zhí)行值類(lèi)型裝箱操作,系統(tǒng)會(huì)頻繁的分配內(nèi)存、復(fù)制值已經(jīng)進(jìn)行垃圾回收,這樣就導(dǎo)致了大量的性能開(kāi)銷(xiāo)。通過(guò)前面的描述部分讀者應(yīng)該看出來(lái)了 Stack 類(lèi)不是類(lèi)型安全的類(lèi),因此在不使用泛型的情況下,我們?nèi)绻薷?Stack 類(lèi)并保證它是類(lèi)型安全的,并且要求它存儲(chǔ)指定的類(lèi)型的話(huà),我們必須這么做:
- public class StackDemo
- {
- public virtual User Pop();
- public virtual void Push(User user);
- //more code
- }
上面的代碼是不是很簡(jiǎn)單?如果你真的這么認(rèn)為那么你就是想多了,由于我們要求只能存儲(chǔ) User 類(lèi)型的隊(duì)形,因此我們需要對(duì) Stack 的每個(gè)方法進(jìn)行重寫(xiě)實(shí)現(xiàn),如果我們還需要一個(gè)存儲(chǔ) Student 類(lèi)型的 Stack ,我們就需要再重寫(xiě)一次 Stack 的每個(gè)方法。這就凸顯了一個(gè)問(wèn)題,代碼中產(chǎn)生了大量的類(lèi)似的代碼和重復(fù)的代碼。
另外在沒(méi)有泛型的情況下如果聲明允許包含 Null 值的變量的時(shí)候就比較麻煩了。一般情況下我們常用的有兩種方法。
方法一:對(duì)需要處理 null 值的每個(gè)類(lèi)型都需要聲明可空數(shù)據(jù)類(lèi)型,我們來(lái)看個(gè)簡(jiǎn)單的例子:
- struct NullInt
- {
- public int Value { get; private set;}
- public bool HasValue {get; private set;}
- }
上述例子很簡(jiǎn)單,但是存在兩個(gè)問(wèn)題,首先如果我們有很多可空類(lèi)型的話(huà),我們就需要編寫(xiě)大量的類(lèi)似代碼,其次如果可空值類(lèi)型發(fā)生了改變那么我們就必須修改所有的可能類(lèi)型聲明,可想而知工作量是非常巨大的,而且也很容易出現(xiàn)紕漏和錯(cuò)誤。
方法二:這個(gè)方法的出現(xiàn)就是為了解決我們?cè)诜椒ㄒ恢兴岬降膬蓚€(gè)問(wèn)題。我們只需要聲明一個(gè)可能類(lèi)型即可,類(lèi)型中包含 object 類(lèi)型的 Value 屬性,同樣我們先來(lái)看一下代碼:
- struct NullType
- {
- public object Value {get; private set;}
- public bool HasValue {get; private set;}
- }
這個(gè)方法雖然充分解決了方法一出現(xiàn)的問(wèn)題,但是它并不完美。因?yàn)檫\(yùn)行時(shí)在設(shè)置 Value 屬性的時(shí)候總是會(huì)對(duì)值類(lèi)型進(jìn)行裝箱,另外通過(guò) NullType.Value 獲取值地時(shí)候需要進(jìn)行強(qiáng)制類(lèi)型裝換,這個(gè)操作在運(yùn)行時(shí)可能會(huì)報(bào)錯(cuò)。
二、泛型概述
泛型類(lèi)型是 C# 2.0 引入的,它的引入在一定程度上減輕了開(kāi)發(fā)人員的壓力,同時(shí)也使得程序變得更加健壯和穩(wěn)定。泛型類(lèi)的語(yǔ)法也很簡(jiǎn)單,用尖括號(hào)聲明泛型類(lèi)型參數(shù)和提供泛型類(lèi)型實(shí)參即可。我們來(lái)看一個(gè)定義泛型的例子:
- public class DataBaseOperating
- {
- private T[] ModelArray{get;}
- public bool Insert(T t)
- {
- //more code
- }
- public T SelectOne()
- {
- //more code
- }
- public List SelectAll()
- {
- //more code
- }
- //more code
- }
前面這段代碼,我們定義了操作數(shù)據(jù)庫(kù)的泛型類(lèi),這個(gè)類(lèi)可以被項(xiàng)目中所有需要操作數(shù)據(jù)庫(kù)的類(lèi)使用,我們只需將類(lèi)型實(shí)參傳遞進(jìn)來(lái)即可。例如我們需要向數(shù)據(jù)庫(kù)插入一條 User 數(shù)據(jù)。我們可以這么做:
- //more code
- DataBaseOperating dbOp=new DataBaseOperating();
- dbOp.Insert(user);
- //more code
我們看到在定義泛型類(lèi)的時(shí)候,我定義類(lèi)型參數(shù)用的是 T ,這么做是大部分 C# 開(kāi)發(fā)人員的一個(gè)習(xí)慣,也可以說(shuō)是一個(gè)大家都默認(rèn)的規(guī)范,我們?cè)陂_(kāi)發(fā)時(shí)一般都會(huì)使用以大寫(xiě)字母 T 作為前綴來(lái)表明它是一個(gè)類(lèi)型參數(shù)。泛型的定義和使用就這么多,是不是很簡(jiǎn)單呢?下面我們就來(lái)講解一下泛型的各個(gè)方面。在學(xué)習(xí)泛型類(lèi)之前我們要先來(lái)了解一下它的優(yōu)點(diǎn),來(lái)看看為什么微軟在 C# 2.0 中引入了泛型類(lèi)。
泛型促進(jìn)了類(lèi)型安全,它確保了參數(shù)化類(lèi)中只有成員明確希望的數(shù)據(jù)類(lèi)型才可以使用;
類(lèi)型檢查會(huì)在編譯時(shí)發(fā)生進(jìn)而減少了在運(yùn)行時(shí)出現(xiàn)強(qiáng)制類(lèi)型轉(zhuǎn)換無(wú)效的錯(cuò)誤;
泛型類(lèi)成員使用的是值類(lèi)型,因此就不會(huì)出現(xiàn) object 裝箱轉(zhuǎn)換操作。并且代碼既保持具體類(lèi)的優(yōu)勢(shì)又避免了具體類(lèi)的開(kāi)銷(xiāo),這樣代碼的性能得以提高,內(nèi)存消耗也變得很少。
構(gòu)造函數(shù)
我們?cè)陂_(kāi)發(fā)中經(jīng)常用到構(gòu)造函數(shù),在泛型類(lèi)和泛型結(jié)構(gòu)中同樣也適用構(gòu)造函數(shù)。泛型類(lèi)/結(jié)構(gòu)的構(gòu)造函數(shù)和普通類(lèi)/結(jié)構(gòu)的構(gòu)造函數(shù)是一模一樣的,不需要類(lèi)型參數(shù)只需要按照普通類(lèi)/結(jié)構(gòu)的構(gòu)造函數(shù)定義方法定義即可。
- public class Demo
- {
- public T key {get;set;}
- public Demo(T t)
- {
- key=t;
- }
- }
- public struct Demo
- {
- public T value {get;set;}
- public Demo(T t)
- {
- value=t;
- }
- }
Tip:構(gòu)造函數(shù)包含類(lèi)型參數(shù)也可以
結(jié)構(gòu)與接口
在 C# 中不僅僅存在泛型類(lèi),還存在泛型接口和泛型結(jié)構(gòu)。泛型接口和泛型結(jié)構(gòu)的語(yǔ)法和泛型類(lèi)相同。這里主要講解一下在類(lèi)中多次實(shí)現(xiàn)同一個(gè)泛型接口。我們先來(lái)看一下代碼:
- public interface IDemo
- {
- ICollection items {get;set;}
- }
- public class Demo:IDemo,IDemo
- {
- ICollection IDemo.items {get;set;}
- ICollection IDemo.items {get;set;}
- }
在上述代碼中,我們?cè)陬?lèi)中顯示實(shí)現(xiàn)了兩個(gè)不同類(lèi)型實(shí)參的同一個(gè)泛型接口,一般來(lái)說(shuō)在類(lèi)中多次實(shí)現(xiàn)泛型接口并非是一個(gè)最優(yōu)的選擇,因?yàn)樗鼤?huì)造成代碼的混淆以及在使用的過(guò)程中造成誤會(huì)。因此除非特殊情況,絕大多數(shù)情況下,我們不應(yīng)該在一個(gè)類(lèi)中多次實(shí)現(xiàn)同一個(gè)接口。
默認(rèn)值
當(dāng)我們需要在泛型類(lèi)的構(gòu)造函數(shù)中部分屬性進(jìn)行初始化,而其他屬性不進(jìn)行初始化,但是我們?cè)陂_(kāi)發(fā)中無(wú)法確定傳入泛型類(lèi)中的類(lèi)型參數(shù)是什么,因此我們也無(wú)法通過(guò)具體的值設(shè)置默認(rèn)值。這種情況在 C# 中可以說(shuō)是非常好解決,我們可以調(diào)用 default 操作符來(lái)給傳入的任意類(lèi)型參數(shù)提供默認(rèn)值。例如下面這段代碼,我們只初始化 Key ,Value 的初始化則利用 default 操作符。
- public class Demo
- {
- T tKey {get;set;}
- T tValue {get;set;}
- public Demo(T key)
- {
- tKey=key;
- tValue=default(T);
- }
- }
Tip:default 中的參數(shù)并非是必須傳入的,在 C#7 中如果可以推斷出數(shù)據(jù)類(lèi)型的話(huà)是不需要指定參數(shù)的。比如 Demo demo = default(T) 就可以寫(xiě)成 Demo demo=default。
多類(lèi)型參數(shù)
前面我們所講的都是單個(gè)類(lèi)型參數(shù)的泛型類(lèi),但是泛型類(lèi)型不僅僅只能具有一個(gè)參數(shù),它可以具有無(wú)限多的參數(shù),例如我們定義一個(gè)泛型類(lèi),它的構(gòu)造函數(shù)接受兩個(gè)不同類(lèi)型的參數(shù),代碼可以這么實(shí)現(xiàn)。
- public class Demo()
- {
- public TKey key {get;set;}
- public TValue value {get;set;}
- public Demo(TKey tKey,TValue tValue)
- {
- key=tKey;
- value=tValue;
- }
- }
我們?cè)谑褂?Demo 時(shí),只需要聲明和實(shí)例化語(yǔ)句尖括號(hào)中指定的多個(gè)類(lèi)型參數(shù)即可。在調(diào)用時(shí)要提供和方法參數(shù)匹配的類(lèi)型。
- Demo demo=new Demo(1,"小明");
- Console.Write($"編號(hào) {demo.key} 是 {demo.value}")
Tip:在 C# 中在同一個(gè)命名空間中可以存在多個(gè)同名但類(lèi)型參數(shù)數(shù)量不同的類(lèi)。在部分文章或圖書(shū)中會(huì)將類(lèi)型參數(shù)數(shù)量稱(chēng)為 元數(shù) 。
嵌套泛型類(lèi)型
嵌套泛型類(lèi)型在開(kāi)發(fā)中用的比較少,但是還是有必要在這里說(shuō)一下,因?yàn)橛胁糠珠_(kāi)發(fā)人員對(duì)于這一塊不甚了解。嵌套泛型類(lèi)型的外層也是一個(gè)泛型類(lèi)型,外層的這個(gè)泛型類(lèi)型通常被稱(chēng)為包容泛型類(lèi)型,嵌套泛型類(lèi)型會(huì)自動(dòng)獲得包容泛型類(lèi)型的類(lèi)型參數(shù),這段話(huà)有些繞口,我詳細(xì)講解一下。
例如 A 是包容泛型類(lèi)型,它有一個(gè)類(lèi)型參數(shù) T,B 是嵌套泛型類(lèi)型,它位于 A 中,那么 B 也可以使用 A 的類(lèi)型參數(shù) T ,如果 B 中也包含一個(gè)類(lèi)型參數(shù) T ,那么 B 會(huì)隱藏 A 的類(lèi)型參數(shù) T 。這里需要提醒的是如果嵌套泛型類(lèi)型的類(lèi)型參數(shù)和包容泛型類(lèi)型的類(lèi)型參數(shù)相同,那么開(kāi)發(fā)工具將會(huì)出現(xiàn)編譯警告,這個(gè)警告是在告知開(kāi)發(fā)人員使用了相同的類(lèi)型參數(shù),因此這里就引出一條編碼規(guī)則:避免在嵌套泛型類(lèi)型中使用同名參數(shù)隱藏外層類(lèi)型的類(lèi)型參數(shù)。
泛型方法
前面我們所說(shuō)的都是泛型類(lèi),在 C# 中除了有泛型類(lèi)還有泛型方法,泛型方法的語(yǔ)法和泛型類(lèi)的語(yǔ)法類(lèi)似,并且泛型方法不僅可以出現(xiàn)在泛型類(lèi)中也可以出現(xiàn)在普通類(lèi)中。泛型方法和泛型類(lèi)相比有一個(gè)很特別的地方,就是泛型方法可以自己推斷類(lèi)型。編譯器可以根據(jù)傳給方法的實(shí)參來(lái)推斷泛型參數(shù)類(lèi)型。因此如果想讓方法類(lèi)型推斷成功,那么實(shí)參類(lèi)型必須與泛型方法的形參相匹配。
三、泛型約束
在開(kāi)發(fā)中大部分情況,我們不允許任何不符合我們要求的類(lèi)型參數(shù)出現(xiàn)在我們的代碼中并引起錯(cuò)誤。要杜絕這個(gè)問(wèn)題就需要用到泛型約束。聲明泛型約束需要使用 where 關(guān)鍵字,后面跟一對(duì) 參數(shù):要求 。這里面的參數(shù)必須是泛型類(lèi)型中聲明的一個(gè)參數(shù),要求描述的是類(lèi)型參數(shù)所能轉(zhuǎn)換成的類(lèi)或接口等條件。泛型約束分為:接口約束、類(lèi)類(lèi)型約束、class 和 struct 約束、多約束以及構(gòu)造函數(shù)約束。下面我們就來(lái)一一講解一下。
接口約束
為規(guī)定某個(gè)數(shù)據(jù)類(lèi)型必須是向某個(gè)接口,需要聲明一個(gè) 接口類(lèi)型約束 ,利用這種約束可以避免需要通過(guò)轉(zhuǎn)型才能調(diào)用一個(gè)顯示接口成員的實(shí)現(xiàn)。下面我們通過(guò)一個(gè)代碼段來(lái)講解一下接口約束。
- public class Demo where T:System.IComparable
- {
- //more code
- }
在上面這段代碼中,我們添加了 System.IComparable 約束,也就是說(shuō)所提供的類(lèi)型參數(shù)都必須實(shí)現(xiàn) System.IComparable 接口。那么當(dāng)我們向 Demo 傳遞 StringBuilder 作為類(lèi)型參數(shù)來(lái)創(chuàng)建 Demo 變量時(shí)編譯器會(huì)報(bào)告一個(gè)錯(cuò)誤,這是因?yàn)?StringBuilder 沒(méi)有實(shí)現(xiàn) IComparable 接口。
類(lèi)類(lèi)型約束
當(dāng)我們需要將類(lèi)型實(shí)參轉(zhuǎn)換為特定的類(lèi)類(lèi)型時(shí)就需要用到 類(lèi)類(lèi)型約束。類(lèi)類(lèi)型約束的語(yǔ)法和接口約束語(yǔ)法相同。這里有一點(diǎn)需要注意,如果同時(shí)指定了多種約束,那么類(lèi)類(lèi)型約束必須位于第一位(第一個(gè)出現(xiàn)),并且泛型約束中是不允許使用多個(gè)類(lèi)類(lèi)型約束的,這是因?yàn)槲覀兊拇a不可能從多個(gè)不想管的類(lèi)中派生出來(lái),同樣類(lèi)類(lèi)型約束也不能指定密封類(lèi)或者不是類(lèi)的類(lèi)型。
class、struct 約束
class 和 struct 約束是一個(gè)很容易出錯(cuò)并且也很容易讓新手程序員造成困惑的地方。首先很多新手程序員看到 class 約束會(huì)認(rèn)為是將類(lèi)型實(shí)參限制為類(lèi)類(lèi)型,其實(shí)不是這樣的。class 約束是類(lèi)型實(shí)參為引用類(lèi)型,因此這里使用接口、類(lèi)、委托以技術(shù)組類(lèi)型都符合這個(gè)條件。struct 約束和 class 約束正好相反,它是將類(lèi)型實(shí)參限制為值類(lèi)型,并且值類(lèi)型還不能是可空值類(lèi)型。因?yàn)榭煽罩殿?lèi)型是作為泛型 NUllable 來(lái)實(shí)現(xiàn)的,并且 NUllable 中的 T 使用的是 struct 約束。如果可以使用可控制類(lèi)型那么 NUllable 就有很大的可能被用成 NUllable
Tip:因?yàn)?class 約束要求引用類(lèi)型而 struct 約束要求值類(lèi)型,因此這兩種約束是不能同時(shí)出現(xiàn)的。
多約束
我們可以為任意類(lèi)型的參數(shù)指定任意數(shù)量的接口約束,所有的接口約束需要用逗號(hào)分割。如果存在多個(gè)不同類(lèi)型的約束,針對(duì)每種約束都需要寫(xiě)一個(gè) where 關(guān)鍵字,不同種類(lèi)約束之間不需要用任何符號(hào)分割。我們來(lái)看一下多約束的代碼段:
- public class Demo
- where TKey:IA,IB
- where TValue: ClassA
- {
- //more code
- }
構(gòu)造函數(shù)約束
有時(shí)我們需要在泛型類(lèi)中創(chuàng)建類(lèi)型實(shí)參的實(shí)例,這時(shí)我們可以規(guī)定傳入泛型類(lèi)的類(lèi)型實(shí)參必須具有構(gòu)造函數(shù),如果要實(shí)現(xiàn)這一點(diǎn)我們可以使用new() 來(lái)作為限制,這個(gè)約束叫做 構(gòu)造函數(shù)約束 。這里需要注意的是構(gòu)造函數(shù)約束必須位于所有約束的后面,并且它只能對(duì)默認(rèn)構(gòu)造函數(shù)進(jìn)行約束,而不能對(duì)有參構(gòu)造函數(shù)進(jìn)行約束。
Tip 1:關(guān)于約束繼承這個(gè)問(wèn)題,想必好多開(kāi)發(fā)人員都是一頭霧水。在這里我通過(guò)簡(jiǎn)單的幾句來(lái)說(shuō)一下約束繼承。首先無(wú)論是泛型類(lèi)型參數(shù)還是它們的約束都不會(huì)被 派生類(lèi) 繼承,這是因?yàn)榉盒皖?lèi)型參數(shù)和約束不是類(lèi)的成員。雖然不能被派生類(lèi)繼承,但是可以被從其派生的泛型類(lèi)所繼承。由于派生的泛型類(lèi)類(lèi)型參數(shù)是泛型基類(lèi)的類(lèi)型實(shí)參,所以類(lèi)型參數(shù)必須具有等同于或者強(qiáng)于泛型基類(lèi)的約束條件。
Tip 2:泛型方法同樣也可以使用約束,約束條件和泛型類(lèi)類(lèi)似。
六、總結(jié)
這篇文章我主要講解了泛型的一些知識(shí),不能說(shuō)很全面,但已經(jīng)覆蓋了百分之九十的內(nèi)容。泛型在開(kāi)發(fā)中可以說(shuō)是經(jīng)常用到,良好的使用泛型可以提高代碼復(fù)用率以及程序的運(yùn)行性能。
作者介紹
朱鋼,筆名喵叔,國(guó)內(nèi)知名技術(shù)社區(qū)博客認(rèn)證專(zhuān)家,2019年知名技術(shù)社區(qū)博客之星20強(qiáng),.NET高級(jí)開(kāi)發(fā)工程師,7年一線(xiàn)開(kāi)發(fā)經(jīng)驗(yàn),參與過(guò)電子政務(wù)系統(tǒng)和AI客服系統(tǒng)的開(kāi)發(fā),以及互聯(lián)網(wǎng)招聘網(wǎng)站的架構(gòu)設(shè)計(jì),目前就職于一家初創(chuàng)公司,從事企業(yè)級(jí)安全監(jiān)控系統(tǒng)的開(kāi)發(fā)。
【原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為.com】
本文標(biāo)題:一文搞定泛型,提高代碼復(fù)用率及程序的運(yùn)行性能
分享網(wǎng)址:http://www.fisionsoft.com.cn/article/cdeojch.html


咨詢(xún)
建站咨詢(xún)
