1、泛型安全
假设我要写个公用的输出传入参数的方法(不用泛型),由于万物皆对象的理由,我先定义一个方法show(object obj),以下面所示:架构
public static void Show(object obj) { Console.WriteLine(obj.ToString()); }
执行这个方法ide
int i = 1; //装箱 Show(i);
若是传入的是值类型,值类型转换为引用类型,咱们知道会发生装箱,这是对性能的损害,想一想若是是个集合,就得屡次执行装箱、拆箱操做。如ArrayList类,ArrayList储存对象,Add()方法定义为须要把一个对象做为参数,若是传入的值类型,就得装箱,在读取ArrayList中的值时,又得进行拆箱,以下面代码所示:函数
var list = new ArrayList(); list.Add(1); //装箱 foreach (int i in list) { Console.WriteLine(i); //拆箱 }
若是使用泛型,就不会出现这样的问题了,咱们使用List<T>类来改造上面代码:性能
var list = new List<int>(); list.Add(1); foreach (int i in list) { Console.WriteLine(i); }
这里就不存在装箱和拆箱了,因此咱们在使用集合的时候,尽可能使用泛型集合,不要使用非泛型集合。ui
2、类型安全spa
在上面ArrayList类中,添加参数时,能够添加任何对象,好比上面的例子,若是在添加整数类型后再添加引用类型,这么作在编译时是没有任何问题,可是在foreach语句使用整数类型迭代的时候就会报错。code
var list = new ArrayList(); list.Add(1); //装箱 list.Add("string"); foreach (int i in list) { Console.WriteLine(i); }
这时候就会报InvalidCastException的异常。对象
若是使用泛型集合List<T>的时候去重写上面的代码,在编译的时候就会报错。因此这个地方咱们就能知道,泛型是在编译时就已经执行了,因此系统运行时咱们时没有装箱拆箱的系统开销,而非泛型是在运行时执行的,因此可能致使异常发生;blog
3、建立泛型类和泛型方法
泛型方法,从我最早第一个例子Show(object) ,采用泛型来重写,定义为Show<T>(T);
public static void Show<T>(T obj) { Console.WriteLine(obj.ToString()); }
泛型类,如public class List<T>{}
3.1 命名约定
public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e);
public delegate TOutput Convert<TInput,TOutput>(TInput input);
public class SortedList<TKey,TValue>{};
3.2 默认值
在泛型类和泛型方法中产生的一个问题是,在预先未知如下状况时,如何将默认值分配给参数化类型 T,给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。 解决方案是使用 default 关键字,此关键字对于引用类型会返回 null,对于数值类型会返回零。 对于结构,此关键字将返回初始化为零或 null 的每一个结构成员。
使用方式如:T obj=default(T);
3.3 约束
在定义泛型类时,能够对客户端代码可以在实例化类时用于类型参数的类型种类施加限制。 若是客户端代码尝试使用某个约束所不容许的类型来实例化类,则会产生编译时错误。 这些限制称为约束。 约束是使用where上下文关键字指定的。 下表列出了六种类型的约束:
约束 | 说明 |
where T:struct | 对于结构的约束,类型T必须是值类型。 |
where T:class | 类的约束,类型T必须是应用类型。 |
where T:<接口名称> | 类型参数必须是指定的接口或实现指定的接口。 能够指定多个接口约束。 约束接口也能够是泛型的。 |
where T:<基类名> | 类型参数必须是指定的基类或派生自指定的基类。 |
where T:new() | 类型参数必须具备无参数的公共构造函数。 当与其余约束一块儿使用时,new() 约束必须最后指定。 |
where T1:T2 | 类型T1必须是类型T2或派生自泛型类型T2,该约束也称为裸型约束。 |
public class MyClass<T> where T : IComparer<T>, new() { }
上面代码,使用泛型类型添加了两个约束,声明指定类型T必须实现了IComparer接口,且必须有一个默认构造函数
public class MyClass<TOutput, TInput> where TOutput : IComparer<TOutput>, new() where TInput:class,TOutput { }
上面代码用了两个泛型类型,TOutput必须实现了IComparer接口,且必须有一个默认构造函数,TInput必须是引用类型,且类型必须是TOutput或派生自TOutput。
3.4 继承
泛型类型能够实现泛型接口,也能够派生自一个类。泛型类型能够派生自泛型基类,其要求必须重复接口的泛型类型,或者必须指定基类的类型。以下列所示:
public class BaseClass<T> { } ///必须重复接口\基类的泛型类型 public class MyClass<T> : BaseClass<T> { }
public class BaseClass<T> { } ///必须指定基类的类型 public class MyClass<T> : BaseClass<String> { }
派生类能够是泛型类或非泛型类,例如定义一个抽象的泛型基类,它在派生类中用一个具体的类型实现,以下列所示:
public abstract class Calcu<T> { public abstract T Add(T x, T y); public abstract T Sub(T x, T y); } /// <summary> /// 派生类中具体的类型实现 /// </summary> public class IntCalcu : Calcu<int> { public override int Add(int x, int y) { return x + y; } public override int Sub(int x, int y) { return x - y; } }
4、结语
这些泛型类和泛型方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。 例如,经过使用泛型类型参数 T,您能够编写其余客户端代码可以使用的单个类,而不致引入运行时强制转换或装箱操做的成本或风险。在架构中有句话是让一切能延迟的延迟。