感谢腾讯课堂软谋教育的Eleven老师对于泛型的详细讲解。
PS:如有不足之处,还望大家多多指教,万分感谢。
泛型概念
泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性。泛型为.NET框架引入了类型参数(type parameters)的概念。类型参数使得设计类和方法时,不必确定一个或多个具体参数,其的具体参数可延迟到客户代码中声明、实现。
泛型类和泛型方法兼复用性、类型安全和高效率于一身,是与之对应的非泛型的类和方法所不及。泛型广泛用于容器(collections)和对容器操作的方法中。.NET框架2.0的类库提供一个新的命名空间System.Collections.Generic,其中包含了一些新的基于泛型的容器类。要查找新的泛型容器类(collection classes)的示例代码,请参见基础类库中的泛型。当然,你也可以创建自己的泛型类和方法,以提供你自己的泛化的方案和设计模式,这是类型安全且高效的。
泛型的声明和使用
泛型类的定义
访问修饰符 class ClassName<T>{}
示例:public class GenericClass<T>{}
泛型方法的定义
访问修饰符 返回值类型 methodName(参数类型/参数类型列表){}
示例:
publice T GenericMethod<T,M>(){}//声明一个带返回值(泛型)的方法,有N个泛型参数
调用时需要注意:必须指定类型参数(泛型方法可以不知道是什么类型,但是调用者必须知道是什么类型)
.GenericMethod<int,string>(tempInt,tempString);//必须和声明时的个数保持一致,<>里的类型和()的参数的类型必须是一致。
泛型接口的定义
申明接口的关键字 interfaceName<T.....>{ 返回值类型 methodName(参数类型 参数名称);}
interface interfaceName<T>{ void method(T s1)}//这里举例用泛型
继承
类:接口名称<这里需要指定具体类型>//例如string.....
Class :interfaceName<string>//快捷实现接口的method方法
publice void method<T>(T s1){//方法体}
调用
实例化Class.method<string>("嘿嘿");//调用时必须指定类型和具体值.
泛型委托的定义
访问修饰符 delegate 返回类型 delegateName<T>(T t1......);//这里用返回值(泛型)、带参数(泛型)举例
public delegate T DelegateName<T>(T t1);//声明委托,这里用返回值(泛型)、带参数(泛型)举例
//定义委托方法
publice T tempMethod<T>(T t1){ return t1;}//这里省略,只是简单返回值
//在方法内部实例化委托
{
//语法糖,可以把原来的=new DelegateNmae<string>(tempMethod<string>)省略
DelegateName<string> tempName=tempMethod<string>;//实例化委托的时候必须给定具体类型
tempName.Invoke("测试");//传递具体参数
}
类型参数的约束
为什么要有约束? 因为有约束才有权利
约束使得泛型类能够使用其他实例的属性,因为所有为类型T的元素,都是一个对象或是一个继承自Employee的对象。
public class Employee { public class Employee { private string name; private int id; public Employee(string s, int i) { name = s; id = i; } public string Name { get { return name; } set { name = value; } } public int ID { get { return id; } set { id = value; } } } }
class MyList<T> where T: Employee { //Rest of class as before. public T FindFirstOccurrence(string s) { T t = null; Reset(); while (HasItems()) { if (current != null) { //The constraint enables this: if (current.Data.Name == s) { t = current.Data; break; } else { current = current.Next; } } //end if } // end while return t; } }
泛型约束的几种常见类型:
约束 | 描述 |
where T: struct | 类型参数必须为值类型。 |
where T : class | 类型参数必须为引用类型。 |
where T : new() | 类型参数必须有一个公有、无参的构造函数。当于其它约束联合使用时,new()约束必须放在最后。 |
where T : <base class name> | 类型参数必须是指定的基类型或是派生自指定的基类型。 |
where T : <interface name> | 类型参数必须是指定的接口或是指定接口的实现。可以指定多个接口约束。接口约束也可以是泛型的。 |
针对早期版本的通用语言运行时和C#语言的局限,泛型提供了一个解决方案。以前类型的泛化(generalization)是靠类型与全局基类System.Object的相互转换来实现。.NET框架基础类库的ArrayList容器类,就是这种局限的一个例子。ArrayList是一个很方便的容器类,使用中无需更改就可以存储任何引用类型或值类型。
//The .NET Framework 1.1 way of creating a list ArrayList list1 = new ArrayList(); list1.Add(3); list1.Add(105); //... ArrayList list2 = new ArrayList(); list2.Add(“It is raining in Redmond.”); list2.Add("It is snowing in the mountains."); //...
但是这种便利是有代价的,这需要把任何一个加入ArrayList的引用类型或值类型都隐式地向上转换成System.Object。如果这些元素是值类型,那么当加入到列表中时,它们必须被装箱;当重新取回它们时,要拆箱。类型转换和装箱、拆箱的操作都降低了性能;在必须迭代(iterate)大容器的情况下,装箱和拆箱的影响可能十分显著。
另一个局限是缺乏编译时的类型检查,当一个ArrayList把任何类型都转换为Object,就无法在编译时预防客户代码类似这样的操作:
ArrayList list = new ArrayList(); //Okay. list.Add(3); //Okay, but did you really want to do this? list.Add(.“It is raining in Redmond.”); int t = 0; //This causes an InvalidCastException to be returned. foreach(int x in list) { t += x; }
下面的示例代码以一个简单的泛型链表类作为示范。(多数情况下,推荐使用由.NET框架类库提供的List<T>类,而不是创建自己的表。)类型参数T在多处使用,具体类型通常在这些地方来指明表中元素的类型。类型参数T有以下几种用法:
在AddHead方法中,作为方法参数的类型。
在公共方法GetNext中,以及嵌套类Node的 Data属性中作为返回值的类型。
在嵌套类中,作为私有成员data的类型。
注意一点,T对嵌套的类Node也是有效的。当用一个具体类来实现MyList<T>时——如MyList<int>——每个出现过的T都要用int代替。
using System; using System.Collections.Generic; public class MyList<T> //type parameter T in angle brackets { private Node head; // The nested type is also generic on T. private class Node { private Node next; //T as private member data type: private T data; //T used in non-generic constructor: public Node(T t) { next = null; data = t; } public Node Next { get { return next; } set { next = value; } } //T as return type of property: public T Data { get { return data; } set { data = value; } } } public MyList() { head = null; } //T as method parameter type: public void AddHead(T t) { Node n = new Node(t); n.Next = head; head = n; } public IEnumerator<T> GetEnumerator() { Node current = head; while (current != null) { yield return current.Data; current = current.Next; } } }
下面的示例代码演示了客户代码如何使用泛型类MyList<T>,来创建一个整数表。通过简单地改变参数的类型,很容易改写下面的代码,以创建字符串或其他自定义类型的表。
class Program { static void Main(string[] args) { //int is the type argument. MyList<int> list = new MyList<int>(); for (int x = 0; x < 10; x++) list.AddHead(x); foreach (int i in list) { Console.WriteLine(i); } Console.WriteLine("Done"); } }