自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

30分鐘泛型教程

開(kāi)發(fā) 后端
泛型是程序設(shè)計(jì)語(yǔ)言的一種特性。允許程序員在強(qiáng)類(lèi)型程序設(shè)計(jì)語(yǔ)言中編寫(xiě)代碼時(shí)定義一些可變部分,那些部分在使用前必須作出指明。下面,各位網(wǎng)友們認(rèn)真看看30分鐘,完全掌握泛型的用法。

我們先來(lái)看一個(gè)最為常見(jiàn)的泛型類(lèi)型List<T>的定義

(真正的定義比這個(gè)要復(fù)雜的多,我這里刪掉了很多東西)

  1. [Serializable]  
  2. public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>  
  3. {  
  4.     public T this[int index] { get; set; }  
  5.     public void Add(T item);  
  6.     public void Clear();  
  7.     public bool Contains(T item);  
  8.     public int IndexOf(T item);  
  9.     public bool Remove(T item);  
  10.     public void Sort();  
  11.     public T[] ToArray();  

List后面緊跟著一個(gè)<T>表示它操作的是一個(gè)未指定的數(shù)據(jù)類(lèi)型(T代表著一個(gè)未指定的數(shù)據(jù)類(lèi)型)

可以把T看作一個(gè)變量名,T代表著一個(gè)類(lèi)型,在List<T>的源代碼中任何地方都能使用T。

T被用作方法的參數(shù)和返回值。

Add方法接收T類(lèi)型的參數(shù),ToArray方法返回一個(gè)T類(lèi)型的數(shù)組

注意:

泛型參數(shù)必須以T開(kāi)頭,要么就叫T,要么就叫TKey或者TValue;

這跟接口要以I開(kāi)頭是一樣的,這是約定。

下面來(lái)看一段使用泛型類(lèi)型的代碼

  1. var a = new List<int>();  
  2.             a.Add(1);  
  3.             a.Add(2);  
  4.             //這是錯(cuò)誤的,因?yàn)槟阋呀?jīng)指定了泛型類(lèi)型為int,就不能在這個(gè)容器中放入其他的值  
  5.             //這是編譯器錯(cuò)誤,更提升了排錯(cuò)效率,如果是運(yùn)行期錯(cuò)誤,不知道要多么煩人  
  6.             a.Add("3");  
  7.             var item = a[2]; 

請(qǐng)注意上面代碼里的注釋

二、泛型的作用(1):

作為程序員,寫(xiě)代碼時(shí)刻不忘代碼重用。

代碼重用可以分成很多類(lèi),其中算法重用就是非常重要的一類(lèi),假設(shè)你要為一組整型數(shù)據(jù)寫(xiě)一個(gè)排序算法,又要為一組浮點(diǎn)型數(shù)據(jù)寫(xiě)一個(gè)排序算法,如果沒(méi)有泛型類(lèi)型,你會(huì)怎么做呢?

你可能想到了方法的重載。

寫(xiě)兩個(gè)同名方法,一個(gè)方法接收整型數(shù)組,另一個(gè)方法接收浮點(diǎn)型的數(shù)組。

但有了泛型,你就完全不必這么做,只要設(shè)計(jì)一個(gè)方法就夠用了,你甚至可以用這個(gè)方法為一組字符串?dāng)?shù)據(jù)排序。

三、泛型的作用(2):

假設(shè)你是一個(gè)方法的設(shè)計(jì)者,這個(gè)方法需要有一個(gè)輸入?yún)?shù),但你并能確定這個(gè)輸入?yún)?shù)的類(lèi)型,那么你會(huì)怎么做呢?

有一部分人可能會(huì)馬上反駁:“不可能有這種時(shí)候!”

那么我會(huì)跟你說(shuō),編程是一門(mén)經(jīng)驗(yàn)型的工作,你的經(jīng)驗(yàn)還不夠,還沒(méi)有碰到過(guò)類(lèi)似的地方。

另一部分人可能考慮把這個(gè)參數(shù)的類(lèi)型設(shè)置成Object的,這確實(shí)是一種可行的方案,但會(huì)造成下面兩個(gè)問(wèn)題,如果我給這個(gè)方法傳遞整形的數(shù)據(jù)(值類(lèi)型的數(shù)據(jù)都一樣),就會(huì)產(chǎn)生額外的裝箱、拆箱操作,造成性能損耗。

