接泛型五
20.8表達式和語句
某些表達式和語句的操作針對泛型進行了修改。這一節將介紹這些改變。
20.8.1預設值表達式
預設值表達式被用來獲得一個類型的預設值(§5.2)。通常一個預設值表達式被用於類型參數,因為如果類型參數的是一個值類型或參考類型,它可能不是已經的。 (不存在從null型別到型別參數的轉換。)
primary-no-array-creation-expression:(基本无数组创建表达式:) … default-value-expression(默认值表达式) default-value-expression:(默认值表达式:) primary-expression . default (基本表达式 .default) predefined-type . default(预定义类型. default)
如果一個基本表達式在一個預設值表達式中被使用,而這個基本表達式不能分成一個型別,那麼將會出現一個編譯時錯誤。然而在§7.5.4.1中所描述的規則也適用於形成E.default的構件。
如果預設值表達式的左邊針對一個參考型別在執行時被計算,結果是將null轉換到那個型別。如果預設值表達式的左邊針對一個值類型在執行時被計算,其結果是值類型的預設值(§4.1.2)。
如果型別是具有類別約束的參考型別或型別參數,預設值運算式是一個常數表達式(§7.15)。此外,如果型別是下列值之一,預設值表達式是一個常數表達式:sbyte、byte、 short、ushort、 int 、 uint 、long 、ulong 、 char 、float 、double 、decimal 或bool。
20.8.2物件建立表達式
物件常見表達式的型別可以是一個型別參數。當型別參數被當作物件建立運算式中的型別而指定時,下列兩個條件必須具備,否則將會出現編譯時錯誤
實參列表必須刪除
必須為型別參數而指定為new()形式的構造函數約束
透過建立一個類型參數被綁定到的運行時類型的實例,並呼叫該類型的預設建構函數,就可以執行物件建立表達式。運行時類型可以是引用或值類型。
20.8.3運算子的型別
typeof運算子可以用於型別參數。其結果是被綁定到型別參數的運行時類型的System.Type物件。 typeof運算子也可以被用來建構型別。
class X <T> { public static void PrintTypes() { Console.WriteLine(typeof(T).FullName); Console.WriteLine(typeof(X<X<T>>).FullName); } } class M { static void Main() { X<int>.PrintTypes(); } }
先前的程序將列印如下。
System.Int32 X<X<Sytem.Int32>> Typeof运算符不能用于没有指定类型实参的泛型类型声明的名字。 class X<T>{…} class M { static void Main() { Type t = typeof(X); //错误,X需要类型实参 } }
20.8.4引用相等運算子
如果T由一個類別約束而約束,引用型別相等運算子可以被用來比較型別參數T的值。
引用型別相等運算子的用法可以讓型別參數T的實參很容易地與其他為null的實參比較,即使T沒有類別限制也是如此。在運行時,如果T是值類型,則比較的結果將是false。
下面的例子檢查一個非約束類型參數類型的實參是否是null。
class C<T> { void F(T x) { if(x==null) thow new ArgumentNullException(); … } }
即使是T可以表示一個值類型,x==null構件也是允許的,並且當T是值類型時,其結果被簡單的定義為false。
20.8.5 is运算符
在开放类型上的is运算符操作遵循通常的规则(§7.9.9)。如果e或T的编译时类型是一个开放类型,那么在运行时对于e和T将总是执行动态类型检查。
20.8.6as运算符
只要T有一个类约束,类型参数T可被用在as运算符的右边。这种限制是需要的,因为值null可能被作为运算符的结果返回。
class X { public T F<T>(object o) where T:Attribute { return o as T; //ok,T有一个类约束 } public T G<T>(object o) { return o as T; //错误,T没有约束 }
}
在as运算符(§7.9.10)的当前规范中,对于表达式e as T最后一点表明,如果从e的编译时类型到T,不存在有效的显式引用转换,将会出现编译时错误。对于泛型,这条规则稍微作了修改。如果E的编译时类型或T是一个开放类型,在这种情况下将不会出现编译时错误;相反,运行时检查将会执行。
20.8.7异常语句
对于开放类型,throw(§8.9.5)和try(§8.10)的通常规则是适用的。
只要类型参数具有System.Exeption异常(或子类具有)作为类约束,那么throw语句可以被用作其类型有一个类型参数给定的表达式。
只要类型参数System.Exception(或子类子类具有)作为类约束,那么在catch语句中的命名的类型可能是一个类型参数。
20.8.8 lock语句
lock语句可以被用作其类型由一个类型参数给定的表达式。如果表达式的运行时类型是一个值类型,lock将没有效果(因为对于装箱值不能有任何其他的引用)。
20.8.9 using 语句
using 语句(§8.13)遵循通常的规则:表达式必须被隐式的转换到System.IDisposable。如果类型参数通过System.IDisposable而约束,那么该类型的表达式可以使用using 语句。
20.8.10 foreach语句
给定如下形式的foreach语句
foreach(ElementType element in collection) statement
如果集合表达式是一个没有实现集合模式的类型,但为每个类型T实现了构造接口System.Collections.Generic.IEnumerable
IEnumerator<T> enumerator = ((IEnuemrable<T>)(collection).GetEnumerator(); try { where (enumerator.MoveNext()){ ElementType element = (ElementType)enumerator.Current; statement; } } finally{ enumerator.Dispose(); }
20.9查找规则修订
泛型修改了用于查找和绑定名字的某些基本规则。下面几节在考虑泛型的情况下,重新叙述了所有的基本名字查找规则。
20.9.1命名空间和类型名字
如下內容可替換§3.8。
在C#程式中有幾個上下文需要指定命名空間或類型名字。任何形式的名字都可以由一個或多個由「.」標記分隔的識別符號組成。
namespace-name:(命名空間名稱:)
namespace-or-type-name(命名空間或模式名稱)
type-name:(模式名稱:)
namespace-or-type-name(命名空間或類型名字)
namespace-or-type-name:(命名空間或型別名稱:)
identifier type-argument-list opt(標識符類型實參列表可選)
namespace-or-type-name . identifier type-argument- list opt(命名空間或型別名稱. 識別字型別實參清單可選)
命名空間名字是引用命名空間的命名空間名字或型別名字(namespace-or-type-name)。請參閱下面所描述的決策,命名空間名字的命名空間或型別名字必須引用一個命名空間,否則出現編譯時錯誤。在一個命名空間名字中不能有類型實參(只有類型可以有類型實參)。
型別名字是一個引用型別的命名空間或型別名字(namespace-or-type-name)。請參閱下面所描述的決策,類型名字的命名空間或類型名字必須引用一個類型,否則出現編譯時錯誤。
命名空間或類型名字的意義如下決定。
如果命名空間或類型名字是I或I
- 如果命名空間或類型名字出現在泛型方法聲明之內,並且該聲明包括一個由I給定名字的沒有指定類型實參列表的類型參數,那麼命名空間或類型名字引用該類型參數。
- 否則,如果命名空間或類型名字出現在類型聲明之內,那麼對於類型T的每個實例(
u 如果沒有指定包含由I給定名字,並且沒有類型實參列表的類型參數T的聲明,那麼命名空間或類型名字引用那個類型參數。
u 否則,如果I是T中可訪問成員的名字,並且如果那個成員是一個帶有匹配類型參數數量的類型,那麼命名空間或類型名字引用類型T.I或類型T.I
- 否則,對於在以命名空間或類型名字出現的命名空間開始的每個命名空間N,並繼續使用每個封閉命名空間(如果有的話),然後以全局命名空間結束,下面的步驟將會被計算,直到該實體被定位。
u 如果I 是在N中的命名空間中的名字,並且沒有指定類型實參列表,那麼命名空間或類型名字引用該個命名空間。
u 否則,如果I是在N中帶有匹配類型參數數量的可訪問類型的名字,那麼命名空間或類型名字引用使用給定類型實參構造的類型。
u 否則,如果命名空間或類型名字出現的位置,由N的命名空間聲明所封閉
- 如果命名空間聲明包含一個由I 給定名字的using 別名指令,而I帶有導入命名空間或類型,並且沒有指定參數列表,那麼命名空間或類型名字引用該命名空間或者類型
- 否則,如果由命名空間聲明的using命名空間指示符導入的命名空間,恰好包含帶有I 給定名字的,匹配類型參數數量的類型,那麼命名空間或型別名字引用由給定型別實參建構的這個型別。
- 否則,如果由命名空間聲明的using 命名空間指示符導入的命名空間包含多個帶有I給定名字的,匹配類型參數數量的類型,那麼命名空間或者類型名字是模糊的,並且將導致錯誤。
- 否則,命名空間或型別名字是未定義的,並且出現編譯時錯誤。
l 否則,命名空間或類型名字是N.I或N.I
- 如果N引用一個命名空間,並且如果I是內嵌在N中的命名空間名字,並且沒有指定類型實參列表,那麼命名空間或類型名字引用該內嵌命名空間。
- 否則,如果N引用一個命名空間,並且I是在具有匹配類型參數數量的N中的可訪問類型的名字,那麼命名空間或類型名字引用由給定類型實參構造的那個類型。
- 否則,如果N引用類別或結構類型,並且I是內嵌在帶有與類型參數匹配的N中的可訪問類型的名字,那麼命名空間或者類型名字引用由給定實參構造的那個類型。
- 否則,N.I是一個無效的命名空間名字,並且會出現編譯時錯誤。
20.9.2成員查找
下面內容可替換§7.3
成員查找是一種根據在上下文中的意義來確定類型的處理過程。在一個表達式中,成員查找可以作為計算一個簡單名字或成員存取(§20.9.4)而出現。
在類型T中的名字N的成員查找如下規則決定。
首先一組名為N的可訪問成員被確定。
- 如果T是一個類型參數,那麼在每個作為T的類別約束或介面約束而指定的類型,連同在object中的N的命名成員的集合中,這個集合就是命名的可存取成員的聯合。
- 否則,這個集合由T中的N的所有命名可訪問成員組成,包括繼承成員和在object中的N的命名可訪問成員。如果T 是構造類型,成員的集合透過替換§20.5.4中所描述的類型實參而得到。包含override修飾符的成員將從集合中排出。
接著,透過其他成員而被隱藏的成員將從這個集合中刪除。對於在集合中的每個成員S.M,S是M在其中被聲明的類型,下面的規則可適用
- 如果M是一個常數、字段、屬性、事件或枚舉成員,那麼在S的所有基類別中聲明的成員都將從這個集合中刪除。
- 如果M是一個類型聲明,那麼在S基類中的所有非類型聲明都從集合被刪除,並且,作為S 在基類型中聲明的M的所有帶有相同數量類型參數的類型聲明,都將從該集合中刪除。
- 如果M是一個方法,那麼在S的基類中聲明的所有非方法成員,都將從這個集合刪除,並且,作為S 在基類型中聲明的M的帶有相同簽名的所有方法都將從這個集合中刪除。
接著,透過類別成員隱藏的介面成員將從該集合中刪除。這一步,只有當T是類型參數,且T有類別約束和多個介面約束時才有效。對於在集合中的每個成員S.M,其中S是M在其中聲明的類型,如果S是一個類別聲明而不是object的話,下面的規則適用
- 如果M是一個常數、字段、屬性、事件、枚舉成員或類型聲明,那麼在介面聲明中聲明的所有成員都將從這個集合中刪除。
- 如果M是一個方法,那麼在接口類型中聲明的所有非方法成員都將從這個集合中刪除,並且,作為S 在接口中聲明的M的帶有相同簽名的所有方法都將從這個集合中刪除。
最後,在刪除隱藏成員之後,查找的結果將被確定
- 如果由單一成員組成的集合,不是類型、方法,那麼這個成員就是查找的結果。
- 否則,如果集合只包含方法,那麼這組方法就是尋找的結果。
- 否則,如果集合只包含類型聲明,那麼這組類型聲明在成員尋找的結果中。
- 否則,查找是不明確的,將會出現編譯時錯誤。
對於類型,而不是類型參數和介面的成員查找來說,在介面中的成員查找是嚴格的單繼承(在繼承鏈中的每個介面恰好有零個或一個直接基底介面),查找規則的影響只是派生成員隱藏帶有相同名字和簽名的基底類別成員。這種單繼承查找是很明確的。成員查找中可能引起的模糊性來自於§13.2.5中描述的多重繼承介面
20.9.3簡單名字
如下內容可替換§7.5.2。
簡單名字由一個標識符,其後可跟隨可選的類型參數清單。
simple-name:(簡單名字:)
identifier type-argument-list opt(標識符類型實參列表可選)
對於I或I
如果簡單名字出現在區塊內,並且如果區塊的局部變數聲明空間包含由I給定名字的局部變數或參數,那麼,這個簡單名字引用該局部變數和參數,並且作為一個變數而被分類。如果類型實參清單被指定,將會出現編譯時錯誤。
如果簡單名字出現在泛型方法聲明體之內,並且如果該聲明包含由I給定名字的類型參數,那麼簡單名字引用那個類型參數,如果只定了類型實參列表被,將會出現編譯時錯誤。
否則,對於以直接封閉類別、結構或枚舉宣告的實例類型開始的類型T的每個實例,繼續採用每個封閉外部類別或結構宣告的實例類型(如果有的話)。
- 如果T的聲明包括由 I給定名字的型別參數,那麼,簡單名字引用該型別參數。如果類型實參清單被指定,將會出現編譯時錯誤。
- 否則,如果在T 中I的成員查找產生一個匹配
u 如果T是直接封閉類別或結構類型的實例類型, 並且查找標識一個或多個方法,結果將是一個帶有與this表達式關聯的方法組。如果類型實參清單被指定,它被用在一個泛型方法呼叫中(§20.6.3)。
u 如果T是直接封閉類別或結構類型的實例類型,如果尋找標識一個實例成員,並且引用出現在一個實例建構函式、實例方法或一個實例存取器的區塊之內,其結果將會和this.I形式的成員訪問相似。如果類型實參被指定,將出現編譯時錯誤。
u 否則,結果與T.I或T.I
否則,對於帶有每個簡單名字出現在其中的命名空間的命名空間N,繼續採用每個封閉命名空間(如果有的話),並以全局命名空間結束,下面的步驟將被計算,直到一個實體被定位。
- 如果I是在N 中的一個命名空間的名字,並且沒有指定類型實參列表,那麼簡單名字將引用該命名空間。
- 否則,如果I是在具有匹配類型參數數量的N中的一個可訪問類型的名字,那麼簡單類型引用由給定類型實參構造的那個類型。
u 如果命名空間聲明包含一個關聯由I給定名字的using 別名指令,這裡I是一個導入命名空間或類型,並且沒有指定類型實參列表,那麼簡單名字引用該命名空間或類型。
u 否則,如果由命名空間聲明的using 命名空間指令導入的命名空間,恰好包含一個由I給定名字的,匹配類型參數數量的類型,那麼簡單名字引用由給定類型實參構造的類型。
u 否則,如果由命名空間聲明的using命名空間指令導入的命名空間,包含多個由I給定名字,匹配類型參數數量的類型,那麼簡單名字是不明確的,將導致編譯時錯誤
l否則,由簡單名字給定的名字是未定義的,將導致編譯時錯誤。
20.9.4成員訪問
如下內容可代替§7.5.4。
成員存取由基本表達式或預先定義類型,緊接一個「.」標記,再緊接一個標識符,然後接著可選的類型實參列表而組成。
member-access:(成員存取:)
primary-expression . identifier type-argument-list opt(基本表達式. 標識符類型實參列表可選)
predefined-type . identifier type-argument-list opt(預參考定義類型. 標識符類型實參列表可選)預定義類型:下列之一
bool byte char decimal double float int long
object sbyte short string uint ulong ushort
對於E.I或E.I
如果E 是一個命名空間,I是E 中嵌套命名空間的名字,並且沒有指定類型實參,那麼結果就是這個命名空間。
如果E是一個命名空間,I是在E中可訪問類型的名字,E 匹配類型參數的數量,那麼結果就是由給定類型實參構造的類型。
如果E是一個預先定義類型或作為一個類型而被分類的基本表達式,如果E 不是類型參數,並且如果在E中I的成員查找產生一個匹配,那麼E.I被計算,並按如下分類。
- 如果I標識一個或多個類型聲明,那麼使用相同數量的(可能是零)類型參數來確定該類型聲明,就像是在類型實參中提供的那樣。結果就是由給定型別實參而建構的型別。如果類型聲明不符合類型參數的數量,將出現編譯時錯誤。
- 如果I標識一個或多個方法,那麼結果就是沒有關聯實例表達式的方法組。如果類型實參清單被指定,它將在泛型方法呼叫中被使用(§20.6.3)。
- 如果I 標識一個靜態屬性、靜態欄位、靜態事件、常數或一個枚舉成員,並且如果類型實參列表被指定,將出現編譯時錯誤。
- 如果I標識一個靜態屬性,那麼結果就是帶有無關聯實例表達式的屬性存取。
- 如果I標識一個靜態字段
u 如果字段是唯讀的,並且引用發生在類別或結構的靜態構造函數之外,字段將在這裡被聲明。那麼結果就是一個值,也就是在E中靜態字段I的值。
u 否則,結果是一個變量,也就是在E中的靜態字段I。
- 如果I標識一個靜態事件
u 如果引用發生在事件被聲明的類別或者結構中,並且事件被聲明時沒有使用事件訪問器聲明(event-accessor-declaration)(§10.7),那麼E.I就好像I是一個靜態字段一樣被處理。
u 否則,結果是一個無關聯的實例表達式的事件存取。
- 如果I標識一個常數,那麼結果就是值,也就是常數的值。
- 如果I標識一個枚舉成員,那麼結果就是一個值,也就是枚舉成員的值。
- 否則,E.I是一個無效成員引用,並將導致編譯時錯誤。
如果E是一個屬性存取、索引器存取、變數或值,其類型是T,並且在T中I的成員尋找產生一個匹配,那麼E.I如下被計算和分類。
- 首先,如果E是一個屬性或索引器訪問,那麼屬性或索引器訪問的值將被獲得(§7.1.1),並且E被重分類為值。
- 如果I標識一個或多個方法,那麼結果就是一個帶有關聯的E的實例表達式的方法組。如果類型實參清單被指定,它將在泛型方法呼叫中被使用(§20.6.3)。
- 如果I標識一個實例屬性、實例欄位或實例事件,並且如果類型實參清單被指定,將產生編譯時錯誤。
- 如果I標識一個實例屬性,那麼結果就是一個帶有關聯的E 的實例表達式。
- 如果T是一個類別類型且I 標識一個類別類型的實例欄位
u 如果E的值是null,那麼將會拋出System.NullReferenceException。
u 否則,如果該字段是唯讀的,並且引用出現在字段聲明的類別的實例構造函數之外,那麼結果是值,也就是由E引用的對像中 I的值。
u 否則,結果是變量,也就是由E引用的物件中字段I。
- 如果T是一個結構類型,並且I 標識該結構類型的實例字段
u 如果E是一個值,或者如果字段是唯讀的,並且引用出現在字段聲明的結構的實例構造函數之外,那麼結果是一個值,也就是由E給定的結構實例中字段I的值。
u 否則,結果是一個變量,也就是由E給定結構實例中的字段I;
- 如果I標識一個實例事件
u 如果引用出現在該事件被聲明的類或結構之內,並且事件被聲明時沒有使用事件存取器聲明,那麼E.I就好像I是實例欄位一樣被處理。
u 否則,結果是一個帶有關聯的E的實例表達式。
否則,E.I是一個無效成員引用,將導致編譯時錯誤。
20.9.5方法呼叫
如下內容可取代§7.5.5.1中描述方法呼叫的編譯時處理部分。
對於M(A)形式的方法呼叫的編譯時處理,其中M是一個方法組(可能包含一個類型實參列表),A是可選實參列表,由以下步驟組成。
方法呼叫的候選集合被構造。對於每個與方法組M關聯的方法F
- 如果F是非泛型的,當如下成立時F是候選項
u M沒有類型實參列表,並且
u 對於A(§7.4.2.1) , F是適用的。
- 如果F是泛型,且M沒有類型實參列表,當如下成立時,F是候選項
u 類型推斷成功(§20.6.4),對於調用推斷出類型實參的列表,並且
u一旦推斷的類型實參替換了對應方法類型參數,F的參數列表對於A是適用的,並且
u 在替換類型實參後,F 的參數列表,與適用的可能以其擴展形式(§7.4. 2.1)在相同的型別中作為F而聲明的非泛型方法是不同的。
- 如果F是泛型,且M包括一個類型實參列表,當如下成立時,F是候選項
u F和在類型實參列表中提供的一樣,具有相同數量的方法類型參數,並且
u 一旦,類型實參替換對應的方法類型參數,F的參數清單對於A 是可適用的(§7.4.2.1)。
候選方法的集合被縮減到只包含從深度派生類型而來的方法:對於在集合中的每個C.F方法,C是F在其中聲明的類型,在C的基類型中聲明的所有方法都被從集合中刪除。
如果候選方法的結果集合是空的,那麼沒有可適用的方法存在,並且會出現編譯時錯誤。如果候選方法並不是在同一類型中聲明的,方法呼叫將是不明確的,並且會出現編譯時錯誤(這後一種情況,只可能出現在對於一個在具有多重直接基介面的介面中的方法的調用,如§13.2.5的描述)。
候選方法集合的最佳方法使用重載決策規則(§7.4.2)來識別。如果一個單一的最佳方法不能被標識,則該方法呼叫是不明確的,並產生編譯時錯誤。當執行重載決策時,泛型方法的參數在將對應的方法類型參數替換為類型實參(提供的或推斷的)之後將被考慮。
被選擇的最佳方法的最後驗證被執行
- 方法在方法組的上下文中是有效的:如果方法是一個靜態方法,方法組必須透過類型源自於簡單名字或成員存取。如果該最佳方法是實例方法,方法群組必須透過變數或值或基底類別存取(base-access)源自於簡單名稱或成員存取。如果這些需求都不滿足,那麼將會出現編譯時錯誤。
- 如果該最佳方法是一個泛型方法,類型實參(提供的或推斷的)將被針對聲明在泛型方法之上的約束作出檢查。如果任何類型實參不滿足對應類型參數的約束,將產生一個編譯時錯誤。
一旦方法根據前面的步驟被選擇和驗證,實際的運行時呼叫將根據§7.4.中的函數成員呼叫規則而被處理。
20.9.6委託建立表達式
如下內容可取代§7.5.10.3中委託建立表達式的編譯時處理部分。
對於new D(E)形式的委託創建表達式的編譯時處理,其中D 是一個委託類型,E是一個表達式,由以下步驟組成。
如果E是一個方法組
- 對應於E(A)形式的方法調用,一個單一的方法調用將被選擇。
u D的參數類型和修飾符(ref或out)被用作實參類型和實參列表A的修飾符。
u 在適用的測試和類型推論中,轉換不予考慮。在隱式轉換足夠的實例中,類型要求是同一的。
u 重載決策步驟不會執行。相反,候選的集合必須恰好包含一個與D相容的方法(接著使用類型實參替換類型參數),而這個方法將變成新建立委託所引用的方法。如果沒有符合的方法存在,或有多個符合的方法存在,將發生編譯時錯誤。
- 如果被選擇的方法是一個實例方法,與E關聯的實例表達式決定委託的目標物件。
- 結果是一個D類型的值,也就是引用選擇的方法和目標物件的新建立委託。
否則,E是一個委託類型的值
- D和E必須相容;否則出現編譯時錯誤。
- 結果是D類型的值,也就是像E一樣引用相同的呼叫清單的新建立的委託。
否則,委託建立表達式是無效的,並且出現編譯時錯誤。
20.10右移語法改變
泛型使用「」字元分隔類型參數和類型實參(與C++的模板語法相似)。構造類型有時可嵌套,如List
為了讓這些中性的構件維持簡單的詞法,「>>」和「>>=」標記從詞法中刪除了,取代的是右移和右移賦值產生式。
運算子或標點:之一
{ } [ ] ( ) . , : ;
+ - * / % & | ^ ! ~
= ? ++ -- && || == ->
!= = += -= *= /= %= &= |=
^= right-shift:(右移:)
> >
right-shift-assignment:(右移賦值)
> >=
不像在句法中的其他產生式,在右移和右移賦值產生式的標記之間不允許任何種類的字符存在(甚至是空格)。
下面的產生式被使用右移或右移賦值進行了修改。
shift-expression:(移位表達式:)
additive-expression(附加表達式)
shift-expression shift-expression right-shift additive-shift expression(移位表達式right-shift 附加表達式)
assignment-operator:(賦值運算子:)
=
+=
-=
*=
/=
%=
&=
=
right-shift-assignment
overloadable-binary-operator:(可重載二元運算子:)
+
-
*
/
%
&
|
*
/
%
&
|
^
==
!=
>
>=
(泛型完)
以上就是C# 2.0 Specification(泛型六)的內容,更多相關內容請關注PHP中文網(m.sbmmt.com)!