C# 2.0 Specification(泛型五)

來源:互聯網
上載者:User

接泛型四

20.6.5文法歧義

在§20.9.3和§20.9.4中簡單名字(simple-name)和成員訪問(member-access)對於運算式來說容易引起文法歧義。例如,語句

F(G<A,B>(7));


可以被解釋為對帶有兩個參數G<A和B>(7)的F的調用[1]。同樣,它還能被解釋為對帶有一個參數的F的調用,這是一個對帶有兩個類型實參和一個正式參數的泛型方法G的調用。
如果運算式可以被解析為兩種不同的有效方法,那麼在“>”能被解析作為運算子的所有或一部分時,或者作為一個類型實參列表,那麼緊隨“>”之後的標記將會被檢查。如果它是如下之一:

{ } ] > : ; , . ?


那麼“>”被解析作為類型實參列表。否則“>”被解析作為一個運算子。

20.6.6對委託使用泛型方法

委託的執行個體可通過引用一個泛型方法的聲明而建立。委託運算式確切的編譯時間處理,包括引用泛型方法的委託建立運算式,這在§20.9.6中進行了描述。
當通過委託調用一個泛型方法時,所使用的類型實參將在委託執行個體化時被確定。類型實參可以通過類型實參列表顯式給定,或者通過類型推斷(§20.6.4)而確定。如果採用類型推斷,委託的參數類型將被用作推斷處理過程的實參類型。委託的傳回型別不用於推斷。下面的例子展示了為一個委託執行個體化運算式提供類型實參的方法。

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,類型實參被顯式給定D d2 = new D(F); //ok,int作為類型實參而被推斷E e1 = new E(G<int>); //ok,類型實參被顯式給定E e2 = new E(G); //錯誤,不能從傳回型別推斷}}

在先前的例子中,非泛型委派類型使用泛型方法執行個體化。你也可以使用泛型方法建立一個構造委託類型的執行個體。在所有情形下,當委託執行個體被建立時,類型實參被給定或可以被推斷,但委託被調用時,可以不用提供類型實參列表(§15.3)。

20.6.7非泛型屬性、事件、索引器或運算子

屬性、事件、索引器和運算子他們自身可以沒有類型實參(儘管他們可以出現在泛型類中,並且可從一個封閉類中使用類型實參)。如果需要一個類似屬性泛型的構件,取而代之的是你必須使用一個泛型方法。

20.7約束

泛型型別和方法聲明可以可選的指定型別參數約束,這通過在聲明中包含型別參數約束語句就可以做到。

type-parameter-constraints-clauses(型別參數約束語句:)type-parameter-constraints-clause(型別參數約束語句)type-parameter-constraints-clauses type-parameter-constraints-clause(型別參數約束語句 型別參數約束語句)type-parameter-constraints-clause:(型別參數約束語句:)where type-parameter : type-parameter-constraints(where 型別參數:型別參數約束)type-parameter-constraints:(型別參數約束:)class-constraint(類約束)interface-constraints(介面約束)constructor-constraint(建構函式約束)class-constraint , interface-constraints(類約束,介面約束)class-constraint , constructor-constraint(類約束,建構函式約束)interface-constraints , constructor-constraint(介面約束,建構函式約束)class-constraint , interface-constraints , constructor-constraint(類約束,介面約束,建構函式約束)class-constraint:(類約束:)class-type(類類型)interface-constraint:(介面約束:)interface-constraint(介面約束)interface-constraints , interface-constraints(介面約束,介面約束)interface-constraints:(介面約束:) interface-type(介面類型)constructor-constraint:(建構函式約束:)new ( )

每個型別參數約束語句由標誌where 緊接著型別參數的名字,緊接著冒號和型別參數的約束列表。對每個型別參數只能有一個where 語句,但where語句可以以任何順序列出。與屬性訪問器中的get和set標誌相似,where 語句不是關鍵字。

在where語句中給定的約束列表可以以這個順序包含下列組件:一個單一的類約束、一個或多個介面約和建構函式約束new ()。
如果約束是一個類類型或者介面類型,這個類型指定型別參數必須支援的每個類型實參的最小“基底類型”。無論什麼時候使用一個構造類型或者泛型方法,在編譯時間對於類型實參的約束建會被檢查。所提供的類型實參必須派生於或者實現那個型別參數個定的所有約束。
被指定作為類約束的類型必須遵循下面的規則。

