20.1.6泛型類別中的靜態建構子
在泛型類別中的靜態建構子被用於初始化靜態字段,為每個從特定泛型類別聲明中創建的不同的封閉構造類型,執行其他初始化。泛型型別宣告的型別參數在作用域之內,可以在靜態建構子體內被使用。
如果下列情形之一發生,一個新的封閉構造類別將首次初始化。
一個封閉構造類型的實例被創建時
封閉構造類型的任何靜態成員被引用時
為了初始化一個新的封閉的構造類類型,首先那個特定封閉類型的一組新靜態字段(§20.1 .5)將會被創建。每個靜態欄位都被初始化為其預設值(§5.2)。接著,靜態字段初始化器(§10.4.5.1)將為這些靜態字段執行。最後靜態建構函數將會被執行。
由於靜態建構函式將為每個封閉建構類別型別執行一次,那麼在無法透過約束(§20.7)檢查的型別參數上實作執行時檢查,將會很方便。例如,下面的型別使用一個靜態建構函式檢查一個型別參數是否是一個參考型別。
class Gen<T> { static Gen(){ if((object)T.default != null){ throw new ArgumentException(“T must be a reference type”); } } }
20.1.7 存取受保護的成員
在一個泛型類別聲明中,對於繼承的受保護的實例成員的存取是允許的,透過從泛型類別建構的任何類型的實例就可以做到。尤其是,用於存取§3.5.3中指定的protected和protected internal實例成員的規則,對於泛型使用如下的規則進行了擴充。
在一個泛型類別G中,對於一個繼承的受保護的實例成員M,使用E.M的基本表達式是允許的,前提是E的類型是一個從G構造的類別類型,或繼承於一個從G構造的類別類型的類別類型。
在例子
class C<T> { protected T x; } class D<T> :C<T> { static void F(){ D<T> dt = new D<T>(); D<int> di = new D<int>(); D<string> ds = new D<string>(); dt.x = T.default; di.x = 123; ds.x = “test”; } }
三個對x的賦值語句都是允許的,因為它們都透過從泛型構造的類別類型的實例發生。
20.1.8在泛型類別中重載
在一個泛型類別宣告中的方法、建構子、索引器和運算子可以被重載。但為了避免在構造類別中的歧義,這些重載是受約束的。在同一個泛型類別宣告中使用相同的名字宣告的兩個函數成員必須具有這樣的參數類型,也就是封閉建構型別中不能出現兩個成員使用相同的名字和簽名。當考慮所有可能的封閉構造類型時,這條規則包含了在當前程序中目前不存在的類型是實參,但它仍然是可能出現的[1]。在類型參數上的類型約束由於這條規則的目的而被忽略了。
下面的例子根據這條規則展示了有效和無效的重載。
nterface I1<T> {…} interface I2<T>{…} class G1<U> { long F1(U u); //无效重载,G<int>将会有使用相同签名的两个成员 int F1(int i); void F2(U u1, U u2); //有效重载,对于U没有类型参数 void F2(int I , string s); //可能同时是int和string void F3(I1<U>a); //有效重载 void F3(I2<U>a); void F4(U a); //有效重载 void F4(U[] a);} class G2<U,V> { void F5(U u , V v); //无效重载,G2<int , int>将会有两个签名相同的成员 void F5(V v, U u); void F6(U u , I1<V> v);//无效重载,G2<I1<int>,int>将会有两个签名相同的成员 void F6(I1<V> v , U u); void F7(U u1,I1<V> V2);//有效的重载,U不可能同时是V和I1<V> void F7(V v1 , U u2); void F8(ref U u); //无效重载 void F8(out V v); } class C1{…} class C2{…} class G3<U , V> where U:C1 where V:C2 { void F9(U u); //无效重载,当检查重载时,在U和V上的约束将被忽略 void F9(V v); }
0.1.9參數陣列方法和型別參數
型別參數可以用在參數陣列的型別中。例如,給定聲明
class C<V> { static void F(int x, int y ,params V[] args); } 方法的扩展形式的如下调用 C<int>.F(10, 20); C<object>.F(10,20,30,40); C<string>.F(10,20,”hello”,”goodbye”); 对应于如下形式: C<int>.F(10,20, new int[]{}); C<object>.F(10,20,new object[]{30,40}); C<string>.F(10,20,new string[](“hello”,”goodbye”));
20.1.10重寫和泛型類別
在泛型类中的函数成员可以重写基类中的函数成员。如果基类是一个非泛型类型或封闭构造类型,那么任何重写函数成员不能有包含类型参数的组成类型。然而,如果一个基类是一个开放构造类型,那么重写函数成员可以使用在其声明中的类型参数。当重写基类成员时,基类成员必须通过替换类型实参而被确定,如§20.5.4中所描述的。一旦基类的成员被确定,用于重写的规则和非泛型类是一样的。
下面的例子演示了对于现有的泛型其重写规则是如何工作的。
abstract class C<T> { public virtual T F(){…} public virtual C<T> G(){…} public virtual void H(C<T> x ){…} } class D:C<string> { public override string F(){…}//OK public override C<string> G(){…}//OK public override void H(C<T> x); //错误,应该是C<string> } class E<T,U>:C<U> { public override U F(){…}//OK public override C<U> G(){…}//OK public override void H(C<T> x){…}//错误,应该是C<U> }
20.1.11泛型类中的运算符
泛型类声明可以定义运算符,它遵循和常规类相同的规则。类声明的实例类型(§20.1.2)必须以一种类似于运算符的常规规则的方式,在运算符声明中被使用,如下
一元运算符必须接受一个实例类型的单一参数。一元运算符“++”和“—”必须返回实例类型。
至少二元运算符的参数之一必须是实例类型。
转换运算符的参数类型和返回类型都必须是实例类型。
下面展示了在泛型类中几个有效的运算符声明的例子
class X<T> { public static X<T> operator ++(X(T) operand){…} public static int operator *(X<T> op1, int op2){…} public static explicit operator X<T>(T value){…} }
对于一个从源类型S到目标类型T的转换运算符,当应用§10.9.3中的规则时,任何关联S或T的类型参数被认为是唯一类型,它们与其他类型没有继承关系,并且在这些类型参数上的任何约束都将被忽略。
在例子
class C<T>{…} class D<T>:C<T> { public static implicit operator C<int>(D<T> value){…}//OK public static implicit operator C<T>(D<T> value){…}//错误 }
第一个运算符声明是允许的,由于§10.9.3的原因,T和int被认为是没有关系的唯一类型。然而,第二个运算符是一个错误,因为C
给定先前的例子,为某些类型实参声明运算符,指定已经作为预定义转换而存在的转换是可能的。
struct Nullable<T> { public static implicit operator Nullable<T>(T value){…} public static explicit operator T(Nullable<T> value){…} }
当类型object作为T的类型实参被指定,第二个运算符声明了一个已经存在的转换(从任何类型到object是一个隐式的,也可以是显式的转换)。
在两个类型之间存在预定义的转换的情形下,在这些类型上的任何用户定义的转换都将被忽略。尤其是
如果存在从类型S到类型T的预定义的隐式转换(§6.1),所有用户定义的转换(隐式的或显式的)都将被忽略。
如果存在从类型S到类型T的预定义的显式转换,那么任何用户定义的从类型S到类型T的显式转换都将被忽略。但用户定义的从S到T的隐式转换仍会被考虑。
对于所有类型除了object,由Nullable
void F(int I , Nullable<int> n){ i = n; //错误 i = (int)n; //用户定义的显式转换 n = i; //用户定义的隐式转换 n = (Nullable<int>)i; //用户定义的隐式转换 }
然而,对于类型object,预定义的转换在所有情况隐藏了用户定义转换,除了一种情况:
void F(object o , Nullable<object> n){ o = n; //预定义装箱转换 o= (object)n; //预定义装箱转换 n= o; //用户定义隐式转换 n = (Nullable<object>)o; //预定义取消装箱转换 }
20.1.12泛型类中的嵌套类型
泛型类声明可以包含嵌套类型声明。封闭类的类型参数可以在嵌套类型中使用。嵌套类型声明可以包含附加的类型参数,它只适用于该嵌套类型。class Outer<T> { class Inner<U> { static void F(T t , U u){…} } static void F(T t) { Outer<T>.Inner<string >.F(t,”abc”);//这两个语句有同样的效果 Inner<string>.F(t,”abc”); Outer<int>.Inner<string>.F(3,”abc”); //这个类型是不同的 Outer.Inner<string>.F(t , “abc”); //错误,Outer需要类型参数 } }
尽管这是一种不好的编程风格,但嵌套类型中的类型参数可以隐藏一个成员,或在外部类型中声明的一个类型参数。
class Outer<T> { class Inner<T> //有效,隐藏了 Ouer的 T { public T t; //引用Inner的T } }
20.1.13应用程序入口点
应用程序入口点不能在一个泛型类声明中。
20.2泛型结构声明
像类声明一样,结构声明可以有可选的类型参数。
struct-declaration:(结构声明:) attributes opt struct-modifiers opt struct identifier type-parameter-list opt struct-interfaces opt type-parameter-constraints-clauses opt struct-body ;opt (特性可选 结构修饰符可选 struct 标识符 类型参数列表可选 结构接口可选 类型参数约束语句可选 结构体;可选)
除了§11.3中为结构声明而指出的差别之外,泛型类声明的规则也适用于泛型结构声明。
20.3泛型接口声明
接口也可以定义可选的类型参数
interface-declaration:(接口声明:) attribute opt interface-modifiers opt interface indentifier type-parameter-list opt interface-base opt type-parameter-constraints-clause opt interface-body; (特性可选 接口修饰符可选 interface 标识符 类型参数列表可选 基接口可选 类型参数约束语句可选 接口体;可选) 使用类型参数声明的接口是一个泛型接口声明。除了所指出的那些,泛型接口声明遵循和常规结构声明相同的规则。
在接口声明中的每个类型参数在接口的声明空间定义了一个名字。在一个接口上的类型参数的作用域包括基接口、类型约束语句和接口体。在其作用域之内,一个类型参数可以被用作一个类型。应用到接口上的类型参数和应用到类(§20.1.1)上的类型参数具有相同的限制。
在泛型接口中的方法与泛型类(§20.1.8)中的方法遵循相同的重载规则。
20.3.1实现接口的唯一性
由泛型类型声明实现的接口必须为所有可能的构造类型保留唯一性。没有这条规则,将不可能为特定的构造类型确定调用正确的方法。例如,假定一个泛型类声明允许如下写法。
interface I<T> { void F(); } class X<U, V>:I<U>,I<V> //错误,I<U>和I<V>冲突 { void I<U>.F(){…} void I<V>.F(){…} }
如果允许这么写,那么下面的情形将无法确定执行那段代码。
I<int> x = new X<int ,int>(); x.F();
为了确定一个泛型类型声明的接口列表是有效的,可以按下面的步骤进行。
让L成为在泛型类、结构或接口声明 C中指定的接口的列表。
将任何已经在L中的接口的基接口添加到L
从L中删除任何重复的接口
在类型实参被替换到L后,如果任何从C创建的可能构造类型,导致在L中的两个接口是同一的,那么C的声明是无效的。当确定所有可能的构造类型时,约束声明不予考虑。
在类声明X之上,接口列表L由I和I
20.3.2显式接口成员实现
使用构造接口类型的显式接口成员实现本质上与简单接口类型方式上是相同的。和以往一样,显式接口成员实现必须由一个指明哪个接口被实现的接口类型而限定。该类型可能是一个简单接口或构造接口,如下例子所示。
interface IList<T> { T[] GetElement(); } interface IDictionary<K,V> { V this[K key]; Void Add(K key , V value); } class List<T>:IList<T>,IDictionary<int , T> { T[] IList<T>.GetElement(){…} T IDictionary<int , T>.this[int index]{…} void IDictionary<int , T>.Add(int index , T value){…} }
[1] 也就是在类型参数被替换成类型实参时,有可能替换后的实参导致出现两个成员使用相同的名字和签名。
以上就是C#2.0 Specification(泛型二)的内容,更多相关内容请关注PHP中文网(www.php.cn)!