感谢腾讯课堂软谋教育的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"); } }
泛型补充: 完整代码分享
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static MyGeneric.Model; namespace MyGeneric { //1 引入泛型:延迟声明 //2 如何声明和使用泛型 //3 泛型的好处和原理 //4 泛型类、泛型方法、泛型接口、泛型委托 //5 泛型约束 class Program { public static void Main(string[] args) { //出现时间: 泛型的引入是从.Net FrameWork 2.0开始 //出现作用: //泛型为.Net框架引入了类型参数的概念,类型参数的概念使得实际类或方法时,不必确定一个或多个参数 //其具体参数类型可以在调用时去指定 //在早期我们需要为不同的参数类型去写不同的方法 CommonMenthod cmd = new CommonMenthod(); //cmd.ShowInt(123); //cmd.ShowString("Hello"); //又或者使用Object,但是使用Object就会涉及到拆箱装箱的问题,会影响到效率 //cmd.ShowObject(123);//所有父类出现的地方都能用子类代替,Object是一切类型的父类 //cmd.ShowObject("Hello"); //泛型之后我们可以用一个泛型方法满足不同的需求 //cmd.ShowGeneric(123);//如果可以根据参数推算出类型,可以省略类型 //cmd.ShowGeneric("Hello"); //哪里用泛型? 泛型到底是干嘛的? //泛型方法:为了一个方法满足不同的类型的需求 //泛型类:一个类,满足不同类型的需求 如:List Dictionary //泛型接口:一个接口,满足不同类型的需求 //泛型委托:一个委托,满足不同类型的需求 //为什么要有泛型约束: //泛型参数有哪些: //where T: struct 类型参数必须为值类型。 //where T : class 类型参数必须为引用类型。 //where T : new() 类型参数必须有一个公有、无参的构造函数。当于其它约束联合使用时,new() 约束必须放在最后。 //where T : <base class name> 类型参数必须是指定的基类型或是派生自指定的基类型。 //where T : <interface name> 类型参数必须是指定的接口或是指定接口的实现。可以指定多个接口约束。接口约束也可以是泛型的。 People people = new People() { Id = 123, Name="张三" }; Chinese chinese = new Chinese() { Id=345, Name="李小龙" }; Hubei hubei = new Hubei() { Id=456, Name="老王" }; Japanese japanese = new Japanese() { Id=567, Name="矢野浩二" }; //没有约束,任何类型都能传递进来,所以可能不安全,也不够灵活 // GenericConstraint.ShowObject(people); //这里传入japanese在运行时候会报错,因为japanese并没有继承People // GenericConstraint.ShowObject(japanese); //GenericConstraint.Show(people); GenericConstraint.Show(chinese); //WebServices WCF 都不能用泛型,为什么? //跨语言的,别的语言也能用,不支持泛型。。 //服务在发布的时候是必须确定的,泛型在编译时确定不了 } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { public class CommonMenthod { public void ShowInt(int iParameter) { Console.WriteLine(iParameter); } public void ShowString(string sParameter) { Console.WriteLine(sParameter); } public void ShowDatetime(DateTime dtParameter) { Console.WriteLine(dtParameter); } public void ShowObject(object oParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMenthod), oParameter.GetType().Name, oParameter); } /// <summary> /// 泛型为什么可以支持任何类型 /// 因为T不知道是什么类型,在使用的时候才能确定 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tParameter"></param> public void ShowGeneric<T>(T tParameter) { Console.WriteLine(tParameter); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static MyGeneric.Model; namespace MyGeneric { public class GenericConstraint { /// <summary> /// 泛型约束 /// </summary> /// <param name="oParameter"></param> public static void ShowObject(object oParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericConstraint), oParameter.GetType().Name, oParameter); People people = (People)oParameter; Console.WriteLine($"{people.Id} {people.Name}"); } public static void Show<T>(T tParameter) //where T: People //约束,只要是继承了People的就可以 where T : People, ISports, IWork, new()//这里的是要同时满足几种需求,具体灵活使用 { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericConstraint), tParameter.GetType().Name, tParameter); Console.WriteLine($"{tParameter.Id} {tParameter.Name}"); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { public class Model { public interface ISports { void Pingpang(); } public interface IWork { void Work(); } public class People { public int Id { get; set; } public string Name { get; set; } public void Hi() { } } public class Chinese : People, ISports, IWork { public void Tradition() { Console.WriteLine("仁义礼智信,温良恭俭让"); } public void SayHi() { Console.WriteLine("吃了么?"); } public void Pingpang() { Console.WriteLine("打乒乓球..."); } public void Work() { throw new NotImplementedException(); } } public class Hubei : Chinese { public string Changjiang { get; set; } public void Majiang() { Console.WriteLine("打麻将啦。。"); } } public class Japanese : ISports { public int Id { get; set; } public string Name { get; set; } public void Pingpang() { Console.WriteLine("打乒乓球..."); } } } }