該類型必須是一個類類型。
該類型必須是密封的(sealed)。
該類型不能是如下的類型:System.Array,System.Delegate,System.Enum,或者System.ValueType類型。
該類型不能是object。由於所有類型派生於object,如果容許的話這種約束將不會有什麼作用。
至多,對於給定型別參數的約束可以是一個類類型。

作為介面約束而被指定的類型必須滿足如下的規則。

該類型必須是一個介面類型。
在一個給定的where語句中相同的類型不能被指定多次。

在很多情況下,約束可以包含任何關聯類別型的型別參數或者方法聲明作為構造類型的一部分,並且可以包括被聲明的類型,但約束不能是一個單一的型別參數。
被指定作為型別參數約束的任何類或者介面類型,作為泛型型別或者被聲明的方法,必須至少是可訪問的(§10.5.4)。
如果一個型別參數的where 語句包括new()形式的建構函式約束,則使用new 運算子建立該類型(§20.8.2)的執行個體是可能的。用於帶有一個建構函式約束的型別參數的任何類型實參必須有一個無參的建構函式(詳細情形參看§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(){…}

下面的例子是一個錯誤,因為它試圖直接使用一個型別參數作為約束。

class Extend<T , U> where U:T{…}//錯誤


約束的型別參數類型的值可以被用於訪問約束暗示的執行個體成員。在例子

interface IPrintable{void Print();}class Printer<T> where T:IPrintable{void PrintOne(T x){x.Pint();}}

IPrintable的方法可以在x上被直接調用,因為T被約束總是實現IPrintable。

20.7.1遵循約束

無論什麼時候使用構造類型或者引用泛型方法,所提供的類型實參都將針對聲明在泛型型別,或者方法中的型別參數約束作出檢查。對於每個where 語句,對應於命名的型別參數的類型實參A將按如下每個約束作出檢查。


如果約束是一個類類型或者介面類型,讓C表示提供類型實參的約束,該類型實參將替代出現在約束中的任何型別參數。為了遵循約束,類型A必須按如下方式可別轉化為類型C:

- 同一轉換(§6.1.1)
- 隱式引用轉換(§6.1.4)
- 裝箱轉換(§6.1.5)
- 從型別參數A到C(§20.7.4)的隱式轉換。

如果約束是new(),類型實參A不能是abstract並,且必須有一個公有的無參的建構函式。如果如下之一是真實的這將可以得到滿足。

- A是一個實值型別(如§4.1.2中所描述的,所有實值型別都有一個公有預設建構函式)。
- A是一個非abstract類,並且 A包含一個無參公有建構函式。
- A是一個非abstract類,並且有一個預設建構函式(§10.10.4)。
如果一個或多個型別參數的約束通過給定的類型實參不能滿足,將會出現編譯時間錯誤。
因為型別參數不被繼承,同樣約束也決不被繼承。在下面的例子中,D 必須在其型別參數T上指定約束,以滿足由基類B<T>所施加的約束。相反,類E不需要指定約束,因為對於任何T,

List<T>實現了IEnumerable介面。class B<T> where T: IEnumerable{…}class D<T>:B<T> where T:IEnumerable{…}class E<T>:B<List<T>>{…}

20.7.2 在型別參數上的成員尋找

在由型別參數T給定的類型中,成員尋找的結果取決於為T所指定的約束(如果有的話)。如果T沒有約束或者只有new ()約束,在T上的成員尋找,像在object上的成員尋找一樣,返回一組相同的成員。否則,成員尋找的第一個階段,將考慮T所約束的每個類型的所有成員,結果將會被合并,然後隱藏成員將會從合并結果中刪除。

在泛型出現之前,成員尋找總是返回在類中唯一聲明的一群組成員,或者一組在介面中唯一聲明的成員, 也可能是object類型。在型別參數上的成員尋找做出了一些改變。當一個型別參數有一個類約束和一個或多個介面約束時,成員尋找可以返回一群組成員,這些成員有一些是在類中聲明的,還有一些是在介面中聲明的。下面的附加規則處理了這種情況。

在成員尋找過程(§20.9.2)中,在除了object之外的類中聲明的成員隱藏了在介面中聲明的成員。
在方法和索引器的重載決策過程中,如果任何可用成員在一個不同於object的類中聲明,那麼在介面中聲明的所有成員都將從被考慮的成員集合中刪除。


這些規則只有在將一個類約束和介面約束綁定到型別參數上時才有效。通俗的說法是,在一個類約束中定義的成員,對於在介面約束的成員來說總是首選。

20.7.3 型別參數和裝箱

當一個結構類型重寫繼承於System.Object(Equals , GetHashCode或ToString)的虛擬方法,通過結構類型的執行個體調用虛擬方法將不會導致裝箱。即使當結構被用作一個型別參數,並且調用通過型別參數類型的執行個體而發生,情況也是如此。例如

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>();}}

