# 泛型 ## 什么是泛型 泛型是一种程序设计语言的特性。泛型是2.0版本C#语言和公共语言运行库(CLR)中新增的一个非常重要的特性。 我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样。但我们没有办法,只能分别写多个方法来处理不同的数据类型。这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的,可以看出,微软还是很贴心的。 泛型是允许延迟编写类或方法中编程元素的数据类型的规范,直到实际在程序中使用它的时候。即,泛型允许你编写一个可以与任何数据类型一起工作的类或方法。 ## 为什么要使用泛型 上面简单说了下泛型的作用,还是不太具体,有点抽象,我们通过下面几段代码来看下为什么要使用泛型。代码如下: ```csharp public class GenericClass { public void ShowInt(int n) { Console.WriteLine("ShowInt print {0},ShowInt Parament Type Is {1}",n,n.GetType()); } public void ShowDateTime(DateTime dt) { Console.WriteLine("ShowDateTime print {0},ShowDateTime Parament Type Is {1}", dt, dt.GetType()); } public void ShowPeople(People people) { Console.WriteLine("ShowPeople print {0},ShowPeople Parament Type Is {1}", people, people.GetType()); } } static void Main(string[] args) { GenericClass generice = new GenericClass(); generice.ShowInt(11); generice.ShowDateTime(DateTime.Now); generice.ShowPeople(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); } ``` 我们可以看出这三个方法,除了传入的参数不同外,其里面实现的功能都是一样的。在1.1版的时候,还没有泛型这个概念,那么怎么办呢。就有人想到了OOP三大特性之一的继承,我们知道,C#语言中,所有类型都源自同一个类型,那就是object。 ```csharp public class GenericClass { public void ShowObj(object obj) { Console.WriteLine("ShowObj print {0},ShowObj Parament Type Is {1}", obj, obj.GetType()); } } static void Main(string[] args) { Console.WriteLine("*****************object调用*********************"); generice.ShowObj(11); generice.ShowObj(DateTime.Now); generice.ShowObj(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); } ``` 我们可以看出,目地是达到了。解决了代码的可读性,但是这样又有个不好的地方了,我们这样做实际上是一个装箱拆箱操作,会损耗性能。 终于,微软在2.0的时候发布了泛型。接下来我们用泛型方法来实现该功能。 ```csharp public class GenericClass { public void ShowT(T t) { Console.WriteLine("ShowObj print {0},ShowObj Parament Type Is {1}", t, t.GetType()); } } static void Main(string[] args) { Console.WriteLine("*****************object调用*********************"); generice.ShowT(11); generice.ShowT(DateTime.Now); generice.ShowT(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); } ``` 在我们调用泛型方法,编译器进行编译时,会确定传入的参数的类型,从而生成副本方法。这个副本方法与原始方法一法,所以不会有装箱拆箱操作,也就没有损耗性能这回事了。 下面我们一起学习下泛型。 ## 类型参数 什么是类型参数呢?`GenericList`中的T就是类型参数,说白了就是在定义泛型对象时的类型占位符。 `GenericList`在具体使用无法按照原样使用,因为T不是一个具体的类型.在使用的时候T必须替换为具体的类型,例如`GenericList`。 ## 泛型类 泛型类用于封装不特定于特定数据类型的操作。泛型类最常见用法是用于链接列表、哈希表、堆栈、队列和树等集合,无论存储什么类型的数据,添加和删除项等操作方式基本相同。 语法: ```csharp public class Generic { public T t; public Generic() { } } ``` 通常,创建泛型类从现有具体类开始,然后每次逐个讲类型改为类型参数,直到泛化和可用性达到最佳平衡。 创建泛型类时需要注意以下几点: - 要将哪些类型泛化为类型参数。 - 可参数化的类型越多,代码就越灵活,其可重用性就越高 - 过度泛化会加大其他程序人员阅读代码的难度 - 要将何种约束(如果有)应用到类型参数。 ## 泛型接口 泛型接口与泛型类类似,只不过没有具体的实现。 语法: ```csharp interface IMyGenericInterface { } ``` 泛型类和接口都可以定义多个类型参数,例如: ```csharp interface IMyGenericInterface { } ``` 具体类可以实现封闭式构造接口,例如 ```csharp interface IBaseInterface { } //泛型继承 class SampleClass : IBaseInterface { } //具体实现继承 class SampleClass : IBaseInterface { }//如果T有约束,那么string类型必须得满足T的约束 ``` ## 泛型方法 泛型方法指的就是方法定义中的某些参数的类型不是具体类型,而是类型参数,语法如下: ```csharp public class GenericClass { public void ShowT(T t) { Console.WriteLine("ShowT print {0},ShowT Parament Type Is {1}", t, t.GetType()); } } static void Main(string[] args) { Console.WriteLine("*****************泛型方法调用*********************"); generice.ShowT(11); generice.ShowT(DateTime.Now); generice.ShowT(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); } } ``` ## 泛型约束 定义泛型类时,可以对代码能够在实例化类时用于类型参数的几种类型施加限制。 如果客户端代码尝试使用约束所不允许的类型来实例化类,则会产生编译时错误。 这些限制称为约束。 通过使用 where 上下文关键字指定约束。 下表列出了六种类型的约束: |约束|说明| |-|-| |where T:struct|类型参数必须是值类型,可以指定除Nullable以外的任意值类型| |where T:class|类型参数必须是引用类型;这同样适用于所有类、接口、委托或数组类型| |where T:new()|类型参数必须有公共无参构造函数。与其他约束一起使用时,new()约束必须最后指定| |where T:<基类名称>|类型参数必须为指定的基类或者派生自指定的基类| |where T:<接口名称>|类型参数必须是指定的接口或者实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型| |where T:U|为T提供的类型参数必须是为U提供的参数,或者是派生自为U提供的参数| where T:struct ```csharp class MyClass where U : struct///约束U参数必须为“值 类型” { } public void MyMetod(T t) where T : struct { } ``` where T:Class ```csharp class MyClass where U : class///约束U参数必须为“引用类型” { } public void MyMetod(T t) where T : class { } ``` where T:new() ```csharp class EmployeeList where T : Employee, IEmployee, System.IComparable, new() { // ... } ``` where T:<基类名> ```csharp public class Employee{} public class GenericList where T : Employee ``` where T:<接口名称> ```csharp /// /// 接口 /// interface IMyInterface { } /// /// 定义的一个字典类型 /// /// /// class Dictionary where TKey : IComparable, IEnumerable where TVal : IMyInterface { public void Add(TKey key, TVal val) { } } ``` where T:U ```csharp class List { void Add(List items) where U : T {/*...*/} } ``` 以上就是对六种泛型的简单示例,当然泛型约束不仅仅适用于类,接口,对于泛型方法,泛型委托都同样适用 ## 泛型委托 委托可以定义它自己的类型参数。 引用泛型委托的代码可以指定类型参数以创建封闭式构造类型,就像实例化泛型类或调用泛型方法一样,如以下示例中所示: ```csharp class Program8 { static void Main(string[] args) { Del m1 = new Del(Notify); m1.Invoke(1111); Del m2 = new Del(Notify); m2.Invoke("字符串"); Console.ReadKey(); } public delegate void Del(T item); public static void Notify(T t) { Console.WriteLine("{0} type is {1}", t, t.GetType()); } } ``` ## 泛型代码中的默认关键字:Default 在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型 T: - T 是引用类型还是值类型。 - 如果 T 为值类型,则它是数值还是结构。 给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。解决方案是使用 default 关键字,此关键字对于引用类型会返回空,对于数值类型会返回零。对于结构,此关键字将返回初始化为零或空的每个结构成员,具体取决于这些结构是值类型还是引用类型。 ```csharp namespace MyGeneric { class Program { static void Main(string[] args) { object obj1=GenericToDefault(); object obj2 = GenericToDefault(); object obj3 = GenericToDefault(); Console.ReadKey(); } public static T GenericToDefault() { return default(T); } } public struct StructDemo { public int Id { get; set; } public string Name { get; set; } } } ```