.Net泛型类的成员
泛型类的所有成员都可以直接或作为构造类型的一部分使用任何包容类 (enclosing class) 中的类型形参。当在运行时使用特定的封闭构造类型时,所出现的每个类型形参都被替换成为该构造类型提供的实际类型实参。例如:
- class C﹤V﹥
- {
- public V f1;
- public C﹤V﹥ f2 = null;
- public C(V x) {
- this.f1 = x;
- this.f2 = this;
- }
- }
- class Application
- {
- static void Main() {
- C﹤int﹥ x1 = new C﹤int﹥(1);
- Console.WriteLine(x1.f1); // Prints 1
- C﹤double﹥ x2 = new C﹤double﹥(3.1415);
- Console.WriteLine(x2.f1); // Prints 3.1415
- }
- }
在实例函数成员中,类型 this 是包含这些成员的声明的实例类型。
除了使用类型形参作为类型以外,泛型类声明中的成员与非泛型类的成员遵循相同的规则。下面几小节将讨论适用于特定种类的成员的附加规则。
.Net泛型类中的静态字段
泛型类声明中的静态变量在相同封闭构造类型的所有实例之间共享,但是不会在不同封闭构造类型的实例之间共享。不管静态变量的类型是否涉及任何类型形参,这些规则都适用。
例如:
- class C﹤V﹥
- {
- static int count = 0;
- public C() {
- count++;
- }
- public static int Count {
- get { return count; }
- }
- }
- class Application
- {
- static void Main() {
- C﹤int﹥ x1 = new C﹤int﹥();
- Console.WriteLine(C﹤int﹥.Count); // Prints 1
- C﹤double﹥ x2 = new C﹤double﹥();
- Console.WriteLine(C﹤int﹥.Count); // Prints 1
- C﹤int﹥ x3 = new C﹤int﹥();
- Console.WriteLine(C﹤int﹥.Count); // Prints 2
- }
- }
.Net泛型类中的静态构造函数
泛型类中的静态构造函数用于初始化静态字段,并为从该泛型类声明创建的每个不同封闭构造类型执行其他初始化操作。泛型类型声明的类型形参处于作用域中,并且可在静态构造函数的函数体中使用。
新的封闭构造类类型在第一次发生下列任一情况时进行初始化:
·创建该封闭构造类型的实例。
·引用该封闭构造类型的任何静态成员。
为了初始化新的封闭构造类类型,需要先为该特定的封闭构造类型创建一组新的静态字段。将其中的每个静态字段初始化为默认值。下一步,为这些静态字段执行静态字段初始值设定项。最后,执行静态构造函数。
由于静态构造函数只为每个封闭构造类类型执行一次,因此对于无法通过约束在编译时进行检查的类型形参来说,此处是进行运行时检查的方便位置。例如,下面的类型使用静态构造函数检查类型实参是否为一个枚举:
- class Gen﹤T﹥ where T: struct
- {
- static Gen() {
- if (!typeof(T).IsEnum) {
- throw new ArgumentException("T must be an enum");
- }
- }
- }
.Net泛型类之访问受保护成员
在泛型类声明中,通过从该泛型类构造的任何类类型的实例,可以对继承的受保护实例成员进行访问。具体而言,指定的用于访问 protected 和 protected internal 实例成员的规则通过下面针对泛型的规则进行了扩充:
在泛型类 G 中,如果 E 的类型是从 G 构造的类类型或从 G 构造的类类型继承的类类型,则使用 E.M 形式的 primary-expression 访问继承的受保护实例成员 M 是允许的。
在下面的示例中
- 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 = default(T);
- di.x = 123;
- ds.x = "test";
- }
- }
对 x 的三个赋值是允许的,因为它们全都通过从该泛型类型构造的类类型的实例进行。
.Net泛型类中的重载
泛型类声明中的方法、构造函数、索引器和运算符可以被重载。虽然声明的签名必须唯一,但是在替换类型实参时可能会导致出现完全相同的签名。在这样的情况下,重载解析的附加规则将挑选最明确的
成员。
下面的示例根据此规则演示有效和无效的重载:
- interface I1﹤T﹥ {}
- interface I2﹤T﹥ {}
- class G1﹤U﹥
- {
- int F1(U u); // Overload resulotion for G﹤int﹥.F1
- int F1(int i); // will pick non-generic
- void F2(I1﹤U﹥ a); // Valid overload
- void F2(I2﹤U﹥ a);
- }
- class G2﹤U,V﹥
- {
- void F3(U u, V v);// Valid, but overload resolution for
- void F3(V v, U u);// G2﹤int,int﹥.F3 will fail
- void F4(U u, I1﹤V﹥ v); // Valid, but overload resolution for
- void F4(I1﹤V﹥ v, U u);// G2﹤I1﹤int﹥,int﹥.F4 will fail
- void F5(U u1, I1﹤V﹥ v2); // Valid overload
- void F5(V v1, U u2);
- void F6(ref U u); // valid overload
- void F6(out V v);
- }
形参数组方法和类型形参
可以在形参数组的类型中使用类型形参。例如,给定下面的声明
- 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"} );
.Net泛型类之重写和泛型类
和往常一样,泛型类中的函数成员可以重写基类中的函数成员。在确定被重写的基成员时,必须通过替换类型实参来确定基类的成员。一旦确定了基类的成员,重写规则就与非泛型类
相同。
下面的示例演示重写规则如何在存在泛型的情况下起作用:
- 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) {} // Error, should be 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) {} // Error, should be C﹤U﹥
- }
.Net泛型类中的运算符
泛型类声明可以定义运算符,所遵循的规则与非泛型类声明相同。运算符声明中使用类声明的实例类型的方式必须与运算符的正常使用规则类似,具体如下:
·一元运算符必须以该实例类型的单个参数为操作对象。一元的 ++ 和 -- 运算符必须返回该实例类型或从该实例类型派生的类型。
·二元运算符的参数中必须至少有一个属于该实例类型。
·转换运算符的形参类型或返回类型必须属于该实例类型。
下面演示泛型类中的有效运算符声明的一些示例:
- 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 的转换运算符,在应用指定的规则时,与 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﹤string﹥(D﹤T﹥ value) {} // Ok
- public static implicit operator C﹤T﹥(D﹤T﹥ value) {} // Error
- }
前两个运算符声明是允许的,T 和 int 以及 string 分别被视为没有关系的唯一类型。但是,第三个运算符是错误的,因为 C﹤T﹥ 是 D﹤T﹥ 的基类。
对于某些类型实参,可以声明这样的运算符,即这些运算符指定了已经作为预定义转换而存在的转换。在下面的示例中
- struct Convertible﹤T﹥
- {
- public static implicit operator Convertible﹤T﹥(T value) {}
- public static explicit operator T(Convertible﹤T﹥ value) {}
- }
当把类型 object 指定为 T 的类型实参时,第二个运算符将声明一个已经存在的转换(存在从任何类型到类型 object 的隐式转换,因此也存在显式转换)。
在两个类型之间存在预定义转换的情况下,这些类型之间的任何用户定义的转换将被忽略。具体而言:
·如果存在从类型 S 到类型T 的预定义隐式转换,则从S 到T 的所有用户定义的转换(隐式或显式)将被忽略。
·如果存在从类型S 到类型T 的预定义显式转换,则从 S 到T 的所有用户定义的显式转换将被忽略。但是,仍然会考虑从 S 到 T 的用户定义的隐式转换。
对于除 object 以外的所有类型,上面的 Convertible﹤T﹥ 类型声明的运算符都不会与预定义的转换发生冲突。例如:
- void F(int i, Convertible﹤int﹥ n) {
- i = n; // Error
- i = (int)n; // User-defined explicit conversion
- n = i; // User-defined implicit conversion
- n = (Convertible﹤int﹥)i; // User-defined implicit conversion
- }
但是对于类型 object,除了下面这个特例之外,预定义的转换将在其他所有情况下隐藏用户定义的
转换:
- void F(object o, Convertible﹤object﹥ n) {
- o = n; // Pre-defined boxing conversion
- o = (object)n; // Pre-defined boxing conversion
- n = o; // User-defined implicit conversion
- n = (Convertible﹤object﹥)o; // Pre-defined unboxing conversion
- }
.Net泛型类中的嵌套类型
泛型类声明可以包含嵌套的类型声明。包容类的类型形参可以在嵌套类型中使用。嵌套类型声明可以包含仅适用于该嵌套类型的附加类型形参。
泛型类声明中包含的每个类型声明都隐式地是泛型类型声明。在编写对嵌套在泛型类型中的类型的引用时,必须指定其包容构造类型(包括其类型实参)。但是可在外层类中不加限定地使用嵌套类型;在构造嵌套类型时可以隐式地使用外层类的实例类型。下面的示例演示三种不同的引用从 Inner 创建的构造类型的正确方法;前两种方法是等效的:
- class Outer﹤T﹥
- {
- class Inner﹤U﹥
- {
- public static void F(T t, U u) {}
- }
- static void F(T t) {
- Outer﹤T﹥.Inner﹤string﹥.F(t, "abc"); // These two statements have
- Inner﹤string﹥.F(t, "abc"); // the same effect
- Outer﹤int﹥.Inner﹤string﹥.F(3, "abc"); // This type is different
- Outer.Inner﹤string﹥.F(t, "abc");// Error, Outer needs type arg
- }
- }
嵌套类型中的类型形参可以隐藏外层类型中声明的成员或类型形参,但这是一种不好的编程风格:
- class Outer﹤T﹥
- {
- class Inner﹤T﹥ // Valid, hides Outer’s T
- {
- public T t; // Refers to Inner’s T
- }
- }