如果你這個(gè)方法里的處理邏輯不適用于字符串的參數(shù),而使用者又傳了一個(gè)字符串進(jìn)來(lái),編譯器是不會(huì)報(bào)錯(cuò)的,只有在運(yùn)行期才會(huì)報(bào)錯(cuò)。

(如果質(zhì)管部門(mén)沒(méi)有測(cè)出這個(gè)運(yùn)行期BUG,那么不知道要造成多大的損失呢)

這就是我們常說(shuō)的:類(lèi)型不安全。

四、泛型的示例:

像List<T>和Dictionary<TKey,TValue>之類(lèi)的泛型類(lèi)型我們經(jīng)常用到,下面我介紹幾個(gè)不常用到的泛型類(lèi)型。

ObservableCollection<T>

當(dāng)這個(gè)集合發(fā)生改變后會(huì)有相應(yīng)的事件得到通知。

請(qǐng)看如下代碼:

  1. static void Main(string[] args)  
  2. {  
  3.     var a = new ObservableCollection<int>();  
  4.     a.CollectionChanged += a_CollectionChanged;  
  5. }  
  6.  
  7. static void a_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)  
  8. {  
  9.     //可以通過(guò)Action來(lái)判斷是什么操作觸發(fā)了事件  
  10.     //e.Action == NotifyCollectionChangedAction.Add  
  11.  
  12.     //可以根據(jù)以下兩個(gè)屬性來(lái)得到更改前和更改后的內(nèi)容  
  13.     //e.NewItems;  
  14.     //e.OldItems;  

使用這個(gè)集合需要引用如下兩個(gè)名稱(chēng)空間

  1. using System.Collections.ObjectModel;  
  2. using System.Collections.Specialized; 

BlockingCollection<int>是線程安全的集合

來(lái)看看下面這段代碼

  1. var bcollec = new BlockingCollection<int>(2);  
  2. //試圖添加1-50  
  3. Task.Run(() =>  
  4. {  
  5.     //并行循環(huán)  
  6.     Parallel.For(1, 51, i =>  
  7.     {  
  8.         bcollec.Add(i);  
  9.         Console.WriteLine("加入:" + i);  
  10.     });  
  11. });  
  12.  
  13. Thread.Sleep(1000);  
  14. Console.WriteLine("調(diào)用一次Take");  
  15. bcollec.Take();  
  16.  
  17. //等待無(wú)限長(zhǎng)時(shí)間  
  18. Thread.Sleep(Timeout.Infinite); 

輸出結(jié)果為:

  1. 加入:1  
  2. 加入:37  
  3. 調(diào)用一次Take  
  4. 加入:13 

BlockingCollection<int>還可以設(shè)置CompleteAdding和IsCompleted屬性來(lái)拒絕加入新元素。

.NET類(lèi)庫(kù)還提供了很多的泛型類(lèi)型,在這里就不一一例舉了。

#p#

五、泛型的繼承:

在.net中一切都繼承字Object,泛型也不例外,泛型類(lèi)型可以繼承自其他類(lèi)型。

來(lái)看一下如下代碼

  1. public class MyType  
  2. {  
  3.     public virtual string getOneStr()  
  4.     {  
  5.         return "base object Str";  
  6.     }  
  7. }  
  8. public class MyOtherType<T> : MyType  
  9. {  
  10.     public override string getOneStr()  
  11.     {  
  12.         return typeof(T).ToString();  
  13.     }  
  14. }  
  15. class Program  
  16. {  
  17.     static void Main(string[] args)  
  18.     {  
  19.         MyType target = new MyOtherType<int>();  
  20.         Console.WriteLine(target.getOneStr());  
  21.         Console.ReadKey();  
  22.     }  

泛型類(lèi)型MyOtherType<T>成功的重寫(xiě)了非泛型類(lèi)型MyType的方法。

如果我試圖按如下方式從MyOtherType<T>類(lèi)型派生子類(lèi)型就會(huì)導(dǎo)致編譯器錯(cuò)誤。

  1. //編譯期錯(cuò)誤  
  2. public class MyThirdType : MyOtherType<T>  
  3. {  
  4. }  
  5.  

但是如果寫(xiě)成這種方式,就不會(huì)出錯(cuò)

  1. public class MyThirdType : MyOtherType<int>  
  2.     {  
  3.         public override string getOneStr()  
  4.         {  
  5.             return "MyThirdType";  
  6.         }  
  7.     } 

注意:

如果按照如上寫(xiě)法,會(huì)造成類(lèi)型不統(tǒng)一的問(wèn)題,

如果一個(gè)方法接收MyThirdType類(lèi)型的參數(shù),

那么不能將一個(gè)MyOtherType<int>的實(shí)例傳遞給這個(gè)方法,   

然而一個(gè)方法如果接收MyOtherType<int>類(lèi)型的參數(shù),

卻可以把MyThirdType類(lèi)型的實(shí)例傳遞給這個(gè)方法,

這是CLR內(nèi)部實(shí)現(xiàn)機(jī)制造成的,

這看起來(lái)確實(shí)很怪異!

寫(xiě)成如下方式也不會(huì)出錯(cuò):

  1. public class MyThirdType<T> : MyOtherType<T>  
  2.     {  
  3.         public override string getOneStr()  
  4.         {  
  5.             return typeof(T).ToString() + " from MyThirdType";  
  6.         }  
  7.     } 

此中訣竅,只可意會(huì),不可言傳。

六、泛型接口

.NET類(lèi)庫(kù)里有很多泛型的接口,比如:IEnumerator<T>、IList<T>等,這里不對(duì)這些接口做詳細(xì)描述了,值說(shuō)說(shuō)為什么要有泛型接口。

其實(shí)泛型接口出現(xiàn)的原因和泛型出現(xiàn)的原因類(lèi)似,拿IComparable這個(gè)接口來(lái)說(shuō),此接口只描述了一個(gè)方法:

  1. int CompareTo(object obj); 

大家看到,如果是值類(lèi)型的參數(shù),勢(shì)必會(huì)導(dǎo)致裝箱和拆箱操作。

同時(shí),也不是強(qiáng)類(lèi)型的,不能在編譯期確定參數(shù)的類(lèi)型,有了IComparable<T>就解決掉這個(gè)問(wèn)題了:

  1. int CompareTo(T other); 

七、泛型委托

委托描述方法,泛型委托的由來(lái)和泛型接口類(lèi)似。

定義一個(gè)泛型委托也比較簡(jiǎn)單:

  1. public delegate void MyAction<T>(T obj); 

這個(gè)委托描述一類(lèi)方法,這類(lèi)方法接收T類(lèi)型的參數(shù),沒(méi)有返回值。

來(lái)看看使用這個(gè)委托的方法:

  1. public delegate void MyAction<T>(T obj);  
  2. static void Main(string[] args)  
  3. {  
  4.     var method = new MyAction<int>(printInt);  
  5.     method(3);  
  6.     Console.ReadKey();  
  7. }  
  8. static void printInt(int i)  
  9. {  
  10.     Console.WriteLine(i);  

由于定義委托比較繁瑣,.NET類(lèi)庫(kù)在System名稱(chēng)空間,下定義了三種比較常用的泛型委托。

Predicate<T>委托:

  1. public delegate bool Predicate<T>(T obj); 

這個(gè)委托描述的方法為接收一個(gè)T類(lèi)型的參數(shù),返回一個(gè)BOOL類(lèi)型的值,一般用于比較方法。

Action<T>委托

  1. public delegate void Action<T>(T obj); 
  1. public delegate void Action<T1, T2>(T1 arg1, T2 arg2); 

這個(gè)委托描述的方法,接收一個(gè)或多個(gè)T類(lèi)型的參數(shù)(最多16個(gè),我這里只寫(xiě)了兩種類(lèi)型的定義方式),沒(méi)有返回值。

Func<T>委托

  1. public delegate TResult Func<TResult>(); 
  1. public delegate TResult Func<T, TResult>(T arg); 

這個(gè)委托描述的方法,接收零個(gè)或多個(gè)T類(lèi)型的參數(shù)(最多16個(gè),我這里只寫(xiě)了兩種類(lèi)型的定義方式),與Action委托不同的是,它有一個(gè)返回值,返回值的類(lèi)型為T(mén)Result類(lèi)型的。

關(guān)于委托的描述,您還可以看我這篇文章。

#p#

八、泛型方法

泛型類(lèi)型中的T可以用在這個(gè)類(lèi)型的任何地方,然而有些時(shí)候,我們不希望在使用類(lèi)型的時(shí)候就指定T的類(lèi)型,我們希望在使用這個(gè)類(lèi)型的方法時(shí),再指定T的類(lèi)型。

來(lái)看看如下代碼:

  1. public class MyClass  
  2.     {  
  3.         public TParam CompareTo<TParam>(TParam other)  
  4.         {  
  5.             Console.WriteLine(other.ToString());  
  6.             return other;  
  7.         }  
  8.     } 

上面的代碼中MyClass并不是一個(gè)泛型類(lèi)型,但這個(gè)類(lèi)型中的CompareTo<TParam>()卻是一個(gè)泛型方法,TParam可以用在這個(gè)方法中的任何地方。

使用泛型方法一般用如下代碼就可以了:

  1. obj.CompareTo<int>(4);  
  2. obj.CompareTo<string>("ddd"); 

然而,你可以寫(xiě)的更簡(jiǎn)單一些,寫(xiě)成如下的方式:

  1. obj.CompareTo(2);  
  2. obj.CompareTo("123"); 

有人會(huì)問(wèn):“這不可能,沒(méi)有指定CompareTo方法的TParam類(lèi)型,肯定會(huì)編譯出錯(cuò)的”

我告訴你:不會(huì)的,編譯器可以幫你完成類(lèi)型推斷的工作。

注意:

如果你為一個(gè)方法指定了兩個(gè)泛型參數(shù),而且這兩個(gè)參數(shù)的類(lèi)型都是T,那么如果你想使用類(lèi)型推斷,你必須傳遞兩個(gè)相同類(lèi)型的參數(shù)給這個(gè)方法,不能一個(gè)參數(shù)用string類(lèi)型,另一個(gè)用object類(lèi)型,這會(huì)導(dǎo)致編譯錯(cuò)誤。

九、泛型約束

我們?cè)O(shè)計(jì)了一個(gè)泛型類(lèi)型,很多時(shí)候,我們不希望使用者傳入任意類(lèi)型的參數(shù),也就是說(shuō),我們希望“約束”一下T的類(lèi)型。

來(lái)看看如下代碼:

  1. public class MyClass<T> where T : IComparable<T>  
  2.     {  
  3.         public int CompareTo(T other)  
  4.         {  
  5.             return 0;  
  6.         }  
  7.     } 

上面的代碼要求T類(lèi)型必須實(shí)現(xiàn)了IComparable<T>接口。

如你所見(jiàn):泛型的約束通過(guò)關(guān)鍵字where來(lái)實(shí)現(xiàn)。

泛型方法當(dāng)然也可以通過(guò)類(lèi)似的方式對(duì)泛型參數(shù)進(jìn)行約束。

請(qǐng)看如下代碼:

  1. public class MyClass  
  2. {  
  3.     public TParam CompareTo<TParam>(TParam other) where TParam:class 
  4.     {  
  5.         Console.WriteLine(other.ToString());  
  6.         return other;  
  7.     }  

上面代碼中用了class關(guān)鍵字約束泛型參數(shù)TParam;具體稍后解釋。

注意1:

如果我有一個(gè)類(lèi)型也定義為MyClass<T>但沒(méi)有做約束,那么這個(gè)時(shí)候,做過(guò)約束的MyClass<T>將與沒(méi)做約束的MyClass<T>沖突,編譯無(wú)法通過(guò)。

注意2:

當(dāng)你重寫(xiě)一個(gè)泛型方法時(shí),如果這個(gè)方法指定了約束,在重寫(xiě)這個(gè)方法時(shí),不能再指定約束了。

注意3:

雖然我上面的例子寫(xiě)的是接口約束,但你完全可以寫(xiě)一個(gè)類(lèi)型,比如說(shuō)BaseClass。而且,只要是繼承自BaseClass的類(lèi)型都可以當(dāng)作T類(lèi)型使用,你不要試圖約束T為Object類(lèi)型,編譯不會(huì)通過(guò)的。(傻子才這么干)

注意4:

有兩個(gè)特殊的約束:class和struct。

where T : class 約束T類(lèi)型必須為引用類(lèi)型

where T : struct 約束T類(lèi)型必須為值類(lèi)型

注意5:

如果你沒(méi)有對(duì)T進(jìn)行class約束,

那么你不能寫(xiě)這樣的代碼:T obj = null; 這無(wú)法通過(guò)編譯,因?yàn)門(mén)有可能是值類(lèi)型的。

如果你沒(méi)有對(duì)T進(jìn)行struct約束,也沒(méi)有對(duì)T進(jìn)行new約束。

那么你不能寫(xiě)這樣的代碼:T obj = new T(); 這無(wú)法通過(guò)編譯,因?yàn)橹殿?lèi)型肯定有無(wú)參數(shù)構(gòu)造器,而引用類(lèi)型就不一定了。

