12.2 泛型基础结构
12.2.1 开放类型和封闭类型
具有泛型类型参数的类型仍然是类型,CLR同样会为他创建内部的类型对象。
然而具有泛型类型参数的类型称为开放类型,CLR禁制构造开放类型的任何实例。类似于CLR禁止构造接口类型的实例。
代码引用泛型类时可指定一组泛型类型实参。为所有类型参数都传递了实际的数据类型,类型就成为封闭类型。
CLR允许构造封闭类型的实例。然而代码引用泛型类型的时候,可能留下一些泛型类型实参未指定。
这会在CLR中创建新的开放类型对象,而且不能创建该类型的实例:
internal sealed class DictionaryStringKey<Tvalue> : Dictionary<String, Tvalue> { } public static class Program{ public static void Main(string[] args){ Object o = null; //1.Dictionary<,>是开放类型 Type t = typeof(Dictionary<,>); //创建实例,失败 o = CreateInstance(t); //2.DictionaryStringKey<>是开放类型,有一个类型参数 t = typeof(DictionaryStringKey<>); //创建实例,失败 o = CreateInstance(t); //3.DictionaryStringKey<Guid>是封闭类型 t = typeof(DictionaryStringKey<Guid>); //创建实例,成功 o = CreateInstance(t); } private static Object CreateInstance(Type t){ Object o = null; try{ o = Activator.CreateInstance(t); Console.WriteLine($"已创建{t.ToString()}的实例"); } catch(ArgumentException e){ Console.WriteLine(e.Message); } return o; } }
封闭类型静态构造器的作用
每个封闭类型都有自己的静态字段。换言之,假如List<T>定义了任何静态字段,这些字段不会在一个List<DateTime>或List<String>之间共享。
每个封闭类型对象都有自己的每个封闭类型,这样构造器都会执行一次。
泛型类型定义静态构造器的目的是保证传递的类型实参满足特定条件。
例如我们可以像下面这样定义只能处理枚举类型的泛型类型:
internal sealed class GenericTypeThatRequiresAnEnum{ static GenericTypeThatRequiresAnEnum(){ if(!typeof(T).IsEnum){ throw new ArgumentException("T must be an enumerated type"); } } }
CLR提供了名为约束的功能,可以更好地制定有效的类型实参。遗憾的是约束无法将类型实参限制为仅枚举类型。
所以上例需要用静态构造器来保证类型是一个枚举类型。
12.2.2 泛型类型和继承
泛型类型仍然是类型,所以能从其他任何类型派生。
使用泛型类型并指定类型实参时,实际是在CLR中定义一个新的类型对象,新的类型对象从泛型类型派生自的那个类型派生。
换言之,由于List<T>从Object派生,所有List<String>等也称Object派生。
指定类型实参不影响继承层次结构。
假定下面这样定义一个链表节点类:
internal sealed class Node<T>{ public T m_data; public Node<T> m_next; public Node(T data):this(data,null){} public Node(T data,Node<T> next){ m_data=data; m_next=next; } public override String ToString(){ return m_data.ToString()+((m_next!=null)?m_next.ToString():String.Empty); } }
那么可以写代码来构造链表:
private static void SameDataLinkedList(){ Node<Char> head=new Node<Char>('C'); head=new Node<Char>('B',head); head=new Node<char>('A',head); Console.WriteLine(head.ToString);//显示"ABC" }
泛型类继承非泛型基类
在上面这个Node类中,对于m_next字段引用的另一个节点来说,其m_data字段必须包含相同的数据类型。
所以更好的办法是定义非泛型Node基类,再定义非泛型TypedNode类继承Node基类。
这样就可以创建一个链表,其中每个结点都可以是一种具体的数据类型,除了不能是Object。
同时获得编译时的类型安全性,并防止值类型装箱。下面是新的类型定义:
internal class Node{ public Node<T> m_next; public Node(Node<T> next){ m_next=next; } } internal sealed class TypedNode<T>:Node{ public T m_data; public Node(T data):this(data,null){} public Node(T data,Node<T> next){ m_data=data; } public override String ToString(){ return m_data.ToString()+((m_next!=null)?m_next.ToString():String.Empty); } }
现在可以写代码创建一个链表,其中每个结点都是不同的数据类型。
private static void DifferentDataLinkedList(){ Node head=new TypedNode<char>('.'); head=new TypedNode<DateTime>(DateTime.Now,head); head=new TypedNode<String>("Today is",head); Console.WriteLine(head.ToString()); }
12.2.3 泛型类型同一性
错误的简化
为了简化下面这样的代码:
List<DataTime> dt1=new Lis<DateTime>();
一些开发人员可能首先定义下面这样的类:
internal sealed class DataTimeList:List<DateTime>{/*无需放入任何代码*/}
然后就可以简化创建列表的代码了:
DateTimeList dt1=new DateTimeList();
这样虽然方便了,但是绝对不要单纯出于增强源码可读性的目的来定义一个新类。
这样会丧失同一性identity和相等性equivalence,如下所示:
Boolean sameType=(typeof(List<DateTime>)==typeof(DateTimeList));
上述代码运行时,sameType会被初始化为false,因为比较的是两个不同的类型的对象。
也意味着如果方法的原型接受一个DateTimeList就不可以将一个DList<DateTime>传给它。
然而如果方法的原型接受一个List<DateTime>,可以将一个DateTimeList传给他,因为后者从前者派生。使人糊涂。
使用using简化语法
C#允许使用简化的语法来引用泛型封闭类型,同时不会影响类型的相等性,其要求在源文件顶部使用传统using指令:
using DateTimeList=System.Collection.Generic.List<System.DateTime>;
using指令实际定义的是名为DateTimeList的符号。
编译时会将所有DateTimeList替换成System.Collection.Generic.List<System.DateTime>。
这样类型的同一性和相等性得到了维持。
此外可以利用C#的隐式类型局部变量功能,让便一起根据表达式的类型来推断方法的局部变量的类型。