泛型(二)开放类型和封闭类型
在.NET中,CLR会为应用程序使用的每个类型创建一个内部数据结构,这种数据结构成为:类型对象(type Object)。
具有泛型类型参数的类型仍然是类型,CLR同样会为它创建一个内部类型对象。无论是引用类型(类)、值类型(结构)、接口类型,还是委托类型,这一点都是成立的。然而,具有泛型类型参数的类型称之为:开放类型(open type),CLR禁止构造开发类型的任何实例。这一点类似于CLR禁止构造结构类型的实例。
代码引用一个泛型类型时,可指定一组泛型类型实参,假如所有类型实参传递的都是实际数据类型,类型就称为封闭类型(closed type)。CLR允许构造封闭类型的实例,然而,当代码引用一个泛型类型的时候,可能会留下一些泛型类型实参未指定,这会在CLR中创建一个新的开放类型的对象,而且不能创建该类型的实例,以下代码说明了这一点
internal sealed class DictionaryStringKey<TValue> : Dictionary<String, TValue> { } public static class Program { static void Main(string[] args) { Object o = null; //Dictionary<,>是一个开放类型,它有两个类型参数 Type t = typeof(Dictionary<,>); //尝试创建该类型的一个实例失败 o = CreateInstance(t); Console.WriteLine(); //DictionaryStringKey是一个开放类型,他有一个类型参数 t = typeof(DictionaryStringKey<>); //尝试创建该类型的一个实例失败 o = CreateInstance(t); Console.WriteLine(); //DictionaryStringKey<Guid>是一个封闭类型 t = typeof(DictionaryStringKey<Guid>); //尝试创建该类型的一个实例成功 o = CreateInstance(t); //证明它确实能够工作 Console.WriteLine(" 对象类型:"+o.GetType()); } private static Object CreateInstance(Type t) { Object o = null; try { o = Activator.CreateInstance(t); Console.WriteLine("已创建{0}的实例。",t.ToString()); } catch (ArgumentException e) { Console.WriteLine(e.Message); } return o; } }
可以看出,Activator的CreateInstance方法会在试图构造开放类型的实例时跑出了一个ArgumentException异常,注意,在异常的字符串消息中,指明类型中仍然含有一些泛型参数
从输出结构可以看出,类型名是一个以一个“`”字符和一个数字结尾的,这个数字代表类型的元数,也就是类型要求的类型参数的个数,例如Dictionary类的元数 为2它要求为TKey,TValue这两个类型参数指定为具体类型
还要注意的是CLR会在类型对象内部分配类型的静态字段,因此,每个封闭类型都有自己的静态字段,换言之,假如List<T>定义了任何静态字段,这些字段不会再一个List<DateTime>和一个List<String>之间共享,每个封闭类型对象都有它自己的静态字段,另外,假如一个泛型类型定义了一个静态构造器,那么针对每个封闭类型这个构造器都会执行一次,在泛型类型上顶一个静态构造器的目的是保证传递的类型实参满足特定的条件。例如,如果希望一个泛型类型只用于处理枚举类型,就可以这样定义:
inernal sealed class GenericTypeThatRequiresAnEnum<T>{ static GenericTypeThatRequiresAnEnum(){ if(!typeof(T).IsEnum){ throw new ArgumentException("T must be an enumerated type") } } }
CLR提供了一个名为约束的功能,可以利用它更好的定义一个泛型类型来指出哪些实参是有效的,遗憾的是使用约束,无法将类型实参限制为“仅枚举类型”,正式这个原因,所以上列需要用一个静态构造器来保证类型是一个枚举类型。