如果你對(duì)T進(jìn)行了new約束:where T : new(); 那么new T()就是正確的,因?yàn)閚ew約束要求T類(lèi)型有一個(gè)公共無(wú)參構(gòu)造器。

注意6:

就算沒(méi)有對(duì)T進(jìn)行任何約束,也有一個(gè)辦法來(lái)處理值類(lèi)型和引用類(lèi)型的問(wèn)題。

T temp = default(T);

如果T為引用類(lèi)型,那么temp就是null;如果T為值類(lèi)型,那么temp就是0;

注意7:

試圖對(duì)T類(lèi)型的變量進(jìn)行強(qiáng)制轉(zhuǎn)化,一般情況下會(huì)報(bào)編譯期錯(cuò)誤。

但你可以先把T轉(zhuǎn)化成object再把object轉(zhuǎn)化成你要的類(lèi)型(一般不推薦這么做,你應(yīng)該考慮把T轉(zhuǎn)化成一個(gè)約束兼容的類(lèi)型)。

你也可以考慮用as操作符進(jìn)行類(lèi)型轉(zhuǎn)化,這一般不會(huì)報(bào)錯(cuò),但只能轉(zhuǎn)化成引用類(lèi)型。

關(guān)于泛型約束的內(nèi)容,我在這篇文章里也有提到。

