.Net自从2.0版本开始就支持泛型。使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
性能
var list = new ArrayList();
list.Add(20);//装箱--将值类型转换为引用类型
int num = (int)list[0];//拆箱--将引用类型转换为值类型
foreach (int i in list)//拆箱
{
Console.WriteLine(i);
}
ArrayList存储对象,所以Add方法会进行装箱操作,当读取ArrayList中的值时,需要将其转换为int类型,需要进行拆箱操作。装箱、拆箱操作很容易使用,但是性能损失较大,特别是遍历许多项时。
此时,我们可以使用List<T>泛型类来减少装箱和拆箱操作,类型参数T定义为int类型
var list = new List<int>();
list.Add(20);//不进行装箱操作
int num = list[0];//不进行拆箱操作
foreach (int i in list)//不进行拆箱操作
{
Console.WriteLine(i);
}
类型安全
1 var list = new ArrayList();
2 list.Add(20);
3 list.Add("abc");
4 foreach (int i in list)//集合中并非所有元素都可以强制转化为int类型,会出现一个运行异常
5 {
6 Console.WriteLine(i);
7 }
泛型类List<T>中,参数类型T可以定义允许使用的数据类型
1 var list = new List<int>();//参数类型T设置为int类型,只允许对int类型的数值进行操作
2 list.Add(20);
3 list.Add("abc");//传入参数数据类型与设置的参数类型不符,会导致错误
代码复用性
我们看如下代码,为了实现对不同数据类型的数组进行遍历,我们需要为每种类型都编写一个方法:
1 class Generic
2 {
3 public void Main()
4 {
5 int[] arr_int = { 1, 2, 3 };
6 string[] arr_string = { "abc", "123" };
7 char[] arr_char = { 'a', 'b', 'c' };
8 PrintArray(arr_int);
9 PrintArray(arr_string);
10 PrintArray(arr_char);
11
12 Console.ReadKey();
13 }
14 //遍历int类型的数组
15 void PrintArray(int[] arr)
16 {
17 foreach (int i in arr)
18 {
19 Console.WriteLine(i);
20 }
21 }
22 //遍历string类型的数组
23 void PrintArray(string[] arr)
24 {
25 foreach (string str in arr)
26 {
27 Console.WriteLine(str);
28 }
29 }
30 //遍历char类型的数组
31 void PrintArray(char[] arr)
32 {
33 foreach (char ch in arr)
34 {
35 Console.WriteLine(ch);
36 }
37 }
38 }
但是如果我们使用泛型的话,代码就简单多了
1 class Generic
2 {
3 public void Main()
4 {
5 int[] arr_int = { 1, 2, 3 };
6 string[] arr_string = { "abc", "123" };
7 char[] arr_char = { 'a', 'b', 'c' };
8 //完整的写法
9 PrintArray<int>(arr_int);
10 PrintArray<string>(arr_string);
11 PrintArray<char>(arr_char);
12 //简便写法
13 PrintArray(arr_int);
14 PrintArray(arr_string);
15 PrintArray(arr_char);
16
17 Console.ReadKey();
18 }
19 //遍历数组
20 void PrintArray<T>(T[] arr)
21 {
22 foreach (T item in arr)
23 {
24 Console.WriteLine(item);
25 }
26 }
27 }
命名约定
如果在程序中使用了泛型,遵循泛型的命名规则可以帮助我们区分泛型类型和非泛型类型。
泛型类型的命名规则:
- 泛型类型的名称用字母T作为前缀;
1 class TClassName<T>
2 {
3 //...
4 }
- 如果没有特殊的要求,泛型类型允许用任意的类型代替,并且只使用了一个泛型类型,就可以用T作为泛型类型的名称;
- 如果泛型类型有特定的要求的(例如,他必须实现一个接口或派生自基类),或者使用了两个或多个泛型类型,就应该为泛型类型使用描述性的名称
1 class TClassName<TParam1, Tparam2>
2 {
3 //...
4 }
泛型类的默认值
我们看下边的代码,我们需要将变量value进行初始化,要求如果value是引用类型就初始化为null,如果value是值类型就初始化为0;但是泛型中并不能确定T的类型是值类型或是引用类型,那我们要如何进行初始化呢?为了解决这个问题,我们可以是会用default关键字。default关键字会根据T的类型进行初始化,如果T为引用类型就将其设置为null,如果T为值类型就将其设置为0。
1 public T Test<T>()
2 {
3 T value = default(T);
4 //..Do Something
5 return value;
6 }
约束
约束 | 说明 |
where T:struct | 指定类型T必须是值类型 |
where T:class | 指定类型T必须是引用类型 |
where T:IFoo | 指定类型T必须实现接口IFoo |
where T:Foo | 指定类型T必须派生自基类Foo |
where T:new() | 构造函数约束,指定类型T必须有一个默认的无参构造函数(构造函数只能对无参的构造函数进行约束) |
where T1:T2 | 类型T1派生自泛型类型T2 |