집 >백엔드 개발 >C#.Net 튜토리얼 >C#2.0 사양(제네릭 2)
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 내부 인스턴스 멤버에 액세스하기 위한 규칙은 다음 규칙을 사용하여 제네릭에 대해 확장됩니다.
일반 클래스 G에서 상속된 보호 인스턴스 멤버 M의 경우 E.M을 사용하는 기본 표현식이 허용됩니다. 단, E 유형은 G에서 생성된 클래스 유형이거나 A 클래스 유형에서 상속됩니다. 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被认为是没有关系的唯一类型。然而,第二个运算符是一个错误,因为C8742468051c85b06f0a0af9e3e506b5c是D8742468051c85b06f0a0af9e3e506b5c的基类。
给定先前的例子,为某些类型实参声明运算符,指定已经作为预定义转换而存在的转换是可能的。
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,由Nullable8742468051c85b06f0a0af9e3e506b5c类型声明的运算符都不会与预定义的转换冲突。例如
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由I8f4f974c37a8b771235386e098482ce3和Id94943c0b4933ad8cac500132f64757f组成。该声明是无效的,因为任何使用相同类型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)!