程式的輸出如下

123

儘管推薦不要讓ToString帶有附加效果(side effect)[2],但這個例子說明了對於三次x.ToString()的調用不會發生裝箱。
當在一個約束的型別參數上訪問一個成員時,裝箱決不會隱式地發生。例如,假定一個介面ICounter包含了一個方法Increment,它可以被用來修改一個值。如果ICounter被用作一個約束,Increment方法的實現將通過Increment在其上調用的變數的引用而被調用,這個變數不是一個裝箱拷貝。

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>();}}

對變數x的首次調用Increment修改了它的值。這與第二次調用Increment是不等價的,第二次修改的是x裝箱後的拷貝,因此程式的輸出如下

20.7.4包含型別參數的轉換

在型別參數T上允許的轉換,取決於為T所指定的約束。所有約束的或非約束的型別參數,都可以有如下轉換。

從T到T的隱式同一轉換。
從T到object 的隱式轉換。在運行時,如果T是一個實值型別,這將通過一個裝箱轉換進行。否則,它將作為一個隱式地引用轉換。
從object到T的隱式轉換。在運行時,如果T是一個實值型別,這將通過一個unboxing操作而進行。否則它將作為一個顯式地引用轉換。
從T到任何介面類型的顯式轉換。在運行時,如果T是一個實值型別,這將通過一個裝箱轉換而進行。否則,它將通過一個顯式地引用轉換而進行。
從任何介面類型到T的隱式轉換。在運行時,如果T是一個實值型別,這將通過一個unboxing操作而進行。否則,它將作為一個顯式引用轉換而進行。


如果型別參數T指定一個介面I作為約束,將存在下面的附加轉換。

從T到I的隱式轉換,以及從T到I的任何基底介面類型的轉換。在運行時,如果T是一個實值型別,這將作為一個裝箱轉換而進行。否則,它將作為一個隱式地引用轉換而進行。


如果型別參數T指定類型C作為約束,將存在下面的附加轉換:

從T到C的隱式引用轉換,從T到任何C從中派生的類,以及從T到任何從其實現的介面。
從C到T的顯式引用轉換,從C從中派生的類[3]到T,以及C實現的任何介面到T



如果存在從C 到A的隱式使用者定義轉換,從T到 A的隱式使用者定義轉換。
如果存在從A 到C的顯式使用者定義轉換,從A到 T的顯式使用者定義轉換。
從null類型到T的隱式引用轉換

一個帶有元素類型的數群組類型T具有object和System.Array之間的相互轉換(§6.1.4,§6.2.3)。如果T有作為約束而指定的類類型,將有如下附加規則

從帶有元素類型T的數群組類型AT到帶有元素類型U的數群組類型AU的隱式引用轉換,並且如果下列二者成立的話,將存在從AU到AT顯式引用轉換:

- AT和AU 有相同數量的維數。
- U是這些之一:C,C從中派生的類型,C所實現的介面,作為在T上的約束而指定的介面I,或I的基底介面。
先前的規則不允許從非約束型別參數到非介面類型的直接隱式轉換,這可能有點奇怪。其原因是為了防止混淆,並且使得這種轉換的語義更明確。例如,考慮下面的聲明。

class X<T>{public static long F(T t){return (long)t; // ok,允許轉換}

}
如果t到int的直接顯式轉換是允許的,你可能很容易以為X<int>.F(7)將返回7L。但實際不是,因為標準的數值轉換隻有在類型在編譯時間是已知的時候才被考慮。為了使語義更清楚,先前的例子必須按如下形式編寫。

class X<T>{public static long F(T t){return (long)(object)t; //ok;允許轉換}}

[1] 這種情況下“>”被解釋為大於運算子。

[2] 在程式中重寫ToString時,一般不推薦添加這種類似的計算邏輯,因為它的這種結果變化不易控制,增加了偵錯工具的複雜性。

[3] C的基類或其基類的基類等。

以上就是C# 2.0 Specification(泛型五)的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!

  • 相關文章

    聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.