十、逆變和協(xié)變

一般情況下,我們使用泛型時(shí),由T標(biāo)記的泛型類(lèi)型是不能更改的。

也就是說(shuō),如下兩種寫(xiě)法都是錯(cuò)誤的:

  1. var a = new List<object>();  
  2. List<string> b = a;  
  3. var c = new List<string>();  
  4. List<object> d = c; 

注意:這里沒(méi)有寫(xiě)強(qiáng)制轉(zhuǎn)換,即使寫(xiě)了強(qiáng)制轉(zhuǎn)換也是錯(cuò)誤的,編譯就無(wú)法通過(guò),然而泛型提供了逆變和協(xié)變的特性,有了這兩種特性,這種轉(zhuǎn)換就成為了可能。

逆變:

泛型類(lèi)型T可以從基類(lèi)型更改為該類(lèi)的派生類(lèi)型,用in關(guān)鍵字標(biāo)記逆變形式的類(lèi)型參數(shù),而且這個(gè)參數(shù)一般作輸入?yún)?shù)。

協(xié)變:

泛型類(lèi)型T可以從派生類(lèi)型更改為它的基類(lèi)型,用out關(guān)鍵字來(lái)標(biāo)記協(xié)變形式的類(lèi)型參數(shù),而且這個(gè)參數(shù)一般作為返回值。

