泛型类型参数
在泛型类型或方法中,类型参数是客户端在实例化泛型类型的变量时,指定的特定类型的占位符。
若要使用GenericList<T>,客户端代码必须通过指定尖括号中的类型参数来声明和实例化构造类型。此特定类的类型参数可以是编译器识别的任何类型。可以创建任意数目的构造类型实例,每个实例使用不同的类型参数。
MyGenericList<float> list2 = new MyGenericList<float>(); MyGenericList<int> list3 = new MyGenericList<int>();
每个MyGenericList<T>实例中,类中出现每个T都会在运行时替代为相应的类型参数。通过替代这种方式,我们使用一个类定义创建了2个独立的类型安全的有效对象。
类型参数命名准则
务必使用描述性名称命名泛型类型参数,除非单个字母名称完全可以让人了解它表示的含义,而描述性名称不会有更多的意义。
public interface ISessionChannel<TSession> { } public delegate TOutput Convert<TInput, TOutput>(TInput from); public class List<T> { }
考虑使用T作为具有单个字母类型参数的类型的类型参数名
public delegate boolPredicate<T>(T item);
务必将T作为描述性类型参数名的前缀
public interface ISessionChannel<TSession> { TSession Session { get; } }
考虑在参数名中指示对此类型参数的约束。例如,可以将带有ISession约束的参数命名为TSession。
类型参数的约束
在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用Where上下问关键字指定的。
约束 | 说明 |
T:结构 | 类型参数必须是值类型。可以指定除Nullable以外的任何值类型。 |
T:类 | 类型参数必须是引用类型,包括任何类、接口、委托或者数组类型。 |
T:new() | 类型参数必须具有无参书的公共构造函数。当与其他约束一起使用时,new()约束必须最后指定。 |
T:<基类名> | 类型参数必须是指定的基类或者派生指定的基类。 |
T:<接口名称> | 类型参数必须是指定的接口或者实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。 |
T:U | 为T提供的类型参数必须是U提供的参数或者派生自为U提供的参数。这称为裸类型约束 |
使用约束的原因
如果要检查泛型列表中的某项以确认它是否有效,或者将它与其他某项进行比较,则编译器必须在一定程度上保证它需要调用的运算符或方法将受到客户端代码可能指定的任何类型参数的支持。这种保证是通过对泛型类定义应该一个或多个约束约束获得的。例如,基类约束告诉编译器:仅此类型的对象或从此类型派生的对象才可用作类型参数。一旦编译器有了这个保证,它就能够允许在泛型类中调用该类型的方法。约束是使用上下问关键字where应用的。
class Employee { private string name; private int id; public Employee(string s, int i) { name = s; id = i; } public string Name { get { return name; } set { name = value; } } public int ID { get { return id; } set { id = value; } } } public class GenericList<T> where T : Employee { private class Node { private Node next; private T data; public Node(T t) { next = null; data = t; } public Node Next { get { return next; } set { next = value; } } public T Data { get { return data; } set { data = value; } } } private Node head; public GenericList() { head = null; } public void AddHead(T t) { Node n = new Node(t); n.Next = head; head = n; } public IEnumerator<T> GetEnumerator() { Node current = head; while (current != null) { yield return current.Data; current = current.Next; } } public T FindFirstOccurrence(string s) { Node current = head; T t = null; while (current != null) { // 约束使得泛型类能够使用Employee.Name属性 //因为类型为T的所有项都保证是Employee对象或从Employee继承的对象。 // the constraint enables access to the Name property if (current.Data.Name == s) { t = current.Data; break; } else { current = current.Next; } } return t; } }
可以对同一类型参数应用多个约束,并且约束自身可以是泛型类型
class EmployeeList<T> where T : Employee, System.IComparable<T>, new() { }
通过约束类型参数,可以增加约束类型及其继承层次结构中的所有类型所支持的允许操作和方法调用的数量。因此,在设计泛型类或方法时,如果要对泛型成员执行除简单赋值之外的任何操作或者调用System.Object不支持的任何方法,您将需要对该类型应用约束。
在应该应用where T:class 约束时,建议不要对类型参数使用== 和!=运算符,因为这些运算符测试引用同一性而不测试值相等。(==和!=运算符比较的是引用,而Equals比较的是值)即使在用作参数的类型中重载这些运算符也是如此。
static void Main(string[] args) { string s1 = "foo"; System.Text.StringBuilder sb = new System.Text.StringBuilder("foo"); string s2 = sb.ToString(); Optest<string>(s1, s2); System.Console.ReadKey(); } public static void Optest<T>(T s, T t) where T : class { System.Console.WriteLine(s == t);// false System.Console.WriteLine(s.Equals(t));//true System.Console.WriteLine("foo equal foo? {0}", ("foo" == "foo"));//true }
对于此种情况,如果需要测试值相等性,建议的方法时同时应用where T:IComparable<T>约束,并在将用于构造泛型类的任何类中实现该接口。
未绑定的类型参数
没有约束的类型参数(如公共类SampleClass<T>{}中的T)称为未绑定的类型参数。未绑定的类型参数具有以下规则:
- 不能使用!=和==运算符,因为无法保证具体类型参数能支持这些运算符。
可以在它们与System.Object之间回来转换,或将它们显式转换为任何接口类型。
可以将它们与null进行比较。将未绑定的参数与null进行比较时,如果类型参数为值类型,则该比较将始终返回false
裸类型约束
用作约束的泛型类型参数称为裸类型约束。当具有自己的类型参数的成员函数需要将该参数约束为包括类型的类型参数时,裸类型约束很有用。