C#2.0 Specification(泛型二)
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<T>是D<T>的基类。
给定先前的例子,为某些类型实参声明运算符,指定已经作为预定义转换而存在的转换是可能的。
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<T>类型声明的运算符都不会与预定义的转换冲突。例如
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泛型类中的嵌套类型
泛型类声明可以包含嵌套类型声明。封闭类的类型参数可以在嵌套类型中使用。嵌套类型声明可以包含附加的类型参数,它只适用于该嵌套类型。包含在泛型类声明中的每个类型声明是隐式的泛型类型声明。当编写一个嵌套在泛型类型内的类型的引用时,包含构造类型,包括它的类型实参,必须被命名。然而,在外部类中,内部类型可以被无限制的使用;当构造一个内部类型时,外部类的实例类型可以被隐式地使用。下面的例子展示了三个不同的引用从Inner创建的构造类型的方法,它们都是正确的;前两个是等价的。
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<U>和I<V>组成。该声明是无效的,因为任何使用相同类型U和V的构造类型,将导致这两个接口是同一的。
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)!
上一篇: 微信拍了拍是什么意思?