如果我們定義了一個(gè)這樣的委托:

  1. public delegate TResult MyAction<in T,out TResult>(T obj); 

那么,就可以讓如下代碼通過(guò)編譯(不用強(qiáng)制轉(zhuǎn)換)

  1. var a = new MyAction<object, ArgumentException>(o => new ArgumentException(o.ToString()));  
  2. MyAction<string, Exception> b = a; 

這就是逆變和協(xié)變的威力。

原文鏈接:http://www.cnblogs.com/liulun/archive/2013/05/02/3033599.html

責(zé)任編輯:林師授 來(lái)源: 博客園
相關(guān)推薦

2024-06-19 09:58:29

2021-07-01 06:47:30

Java泛型泛型擦除

2017-01-10 09:07:53

tcpdumpGET請(qǐng)求

2020-09-29 06:37:30

Java泛型

2020-05-22 10:20:27

Shiro架構(gòu)字符串

2017-07-18 11:10:45

2014-04-22 09:42:12

Bash腳本教程

2013-12-11 10:00:14

C++新特性C

2017-06-07 18:40:33

PromiseJavascript前端

2024-08-27 13:43:38

Spring系統(tǒng)業(yè)務(wù)

2022-09-30 15:46:26

Babel編譯器插件

2016-04-06 11:14:48

iOS相機(jī)自定義

2016-08-03 16:01:47

GitLinux開(kāi)源

2019-12-12 10:25:33

Java泛型編程語(yǔ)言

2011-07-11 09:58:52

2018-02-02 10:24:37

Nginx入門(mén)指南

2018-04-24 14:52:48

LinuxBash腳本

2021-10-28 05:34:46

云計(jì)算云游戲Stadia

2022-03-31 06:27:59

網(wǎng)絡(luò)故障網(wǎng)絡(luò)管理平臺(tái)網(wǎng)絡(luò)中斷

2012-06-28 10:26:51

Silverlight
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)