泛型
.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 |