泛型是 .NET 2.0 中引入的一个新特性,从 .NET 2.0 发布到现在已经过去好多年的时间了,到现在很多公司在面试时都喜欢问诸如用过泛型吗、什么是泛型、怎么写泛型之类的问题。似乎泛型是什么高深莫测的绝学了,犹如辟邪剑法一样,一般人难以运用。其实虽然每个 .NET 程序员的具体工作内容不一样,但是对于 .NET 里的一些基本的东西的运用相差不会很大,我想对于学过C# 的刚毕业的学生也不至于没有用过泛型。下面我就简单说一下泛型。
泛型将类型参数的概念引入了 .NET 中,类型参数使类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。使用泛型可以最大限度地重用代码、保护类型的安全以及提高性能。
泛型类型参数
在泛型类或方法的定义中,类型参数是客户端代码在实例化泛型类型的变量时指定的特定类型的占位符。通常我们使用 T 作为类型参数占位符,但这并不是必须的,我们可以使用一些更有意义的描述性的名称作为类型占位符,如 TInput、TOutput 等。
1 |
public class List<TInput, TOutput> |
类型参数的约束
在定义泛型类型时可以对客户端代码在实例化类时用于类型参数的类型加以限制。如果客户端使用违反约束的类型来实例化类型,则会产生编译时错误。约束使用 where 关键字指定。
结束 |
说明 |
where T: struct |
类型参数必须是值类型 |
where T: class |
类型参数必须是引用类型 |
where T: new() |
类型参数必须有一个 public 且无参数的构造函数 |
where T: <base classname> |
类型参数必须继承至指定的基类(base class) |
where T: <interface name> |
类型参数必须是指定的接口或实现了指定接口的类型 |
where T: U |
为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数 |
03 |
public Student( string name, int age) |
08 |
public string Name { get ; set ; } |
10 |
public int Age { get ; set ; } |
13 |
public class Teacher<T> where T : new () |
15 |
public string Name { get ; set ; } |
16 |
public int Age { get ; set ; } |
17 |
public string Course { get ; set ; } |
上面的代码中我们定义了一个 Student 类和一个 Teacher 类,其中 Student 类只有一个带两个参数的构造函数。Teacher 类是一个泛型类,它的类型参数必须有一个无参的构造函数,如果使用 Student 类去实例化 Teacher 类编辑器会给出错误提示。
使用约束可以使用我们对泛型成员执行操作时变得更加安全。
泛型类
泛型类封装非特定于具体数据类型的操作。泛型类通常用于集合。像从集合上添加、移除项这样的操作大致相同,且与数据类型无关。对于泛型类可以添加多个约束条件。如我们将上面定义的 Teacher<T> 泛型类的泛型类型参数限制为引用类型。注意在使用多个约束时,如果有 new() 约束,new() 必须放在最后面。
1 |
public class Teacher<T> where T : class , new () |
3 |
public string Name { get ; set ; } |
4 |
public int Age { get ; set ; } |
5 |
public string Course { get ; set ; } |
泛型接口
为泛型集合或集合中的项的泛型类指定接口通常很有用。将接口指定为泛型类型参数的约束时,只能使用实现该接口的类型。如下面我们为 Teacher<T> 泛型类的类型参数添加必须实现 IComparable<T> 接口限制条件,这样就只有实现了接口 IComparable<T> 且有一个无参的构造函数的引用类型才能作为实例化 Teacher<T> 时的类型参数。
1 |
public class Teacher<T> where T : class , IComparable<T>, new () |
3 |
public string Name { get ; set ; } |
4 |
public int Age { get ; set ; } |
5 |
public string Course { get ; set ; } |
泛型方法
泛型方法就是使用泛型类型参数声明的方法。如下示例:
1 |
static void Swap<T>( ref T lhs, ref T rhs) |
调用泛型方法的方式如下:
4 |
Swap< int >( ref a, ref b); |
调用泛型方法时也可以省略泛型类型参数,编译器会根据传入参数的类型推断它的类型。上面的调用方式也可写成这样:
泛型方法也可以通过泛型类型参数重载。
2 |
void DoWork<T, U>() { } |
泛型委托
委托也可以定义类型参数,可以像调用泛型类中的泛型方法一样调用泛型委托。例如在 System 命名空间里定义的没有返回值的泛型委托 Action<T>。
1 |
public delegate void Action< in T>(T obj) |
例如 List<T> 的 ForEach( Action<T> action ) 的参数是个泛型委托,我们可以传入 Action<T> 泛型委托实例来对列表中的每个元素执行相同的操作。
02 |
public void TestMethod1() |
04 |
List<String> names = new List<String>(); |
13 |
private void Print( string s) |
使用泛型类型参数时,有一个问题就是由于我们并不知道这个参数是值类型还是引用类型,因此无法设置它的默认值。解决方法是使用 default 关键字,default 关键字对于引用类型会返回null,对于数值类型会返回0,对于结构会返回初始化为0或null的结构成员。