泛型类的成员
泛型类的所有成员都可以使用封闭类中的类型参数,不论是直接地使用还是作为构造类型的一部分。当在运行时使用特定的封闭构造类型时,类型参数的每次使用都由构造类型所提供的实际类型实参所代替。
例如:
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); //打印1
}
}
在实例函数成员之内,this的类型就是声明的实例类型。
除了使用类型参数作为类型之外,在泛型类声明中的成员也遵循和非泛型成员相同的规则。
泛型类中的静态字段
在一个泛型类声明中的静态变量,在相同封闭构造类型的所有实例中被共享,但在不同封闭构造类型的实例中,是不被共享的。不管静态变量的类型包含哪种类型参数,这些规则都适用。
例如:
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); //打印1
C<double> x2=new C<double>();
Console.WriteLine(C<double>.Count); //打印 1
C<int> x3=new C<int>();
Console.WriteLine(C<int>.Count); //打印 2
}
}
泛型类中的静态构造函数
泛型类中的静态构造函数用于初始化静态字段,为每个从特定泛型类声明中创建的不同的封闭构造类型,执行其他初始化。泛型类型声明的类型参数在作用域之内,可以在静态构造函数体内被使用。
如果下列情形之一发生,则一个新的封闭构造类类型将被首次初始化。
1、 一个封闭构造类型的实例被创建;
2、 封闭构造类型的任何静态成员被引用
为了初始化一个新的封闭构造类类型,首先那个特定封闭类型的一组新静态字段将会被创建。每个静态字段都被初始化为其默认值。接着,将为这些静态字段而执行静态字段初始化器。最后将执行静态构造函数。
由于静态构造函数将为每个封闭构造类类型执行一次,因此对于无法编译时通过的约束进行的检查来说,在运行时去实施运行时检查将会很方便。
在泛型类中重载
在一个泛型类声明中的方法、构造函数、索引器和运算符可以被重载。但为了避免在构造类中出现歧义,这些重载是受约束的。在同一个泛型类声明中使用相同的名称声明的两个函数成员必须具有这样的参数类型,也就是封闭构造类型中不能出现两个成员使用相同的名称和签名。当考虑所有可能的封闭构造类型时,这条规则包含了在当前程序中目前不存在的类型实参,但它仍然是可能出现的。在类型参数上的类型约束由于这条规则的目的而被忽略了。下面的例子根据这条规则展示了有效的和无效的重载:
interface 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 C3 <U,V>where U:C1 where V:C2
{
void F9(U u); //无效重载,当检查重载时,在U和V上的约束将被忽略
void F9(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”});
重写和泛型类
和往常一样,在泛型类中的函数成员可重写基类中的函数成员。如果基类是一个非泛型类型或封闭构造类型,那么任何重写函数成员不能有包含类型参数的组成类型。然而,如果 一个基类是一个开放构造类型,那么重写函数成员可以使用其声明中的类型参数。当重写基类成员时,基类成员必须通过替换类型实参来确定。一旦基类的成员被确定,则用于重写的规则和非泛型类一样。
下面示例演示了对于现有的泛型其重写规则是如何工作的:
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>
}
泛型类中的运算符
泛型类声明可以定义运算符,它遵循与常规类相同的规则。类声明的实例类型必须以一种类似运算符的常规规则的方式,在运算符声明中被使用:
1、 一元运算符必须接受一个实例类型的单一参数。一元运算符“++”和“--”必须返回实例类型。
2、 二元运算符的参数至少有一个必须是实例类型
3、 转换运算符的参数类型和返回类型必须是实例类
下面在泛型类中展示了几个有效的运算符声明的例子:
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<T>(D<T> value){…} //错误
}
第一个运算符声明是允许的,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可以是隐式的,也可以是显式的转换)。
在两个类型之间存在预定义转换的情形下,在这些类型上的所有用户定义的转换都将被忽略。尤其是:
1、 如果存在从类型S到类型T的预定义的隐式转换,那么所有用户定义的转换(隐式的或显式的)都将被忽略。
2、 如果存在从类型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; //预定义取消装箱转换
}