使用C#的泛型时,在泛型类型的方法或者泛型方法中可能会使用到类型参数的类型的对象的方法、属性或成员,这时候这个类型可能并不存在要使用的方法等。这种情况下就会缺少类型安全。为了改变这种情况,可以对类型参数设置约束。
设置约束的另一个作用是在编辑及编译时,使用约束后可以享受具体类型的智能感知及强类型支持。否则将只是object级的智能感知。这条说起来可能比较抽象,看一下下面这个例子:
如上图所示 ,在不指定类型参数约束的情况下,只可以在object级提供智能感知,包括编译时的类型安全保证。而实现类型约束后:
将获得编辑时的智能感知支持及编译时的强类型保证。不然写成如下这样就会出现编译错误:
(错误示例)
1 class GenericType1<K> where K 2 { 3 public void ShwoParam(K param) 4 { 5 Console.WriteLine(param.TtoString()); 6 } 7 }
泛型类型参数的约束分为3大类:派生约束、构造函数约束和引用/值类型约束。下文将逐一介绍着这几类类型参数约束。类型参数约束的语法是在泛型类型后加一个where,然后在后面书写类型参数约束。如果有一个以上的类型参数,则每个类型参数约束之间使用" "分隔。形如:GenericType1 where TypeConstraint1 GenericType2 where TypeConstraint2 …
派生约束
常见的派生约束是类型参数派生自一个接口,或者一个类,形如:
T:<基类名> 类型参数必须是指定的基类或派生自指定的基类。(只可以指定一个。)
T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。
比较常见的例子:
1 public class MyCache<K, V> 2 where K : IComparable 3 where V : T 4 { 5 }
另一种比较特殊的派生约束的形式是裸类型约束。这种约束形如:
T:U
为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。
裸类型约束 的应用场景如下:
场景1:
1 class List<T> 2 { 3 void Add<U>(List<U> items) where U : T 4 { 5 } 6 }
场景2:
1 public class SampleClass<T, U, V> where T : V 2 { 3 }
构造函数约束
这种约束形如:
T:new()
表示类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。
当你在代码中需要声明一个参数类型的新实例时,将给类型参数添加构造函数约束就很有用,如下代码说明了其使用:
1 class GenericType1<K> where K : new() 2 { 3 //声明构造函数约束后可以使用如下语句 4 K k = new K(); 5 }
引用/值类型约束
使用这种类型的约束可以将类型参数约束为引用类型或值类型。
这种约束形如:
T:结构 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。
T:类 类型参数必须是引用类型,包括任何类、接口、委托或数组类型。
这种类型参数约束最简单,如下:
1 public class GenericClass<T> where T : struct 2 { 3 } 4 5 public class GenericClass<K> where K : class 6 { 7 }
注意:值类型约束不能与引用类型约束、派生约束声明及构造约束声明共同使用。
泛型方法
在说明泛型方法的用途之前首先来看如下实现:
1 public class GenericClass<T> 2 { 3 void PushToStack(Stack<T> stack, params T[] values) 4 { 5 foreach (T value in values) 6 { 7 stack.Push(value); 8 } 9 } 10 }
如果我们只需要在函数范围内使用泛型而非在整个类范围内使用,我们可以使用泛型方法,以上代码可以改下为如下情况:
1 public class GenericClass 2 { 3 void PushToStack<T>(Stack<T> stack, params T[] values) 4 { 5 foreach (T value in values) 6 { 7 stack.Push(value); 8 } 9 } 10 }
这个泛型函数的调用方法如下:
1 GenericClass gc = new GenericClass(); 2 gc.PushToStack<int>(new Stack<int>(), new int[] { 1, 2, 3 });
由于泛型方法可以根据参数的泛型类型推断出函数的泛型类型(类型推断特性),所以可以省略方法的类型参数,如下:
1 GenericClass gc = new GenericClass(); 2 gc.PushToStack(new Stack<int>(), new int[] { 1, 2, 3 });