C#泛型詳解及關(guān)鍵字作用
這篇文章主要來講講c#中的泛型,因?yàn)榉盒驮赾#中有很重要的位置,對(duì)于寫出高可讀性,高性能的代碼有著關(guān)鍵的作用。
一、什么是泛型?
泛型是 2.0 版 C# 語言和公共語言運(yùn)行庫 (CLR) 中的一個(gè)非常重要的新功能。
我們?cè)诰幊坛绦驎r(shí),經(jīng)常會(huì)遇到功能非常相似的模塊,只是它們處理的數(shù)據(jù)不一樣。但我們沒有辦法,只能分別寫多個(gè)方法來處理不同的數(shù)據(jù)類型。這個(gè)時(shí)候,那么問題來了,有沒有一種辦法,用同一個(gè)方法來處理傳入不同種類型參數(shù)的辦法呢?泛型的出現(xiàn)就是專門來解決這個(gè)問題的,可以看出,微軟還是很貼心的。
二、為什么要使用泛型?
接下來我們來看一段代碼。
public class GenericClass { public void ShowInt(int n) { Console.WriteLine("ShowInt print {0},ShowInt Parament Type Is {1}",n,n.GetType()); } public void ShowDateTime(DateTime dt) { Console.WriteLine("ShowDateTime print {0},ShowDateTime Parament Type Is {1}", dt, dt.GetType()); } public void ShowPeople(People people) { Console.WriteLine("ShowPeople print {0},ShowPeople Parament Type Is {1}", people, people.GetType()); } }
static void Main(string[] args) { GenericClass generice = new GenericClass(); generice.ShowInt(11); generice.ShowDateTime(DateTime.Now); generice.ShowPeople(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); }
顯示結(jié)果:
我們可以看出這三個(gè)方法,除了傳入的參數(shù)不同外,其里面實(shí)現(xiàn)的功能都是一樣的。在1.1版的時(shí)候,還沒有泛型這個(gè)概念,那么怎么辦呢。就有人想到了OOP三大特性之一的繼承,我們知道,C#語言中,object是所有類型的基類,將上面的代碼進(jìn)行以下優(yōu)化:
public class GenericClass { public void ShowObj(object obj) { Console.WriteLine("ShowObj print {0},ShowObj Parament Type Is {1}", obj, obj.GetType()); } } static void Main(string[] args) { Console.WriteLine("*****************object調(diào)用*********************"); generice.ShowObj(11); generice.ShowObj(DateTime.Now); generice.ShowObj(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); }
顯示結(jié)果:
我們可以看出,目地是達(dá)到了。解決了代碼的可讀性,但是這樣又有個(gè)不好的地方了,我們這樣做實(shí)際上是一個(gè)裝箱拆箱操作,會(huì)損耗性能。
終于,微軟在2.0的時(shí)候發(fā)布了泛型。接下來我們用泛型方法來實(shí)現(xiàn)該功能。
三、泛型類型參數(shù)
在使用泛型方法之前,我們先來了解下有關(guān)于泛型的一些知識(shí)。
在泛型類型或方法定義中,類型參數(shù)是在其實(shí)例化泛型類型的一個(gè)變量時(shí),客戶端指定的特定類型的占位符。 泛型類(GenericList<T>
)無法按原樣使用,因?yàn)樗皇钦嬲念愋?;它更像是類型的藍(lán)圖。 若要使用GenericList<T>
,客戶端代碼必須通過指定尖括號(hào)內(nèi)的類型參數(shù)來聲明并實(shí)例化構(gòu)造類型。 此特定類的類型參數(shù)可以是編譯器可識(shí)別的任何類型。 可創(chuàng)建任意數(shù)量的構(gòu)造類型實(shí)例,其中每個(gè)使用不同的類型參數(shù),如下所示:
GenericList<float> list1 = new GenericList<float>(); GenericList<ExampleClass> list2 = new GenericList<ExampleClass>(); GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();
在GenericList<T>
的每個(gè)實(shí)例中,類中出現(xiàn)的每個(gè)T
在運(yùn)行時(shí)均會(huì)被替換為類型參數(shù)。 通過這種替換,我們已通過使用單個(gè)類定義創(chuàng)建了三個(gè)單獨(dú)的類型安全的有效對(duì)象。
三、泛型約束
定義泛型類時(shí),可以對(duì)客戶端代碼能夠在實(shí)例化類時(shí)用于類型參數(shù)的幾種類型施加限制。 如果客戶端代碼嘗試使用約束所不允許的類型來實(shí)例化類,則會(huì)產(chǎn)生編譯時(shí)錯(cuò)誤。 這些限制稱為約束。 通過使用where
上下文關(guān)鍵字指定約束。 下表列出了六種類型的約束:
where T:結(jié)構(gòu)(類型參數(shù)必須是值類型??梢灾付ǔ?Nullable 以外的任何值類型。)
class MyClass<U> where U : struct///約束U參數(shù)必須為“值 類型” { } public void MyMetod<T>(T t) where T : struct { }
where T:類(類型參數(shù)必須是引用類型;這一點(diǎn)也適用于任何類、接口、委托或數(shù)組類型。)
class MyClass<U> where U : class///約束U參數(shù)必須為“引用類型” { } public void MyMetod<T>(T t) where T : class { }
where T:new()(類型參數(shù)必須具有無參數(shù)的公共構(gòu)造函數(shù)。當(dāng)與其他約束一起使用時(shí),new() 約束必須最后指定。)
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new() { // ... }
where T:<基類名>(類型參數(shù)必須是指定的基類或派生自指定的基類。)
public class Employee{} public class GenericList<T> where T : Employee
where T:<接口名稱>(類型參數(shù)必須是指定的接口或?qū)崿F(xiàn)指定的接口??梢灾付ǘ鄠€(gè)接口約束。約束接口也可以是泛型的。)
/// <summary> /// 接口 /// </summary> interface IMyInterface { } /// <summary> /// 定義的一個(gè)字典類型 /// </summary> /// <typeparam name="TKey"></typeparam> /// <typeparam name="TVal"></typeparam> class Dictionary<TKey, TVal> where TKey : IComparable, IEnumerable where TVal : IMyInterface { public void Add(TKey key, TVal val) { } }
where T:U(為 T 提供的類型參數(shù)必須是為 U 提供的參數(shù)或派生自為 U 提供的參數(shù)。也就是說T和U的參數(shù)必須一樣)
class List<T> { void Add<U>(List<U> items) where U : T {/*...*/} }
以上就是對(duì)六種泛型的簡(jiǎn)單示例,當(dāng)然泛型約束不僅僅適用于類,接口,對(duì)于泛型方法,泛型委托都同樣適用。
三、泛型方法
public class GenericClass { public void ShowT<T>(T t) { Console.WriteLine("ShowT print {0},ShowT Parament Type Is {1}", t, t.GetType()); } } static void Main(string[] args) { Console.WriteLine("*****************泛型方法調(diào)用*********************"); generice.ShowT<int>(11); generice.ShowT<DateTime>(DateTime.Now); generice.ShowT<People>(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); }
顯示結(jié)果:
也是一樣的,現(xiàn)在終于實(shí)現(xiàn)了我們想要達(dá)到的效果了。我們可以看出,無論是什么方式調(diào)用,最后我們獲取出來的類型都是原始類型。我們知道,用object獲取是利用了繼承這一特性,當(dāng)編譯器編譯的時(shí)候,我們傳入的參數(shù)會(huì)進(jìn)行裝箱操作,當(dāng)我們獲取的時(shí)候又要進(jìn)行拆箱操作,這個(gè)方法會(huì)損耗性能 。那么泛型方法實(shí)現(xiàn)的原理又是怎樣的呢?首先,我們要知道,泛型是一個(gè)語法糖,在我們調(diào)用泛型方法,編譯器進(jìn)行編譯時(shí),才會(huì)確定傳入的參數(shù)的類型,從而生成副本方法。這個(gè)副本方法與原始方法一法,所以不會(huì)有裝箱拆箱操作,也就沒有損耗性能這回事了。
四、泛型類
泛型類封裝不特定于特定數(shù)據(jù)類型的操作。
通常,創(chuàng)建泛型類是從現(xiàn)有具體類開始,然后每次逐個(gè)將類型更改為類型參數(shù),直到泛化和可用性達(dá)到最佳平衡。
創(chuàng)建自己的泛型類時(shí),需要考慮以下重要注意事項(xiàng):
- 要將哪些類型泛化為類型參數(shù)。
通常,可參數(shù)化的類型越多,代碼就越靈活、其可重用性就越高。 但過度泛化會(huì)造成其他開發(fā)人員難以閱讀或理解代碼。
- 要將何種約束(如有)應(yīng)用到類型參數(shù)
其中一個(gè)有用的規(guī)則是,應(yīng)用最大程度的約束,同時(shí)仍可處理必須處理的類型。 例如,如果知道泛型類僅用于引用類型,則請(qǐng)應(yīng)用類約束。 這可防止將類意外用于值類型,并 使你可在 T 上使用 as 運(yùn)算符和檢查 null 值?! ?/p>
- 是否將泛型行為分解為基類和子類。
因?yàn)榉盒皖惪捎米骰?,所以非泛型類的相同設(shè)計(jì)注意事項(xiàng)在此也適用。 請(qǐng)參閱本主題后文有關(guān)從泛型基類繼承的規(guī)則。
- 實(shí)現(xiàn)一個(gè)泛型接口還是多個(gè)泛型接口。
class BaseNode { } class BaseNodeGeneric<T> { } // concrete type class NodeConcrete<T> : BaseNode { } //closed constructed type class NodeClosed<T> : BaseNodeGeneric<int> { } //open constructed type class NodeOpen<T> : BaseNodeGeneric<T> { }
五、泛型接口
定義一個(gè)泛型接口:
interface IMyGenericInterface<T> { }
一個(gè)接口可定義多個(gè)類型參數(shù),如下所示:
interface IMyGenericInterface<TKey,TValue> { }
具體類可實(shí)現(xiàn)封閉式構(gòu)造接口,如下所示:
interface IBaseInterface<T> { } class SampleClass : IBaseInterface<string> { }//如果T有約束,那么string類型必須得滿足T的約束
六、泛型委托
委托可以定義它自己的類型參數(shù)。 引用泛型委托的代碼可以指定類型參數(shù)以創(chuàng)建封閉式構(gòu)造類型,就像實(shí)例化泛型類或調(diào)用泛型方法一樣,如以下示例中所示:
class Program { static void Main(string[] args) { Del<int> m1 = new Del<int>(Notify); m1.Invoke(1111); Del<string> m2 = new Del<string>(Notify); m2.Invoke("字符串"); Console.ReadKey(); } public delegate void Del<T>(T item); public static void Notify(int i) { Console.WriteLine("{0} type is {1}", i,i.GetType()); } public static void Notify(string str) { Console.WriteLine("{0} type is {1}", str, str.GetType()); } }
運(yùn)行結(jié)果:
七、泛型代碼中的默認(rèn)關(guān)鍵字:Default
在泛型類和泛型方法中產(chǎn)生的一個(gè)問題是,在預(yù)先未知以下情況時(shí),如何將默認(rèn)值分配給參數(shù)化類型 T:
- T 是引用類型還是值類型。
- 如果 T 為值類型,則它是數(shù)值還是結(jié)構(gòu)。
給定參數(shù)化類型 T 的一個(gè)變量 t,只有當(dāng) T 為引用類型時(shí),語句 t = null 才有效;只有當(dāng) T 為數(shù)值類型而不是結(jié)構(gòu)時(shí),語句 t = 0 才能正常使用。解決方案是使用default關(guān)鍵字,此關(guān)鍵字對(duì)于引用類型會(huì)返回空,對(duì)于數(shù)值類型會(huì)返回零。對(duì)于結(jié)構(gòu),此關(guān)鍵字將返回初始化為零或空的每個(gè)結(jié)構(gòu)成員,具體取決于這些結(jié)構(gòu)是值類型還是引用類型。
namespace MyGeneric { class Program { static void Main(string[] args) { object obj1=GenericToDefault<string>(); object obj2 = GenericToDefault<int>(); object obj3 = GenericToDefault<StructDemo>(); Console.ReadKey(); } public static T GenericToDefault<T>() { return default(T); } } public struct StructDemo { public int Id { get; set; } public string Name { get; set; } } }
運(yùn)行結(jié)果:
到此這篇關(guān)于C#泛型詳解的文章就介紹到這了,更多相關(guān)C#泛型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C# lambda表達(dá)式原理定義及實(shí)例詳解
這篇文章主要介紹了C# lambda表達(dá)式原理定義及實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07unity實(shí)現(xiàn)方向盤轉(zhuǎn)動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了unity實(shí)現(xiàn)方向盤轉(zhuǎn)動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09讀寫XML文件的內(nèi)容并將其顯示在ListView控件上的方法
下面小編就為大家?guī)硪黄x寫XML文件的內(nèi)容并將其顯示在ListView控件上的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02微信公眾號(hào)被動(dòng)消息回復(fù)原理解析
這篇文章主要介紹了公眾號(hào)被動(dòng)消息回復(fù)原理解析,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-06-06