接泛型四 20.6.5語(yǔ)法歧義 在§20.9.3和§20.9.4中簡(jiǎn)單名字(simple-name)和成員訪問(wèn)(member-access)對(duì)于表達(dá)式來(lái)說(shuō)容易引起語(yǔ)法歧義。例如,語(yǔ)句 F(G<A,B>(7));
可以被解釋為對(duì)帶有兩個(gè)參數(shù)G<A和B>(7)的F的調(diào)用[1]。同樣,它還能被解釋為對(duì)帶有一個(gè)參數(shù)的F的調(diào)用,這是一個(gè)對(duì)帶有兩個(gè)類型實(shí)參和一個(gè)正式參數(shù)的泛型方法G的調(diào)用。 如果表達(dá)式可以被解析為兩種不同的有效方法,那么在“>”能被解析作為運(yùn)算符的所有或一部分時(shí),或者作為一個(gè)類型實(shí)參列表,那么緊隨“>”之后的標(biāo)記將會(huì)被檢查。如果它是如下之一: { } ] > : ; , . ? 那么“>”被解析作為類型實(shí)參列表。否則“>”被解析作為一個(gè)運(yùn)算符。 20.6.6對(duì)委托使用泛型方法 委托的實(shí)例可通過(guò)引用一個(gè)泛型方法的聲明而創(chuàng)建。委托表達(dá)式確切的編譯時(shí)處理,包括引用泛型方法的委托創(chuàng)建表達(dá)式,這在§20.9.6中進(jìn)行了描述。 當(dāng)通過(guò)委托調(diào)用一個(gè)泛型方法時(shí),所使用的類型實(shí)參將在委托實(shí)例化時(shí)被確定。類型實(shí)參可以通過(guò)類型實(shí)參列表顯式給定,或者通過(guò)類型推斷(§20.6.4)而確定。如果采用類型推斷,委托的參數(shù)類型將被用作推斷處理過(guò)程的實(shí)參類型。委托的返回類型不用于推斷。下面的例子展示了為一個(gè)委托實(shí)例化表達(dá)式提供類型實(shí)參的方法。 delegate int D(string s , int i) delegate int E(); class X { public static T F<T>(string s ,T t){…} public static T G<T>(){…} static void Main() { D d1 = new D(F<int>); //ok,類型實(shí)參被顯式給定 D d2 = new D(F); //ok,int作為類型實(shí)參而被推斷 E e1 = new E(G<int>); //ok,類型實(shí)參被顯式給定 E e2 = new E(G); //錯(cuò)誤,不能從返回類型推斷 } } 在先前的例子中,非泛型委托類型使用泛型方法實(shí)例化。你也可以使用泛型方法創(chuàng)建一個(gè)構(gòu)造委托類型的實(shí)例。在所有情形下,當(dāng)委托實(shí)例被創(chuàng)建時(shí),類型實(shí)參被給定或可以被推斷,但委托被調(diào)用時(shí),可以不用提供類型實(shí)參列表(§15.3)。
20.6.7非泛型屬性、事件、索引器或運(yùn)算符 屬性、事件、索引器和運(yùn)算符他們自身可以沒(méi)有類型實(shí)參(盡管他們可以出現(xiàn)在泛型類中,并且可從一個(gè)封閉類中使用類型實(shí)參)。如果需要一個(gè)類似屬性泛型的構(gòu)件,取而代之的是你必須使用一個(gè)泛型方法。 20.7約束 泛型類型和方法聲明可以可選的指定類型參數(shù)約束,這通過(guò)在聲明中包含類型參數(shù)約束語(yǔ)句就可以做到。 type-parameter-constraints-clauses(類型參數(shù)約束語(yǔ)句:) type-parameter-constraints-clause(類型參數(shù)約束語(yǔ)句) type-parameter-constraints-clauses type-parameter-constraints-clause(類型參數(shù)約束語(yǔ)句 類型參數(shù)約束語(yǔ)句) type-parameter-constraints-clause:(類型參數(shù)約束語(yǔ)句:) where type-parameter : type-parameter-constraints(where 類型參數(shù):類型參數(shù)約束) type-parameter-constraints:(類型參數(shù)約束:) class-constraint(類約束) interface-constraints(接口約束) constructor-constraint(構(gòu)造函數(shù)約束) class-constraint , interface-constraints(類約束,接口約束) class-constraint , constructor-constraint(類約束,構(gòu)造函數(shù)約束) interface-constraints , constructor-constraint(接口約束,構(gòu)造函數(shù)約束) class-constraint , interface-constraints , constructor-constraint(類約束,接口約束,構(gòu)造函數(shù)約束) class-constraint:(類約束:) class-type(類類型) interface-constraint:(接口約束:) interface-constraint(接口約束) interface-constraints , interface-constraints(接口約束,接口約束) interface-constraints:(接口約束:) interface-type(接口類型) constructor-constraint:(構(gòu)造函數(shù)約束:) new ( ) 每個(gè)類型參數(shù)約束語(yǔ)句由標(biāo)志where 緊接著類型參數(shù)的名字,緊接著冒號(hào)和類型參數(shù)的約束列表。對(duì)每個(gè)類型參數(shù)只能有一個(gè)where 語(yǔ)句,但where語(yǔ)句可以以任何順序列出。與屬性訪問(wèn)器中的get和set標(biāo)志相似,where 語(yǔ)句不是關(guān)鍵字。
在where語(yǔ)句中給定的約束列表可以以這個(gè)順序包含下列組件:一個(gè)單一的類約束、一個(gè)或多個(gè)接口約和構(gòu)造函數(shù)約束new ()。 如果約束是一個(gè)類類型或者接口類型,這個(gè)類型指定類型參數(shù)必須支持的每個(gè)類型實(shí)參的最小“基類型”。無(wú)論什么時(shí)候使用一個(gè)構(gòu)造類型或者泛型方法,在編譯時(shí)對(duì)于類型實(shí)參的約束建會(huì)被檢查。所提供的類型實(shí)參必須派生于或者實(shí)現(xiàn)那個(gè)類型參數(shù)個(gè)定的所有約束。 被指定作為類約束的類型必須遵循下面的規(guī)則。 該類型必須是一個(gè)類類型。 該類型必須是密封的(sealed)。 該類型不能是如下的類型:System.Array,System.Delegate,System.Enum,或者System.ValueType類型。 該類型不能是object。由于所有類型派生于object,如果容許的話這種約束將不會(huì)有什么作用。 至多,對(duì)于給定類型參數(shù)的約束可以是一個(gè)類類型。 作為接口約束而被指定的類型必須滿足如下的規(guī)則。 該類型必須是一個(gè)接口類型。 在一個(gè)給定的where語(yǔ)句中相同的類型不能被指定多次。 在很多情況下,約束可以包含任何關(guān)聯(lián)類型的類型參數(shù)或者方法聲明作為構(gòu)造類型的一部分,并且可以包括被聲明的類型,但約束不能是一個(gè)單一的類型參數(shù)。 被指定作為類型參數(shù)約束的任何類或者接口類型,作為泛型類型或者被聲明的方法,必須至少是可訪問(wèn)的(§10.5.4)。 如果一個(gè)類型參數(shù)的where 語(yǔ)句包括new()形式的構(gòu)造函數(shù)約束,則使用new 運(yùn)算符創(chuàng)建該類型(§20.8.2)的實(shí)例是可能的。用于帶有一個(gè)構(gòu)造函數(shù)約束的類型參數(shù)的任何類型實(shí)參必須有一個(gè)無(wú)參的構(gòu)造函數(shù)(詳細(xì)情形參看§20.7)。 下面是可能約束的例子 interface IPrintable { void Print(); }
interface IComparable<T> { int CompareTo(T value); } interface IKeyProvider<T> { T GetKey(); } class Printer<T> where T:IPrintable{…} class SortedList<T> where T: IComparable<T>{…} class Dictionary<K,V> where K:IComparable<K> where: V: IPrintable,IKeyProvider<K>,new() { … } 下面的例子是一個(gè)錯(cuò)誤,因?yàn)樗噲D直接使用一個(gè)類型參數(shù)作為約束。 class Extend<T , U> where U:T{…}//錯(cuò)誤 約束的類型參數(shù)類型的值可以被用于訪問(wèn)約束暗示的實(shí)例成員。在例子 interface IPrintable { void Print(); } class Printer<T> where T:IPrintable { void PrintOne(T x) { x.Pint(); } } IPrintable的方法可以在x上被直接調(diào)用,因?yàn)門(mén)被約束總是實(shí)現(xiàn)IPrintable。 20.7.1遵循約束 無(wú)論什么時(shí)候使用構(gòu)造類型或者引用泛型方法,所提供的類型實(shí)參都將針對(duì)聲明在泛型類型,或者方法中的類型參數(shù)約束作出檢查。對(duì)于每個(gè)where 語(yǔ)句,對(duì)應(yīng)于命名的類型參數(shù)的類型實(shí)參A將按如下每個(gè)約束作出檢查。
如果約束是一個(gè)類類型或者接口類型,讓C表示提供類型實(shí)參的約束,該類型實(shí)參將替代出現(xiàn)在約束中的任何類型參數(shù)。為了遵循約束,類型A必須按如下方式可別轉(zhuǎn)化為類型C: - 同一轉(zhuǎn)換(§6.1.1) - 隱式引用轉(zhuǎn)換(§6.1.4) - 裝箱轉(zhuǎn)換(§6.1.5) - 從類型參數(shù)A到C(§20.7.4)的隱式轉(zhuǎn)換。 如果約束是new(),類型實(shí)參A不能是abstract并,且必須有一個(gè)公有的無(wú)參的構(gòu)造函數(shù)。如果如下之一是真實(shí)的這將可以得到滿足。 - A是一個(gè)值類型(如§4.1.2中所描述的,所有值類型都有一個(gè)公有默認(rèn)構(gòu)造函數(shù))。 - A是一個(gè)非abstract類,并且 A包含一個(gè)無(wú)參公有構(gòu)造函數(shù)。 - A是一個(gè)非abstract類,并且有一個(gè)默認(rèn)構(gòu)造函數(shù)(§10.10.4)。 如果一個(gè)或多個(gè)類型參數(shù)的約束通過(guò)給定的類型實(shí)參不能滿足,將會(huì)出現(xiàn)編譯時(shí)錯(cuò)誤。 因?yàn)轭愋蛥?shù)不被繼承,同樣約束也決不被繼承。在下面的例子中,D 必須在其類型參數(shù)T上指定約束,以滿足由基類B<T>所施加的約束。相反,類E不需要指定約束,因?yàn)閷?duì)于任何T,List<T>實(shí)現(xiàn)了IEnumerable接口。 class B<T> where T: IEnumerable{…} class D<T>:B<T> where T:IEnumerable{…} class E<T>:B<List<T>>{…} 20.7.2 在類型參數(shù)上的成員查找 在由類型參數(shù)T給定的類型中,成員查找的結(jié)果取決于為T(mén)所指定的約束(如果有的話)。如果T沒(méi)有約束或者只有new ()約束,在T上的成員查找,像在object上的成員查找一樣,返回一組相同的成員。否則,成員查找的第一個(gè)階段,將考慮T所約束的每個(gè)類型的所有成員,結(jié)果將會(huì)被合并,然后隱藏成員將會(huì)從合并結(jié)果中刪除。
在泛型出現(xiàn)之前,成員查找總是返回在類中唯一聲明的一組成員,或者一組在接口中唯一聲明的成員, 也可能是object類型。在類型參數(shù)上的成員查找做出了一些改變。當(dāng)一個(gè)類型參數(shù)有一個(gè)類約束和一個(gè)或多個(gè)接口約束時(shí),成員查找可以返回一組成員,這些成員有一些是在類中聲明的,還有一些是在接口中聲明的。下面的附加規(guī)則處理了這種情況。 在成員查找過(guò)程(§20.9.2)中,在除了object之外的類中聲明的成員隱藏了在接口中聲明的成員。 在方法和索引器的重載決策過(guò)程中,如果任何可用成員在一個(gè)不同于object的類中聲明,那么在接口中聲明的所有成員都將從被考慮的成員集合中刪除。 這些規(guī)則只有在將一個(gè)類約束和接口約束綁定到類型參數(shù)上時(shí)才有效。通俗的說(shuō)法是,在一個(gè)類約束中定義的成員,對(duì)于在接口約束的成員來(lái)說(shuō)總是首選。 20.7.3 類型參數(shù)和裝箱 當(dāng)一個(gè)結(jié)構(gòu)類型重寫(xiě)繼承于System.Object(Equals , GetHashCode或ToString)的虛擬方法,通過(guò)結(jié)構(gòu)類型的實(shí)例調(diào)用虛擬方法將不會(huì)導(dǎo)致裝箱。即使當(dāng)結(jié)構(gòu)被用作一個(gè)類型參數(shù),并且調(diào)用通過(guò)類型參數(shù)類型的實(shí)例而發(fā)生,情況也是如此。例如 using System; struct Counter { int value; public override string ToString() { value++; return value.ToString(); } } class Program { static void Test<T>() where T:new() { T x = new T(); Console.WriteLine(x.ToString()); Console.WriteLine(x.ToString()); Console.WriteLine(x.ToString()); } static void Main() { Test<Counter>(); } }
程序的輸出如下 1 2 3 盡管推薦不要讓ToString帶有附加效果(side effect)[2],但這個(gè)例子說(shuō)明了對(duì)于三次x.ToString()的調(diào)用不會(huì)發(fā)生裝箱。 當(dāng)在一個(gè)約束的類型參數(shù)上訪問(wèn)一個(gè)成員時(shí),裝箱決不會(huì)隱式地發(fā)生。例如,假定一個(gè)接口ICounter包含了一個(gè)方法Increment,它可以被用來(lái)修改一個(gè)值。如果ICounter被用作一個(gè)約束,Increment方法的實(shí)現(xiàn)將通過(guò)Increment在其上調(diào)用的變量的引用而被調(diào)用,這個(gè)變量不是一個(gè)裝箱拷貝。 using System; interface ICounter { void Increment(); } struct Counter:ICounter { int value; public override string ToString() { return value.ToString(); } void ICounter.Increment(){ value++; } } class Program { static void Test<T>() where T:new ,ICounter{ T x = new T(); Console.WriteLine(x); x.Increment(); //修改x` Console.WriteLine(x); ((ICounter)x).Increment(); //修改x的裝箱拷貝 Console.WriteLine(x); } static void Main() { Test<Counter>(); } }
對(duì)變量x的首次調(diào)用Increment修改了它的值。這與第二次調(diào)用Increment是不等價(jià)的,第二次修改的是x裝箱后的拷貝,因此程序的輸出如下 20.7.4包含類型參數(shù)的轉(zhuǎn)換 在類型參數(shù)T上允許的轉(zhuǎn)換,取決于為T(mén)所指定的約束。所有約束的或非約束的類型參數(shù),都可以有如下轉(zhuǎn)換。 從T到T的隱式同一轉(zhuǎn)換。 從T到object 的隱式轉(zhuǎn)換。在運(yùn)行時(shí),如果T是一個(gè)值類型,這將通過(guò)一個(gè)裝箱轉(zhuǎn)換進(jìn)行。否則,它將作為一個(gè)隱式地引用轉(zhuǎn)換。 從object到T的隱式轉(zhuǎn)換。在運(yùn)行時(shí),如果T是一個(gè)值類型,這將通過(guò)一個(gè)取消裝箱操作而進(jìn)行。否則它將作為一個(gè)顯式地引用轉(zhuǎn)換。 從T到任何接口類型的顯式轉(zhuǎn)換。在運(yùn)行時(shí),如果T是一個(gè)值類型,這將通過(guò)一個(gè)裝箱轉(zhuǎn)換而進(jìn)行。否則,它將通過(guò)一個(gè)顯式地引用轉(zhuǎn)換而進(jìn)行。 從任何接口類型到T的隱式轉(zhuǎn)換。在運(yùn)行時(shí),如果T是一個(gè)值類型,這將通過(guò)一個(gè)取消裝箱操作而進(jìn)行。否則,它將作為一個(gè)顯式引用轉(zhuǎn)換而進(jìn)行。 如果類型參數(shù)T指定一個(gè)接口I作為約束,將存在下面的附加轉(zhuǎn)換。 從T到I的隱式轉(zhuǎn)換,以及從T到I的任何基接口類型的轉(zhuǎn)換。在運(yùn)行時(shí),如果T是一個(gè)值類型,這將作為一個(gè)裝箱轉(zhuǎn)換而進(jìn)行。否則,它將作為一個(gè)隱式地引用轉(zhuǎn)換而進(jìn)行。 如果類型參數(shù)T指定類型C作為約束,將存在下面的附加轉(zhuǎn)換: 從T到C的隱式引用轉(zhuǎn)換,從T到任何C從中派生的類,以及從T到任何從其實(shí)現(xiàn)的接口。 從C到T的顯式引用轉(zhuǎn)換,從C從中派生的類[3]到T,以及C實(shí)現(xiàn)的任何接口到T
如果存在從C 到A的隱式用戶定義轉(zhuǎn)換,從T到 A的隱式用戶定義轉(zhuǎn)換。 如果存在從A 到C的顯式用戶定義轉(zhuǎn)換,從A到 T的顯式用戶定義轉(zhuǎn)換。 從null類型到T的隱式引用轉(zhuǎn)換 一個(gè)帶有元素類型的數(shù)組類型T具有object和System.Array之間的相互轉(zhuǎn)換(§6.1.4,§6.2.3)。如果T有作為約束而指定的類類型,將有如下附加規(guī)則 從帶有元素類型T的數(shù)組類型AT到帶有元素類型U的數(shù)組類型AU的隱式引用轉(zhuǎn)換,并且如果下列二者成立的話,將存在從AU到AT顯式引用轉(zhuǎn)換: - AT和AU 有相同數(shù)量的維數(shù)。 - U是這些之一:C,C從中派生的類型,C所實(shí)現(xiàn)的接口,作為在T上的約束而指定的接口I,或I的基接口。 先前的規(guī)則不允許從非約束類型參數(shù)到非接口類型的直接隱式轉(zhuǎn)換,這可能有點(diǎn)奇怪。其原因是為了防止混淆,并且使得這種轉(zhuǎn)換的語(yǔ)義更明確。例如,考慮下面的聲明。 class X<T> { public static long F(T t){ return (long)t; // ok,允許轉(zhuǎn)換 } } 如果t到int的直接顯式轉(zhuǎn)換是允許的,你可能很容易以為X<int>.F(7)將返回7L。但實(shí)際不是,因?yàn)闃?biāo)準(zhǔn)的數(shù)值轉(zhuǎn)換只有在類型在編譯時(shí)是已知的時(shí)候才被考慮。為了使語(yǔ)義更清楚,先前的例子必須按如下形式編寫(xiě)。 class X<T> { public static long F(T t) { return (long)(object)t; //ok;允許轉(zhuǎn)換 } }
--------------------------------------------------------------------------------
[1] 這種情況下“>”被解釋為大于運(yùn)算符。 [2] 在程序中重寫(xiě)ToString時(shí),一般不推薦添加這種類似的計(jì)算邏輯,因?yàn)樗倪@種結(jié)果變化不易控制,增加了調(diào)試程序的復(fù)雜性。 [3] C的基類或其基類的基類等。
|