在开发编程中,咱们常常会遇到功能很是类似的功能模块,只是他们的处理的数据不同,因此咱们会分别采用多个方法来处理不一样的数据类型。可是这个时候,咱们就会想一个问题,有没有办法实现利用同一个方法来传递不一样种类型的参数呢?编程
这个时候,泛型也就因运而生,专门来解决这个问题的。c#
泛型是在C#2.0就推出的一个新语法,由框架升级提供的功能。设计模式
泛型经过参数化类型实如今同一份代码上操做多种数据类型。例如使用泛型的类型参数T,定义一个类Stack
能够用Stack
同时使用泛型类型能够最大限度地重用代码、保护类型安全以及提升性能。安全
能够建立:泛型接口、泛型类、泛型方法、泛型事件和泛型委托框架
泛型类封装不特定于特定数据类型的操做。 泛型类最多见用法是用于连接列表、哈希表、堆栈、队列和树等集合。 不管存储数据的类型如何,添加项和从集合删除项等操做的执行方式基本相同。ide
static void Main(string[] args) { // T是int类型 GenericClass<int> genericInt = new GenericClass<int>(); genericInt._T = 123; // T是string类型 GenericClass<string> genericString = new GenericClass<string>(); genericString._T = "123"; }
新建一个GenericClass类函数
/// <summary> /// 泛型类 /// </summary> /// <typeparam name="T"></typeparam> public class GenericClass<T> { public T _T; }
泛型方法是经过类型参数声明的方法, 解决用一个方法,知足不一样参数类型性能
static void Main(string[] args) { #region 泛型方法 Console.WriteLine("************Generic**************"); int iValue = 123; string sValue = "456"; DateTime dtValue = DateTime.Now; object oValue = "MrValue"; GenericMethod.Show<int>(iValue);//须要指定类型参数 //GenericMethod.Show<string>(iValue);//必须吻合 GenericMethod.Show(iValue);//能省略,自动推算 GenericMethod.Show<string>(sValue); GenericMethod.Show<DateTime>(dtValue); GenericMethod.Show<object>(oValue); #endregion }
新建一个GenericMethod
/// <summary> /// 泛型方法 /// </summary> public class GenericMethod { /// <summary> /// 2.0推出的新语法 /// 泛型方法解决用一个方法,知足不一样参数类型;作相同的事儿 /// 没有写死参数类型,调用的时候才指定的类型 /// 延迟声明:把参数类型的声明推迟到调用 /// 推迟一切能够推迟的~~ 延迟思想 /// 不是语法糖,而是2.0由框架升级提供的功能 /// 须要编译器支持+JIT支持 /// </summary> /// <typeparam name="T">T/S 不要用关键字 也不要跟别的类型冲突 </typeparam> /// <param name="tParameter"></param> public static void Show<T>(T tParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString()); } }
为泛型集合类或表示集合中的项的泛型类定义接口一般颇有用处。在c#中,经过尖括号“<>”将类型参数括起来,表示泛型。声明泛型接口时,与声明通常接口的惟一区别是增长了一个
泛型接口定义完成以后,就要定义此接口的子类。定义泛型接口的子类有如下两种方法。
(1)直接在子类后声明泛型。
(2)在子类实现的接口中明确的给出泛型类型。
static void Main(string[] args) { #region 泛型接口 CommonInterface commonInterface = new CommonInterface(); commonInterface.GetT("123"); #endregion }
新建GenericInterface.cs类文件
/// <summary> /// 泛型类 /// </summary> /// <typeparam name="T"></typeparam> public class GenericClass<T> { public T _T; } /// <summary> /// 泛型接口 /// </summary> public interface IGenericInterface<T> { //泛型类型的返回值 T GetT(T t); } /// <summary> /// 使用泛型的时候必须指定具体类型, /// 这里的具体类型是int /// </summary> public class CommonClass : GenericClass<int> { } /// <summary> /// 必须指定具体类型 /// </summary> public class CommonInterface : IGenericInterface<string> { public string GetT(string t) { return t; } } /// <summary> /// 子类也是泛型的,继承的时候能够不指定具体类型 /// </summary> /// <typeparam name="T"></typeparam> public class CommonClassChild<T> : GenericClass<T> { }
泛型委托主要是想讲一下Action
Action
Fun
无论是否是泛型委托,只要是委托委托那能用Lamdba表达式,由于无论Lamdba表达式仍是匿名函数其实都是将函数变量化。
下面简单的来作的demo说下两个的用法,这个会了基本linq会了一半了。
static void Main(string[] args) { #region 泛型委托 Action<string> action = s => { Console.WriteLine(s); }; action("i3yuan"); Func<int, int, int> func = (int a, int b) => { return a + b; }; Console.WriteLine("sum:{0}", func(1,1)); Console.ReadLine(); #endregion }
上面其实都是将函数作为变量,这也是委托的思想。action是实例化了一个只有一个字符串参数没有返回值得函数变量。func是实例化了一个有两个int类型的参数返回值为int的函数变量。
能够看到经过Lamdba表达式和泛型的结合,算是又方便了开发者们,更加方便实用。
引入委托经常使用的另外一方式
不管是在类定义内仍是类定义外,委托能够定义本身的类型参数。引用泛型委托的代码能够指定类型参数来建立一个封闭构造类型,这和实例化泛型类或调用泛型方法同样,以下例所示:
public delegate void MyDelegate<T>(T item); public void Notify(int i){} //... MyDelegate<int> m = new MyDelegate<int>(Notify); C#2.0版有个新特性称为方法组转换(method group conversion),具体代理和泛型代理类型均可以使用。用方法组转换能够把上面一行写作简化语法: MyDelegate<int> m = Notify; 在泛型类中定义的委托,能够与类的方法同样地使用泛型类的类型参数。 class Stack<T> { T[] items; int index //... public delegate void StackDelegate(T[] items); } 引用委托的代码必需要指定所在类的类型参数,以下: Stack<float> s = new Stack<float>(); Stack<float>.StackDelegate myDelegate = StackNotify; 泛型委托在定义基于典型设计模式的事件时特别有用。由于sender[JX2] ,而不再用与Object相互转换。 public void StackEventHandler<T,U>(T sender, U eventArgs); class Stack<T> { //… public class StackEventArgs : EventArgs{...} public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent; protected virtual void OnStackChanged(StackEventArgs a) { stackEvent(this, a); } } class MyClass { public static void HandleStackChange<T>(Stack<T> stack, StackEventArgs args){...}; } Stack<double> s = new Stack<double>(); MyClass mc = new MyClass(); s.StackEventHandler += mc.HandleStackChange;
所谓的泛型约束,实际上就是约束的类型T。使T必须遵循必定的规则。好比T必须继承自某个类,或者T必须实现某个接口等等。那么怎么给泛型指定约束?其实也很简单,只须要where关键字,加上约束的条件。
定义一个People类,里面有属性和方法:
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() { Console.WriteLine("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 Hubei(int version) { } 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 Hi() { Console.WriteLine("Hi"); } public void Pingpang() { Console.WriteLine("打乒乓球..."); } }
打印方法
/// <summary> /// 打印个object值 /// 1 object类型是一切类型的父类 /// 2 经过继承,子类拥有父类的一切属性和行为;任何父类出现的地方,均可以用子类来代替 /// object引用类型 加入传个值类型int 会有装箱拆箱 性能损失 /// 类型不安全 /// </summary> /// <param name="oParameter"></param> public static void ShowObject(object oParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(Constraint), oParameter.GetType().Name, oParameter); Console.WriteLine($"{((People)oParameter).Id}_{((People)oParameter).Name}"); }
在main方法中
static void Main(string[] args) { #region Constraint 接口约束 Console.WriteLine("************Constraint*****************"); { People people = new People() { Id = 123, Name = "走本身的路" }; Chinese chinese = new Chinese() { Id = 234, Name = "晴天" }; Hubei hubei = new Hubei(123) { Id = 345, Name = "流年" }; Japanese japanese = new Japanese() { Id = 7654, Name = "i3yuan"// }; CommonMethod.ShowObject(people); CommonMethod.ShowObject(chinese); CommonMethod.ShowObject(hubei); CommonMethod.ShowObject(japanese); Console.ReadLine(); } #endregion }
泛型约束总共有五种。
约束 | 说明 |
---|---|
T:结构 | 类型参数必须是值类型 |
T:类 | 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。 |
T:new() | 类型参数必须具备无参数的公共构造函数。 当与其余约束一块儿使用时,new() 约束必须最后指定。 |
T:<基类名> | 类型参数必须是指定的基类或派生自指定的基类。 |
T:<接口名称> | 类型参数必须是指定的接口或实现指定的接口。 能够指定多个接口约束。 约束接口也能够是泛型的。 |
一、基类约束
上面打印的方法约束T类型必须是People类型。
///
/// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tParameter"></param> public static void Show<T>(T tParameter) where T : People { Console.WriteLine($"{tParameter.Id}_{tParameter.Name}"); tParameter.Hi(); }
注意:
基类约束时,基类不能是密封类,即不能是sealed类。sealed类表示该类不能被继承,在这里用做约束就无任何意义,由于sealed类没有子类。
二、接口约束
/// <summary> /// 接口约束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T Get<T>(T t) where T : ISports { t.Pingpang(); return t; }
三、引用类型约束 class
引用类型约束保证T必定是引用类型的。
/// 引用类型约束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T Get<T>(T t) where T : class { return t; }
四、值类型约束 struct
值类型约束保证T必定是值类型的。
/// 值类型类型约束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T Get<T>(T t) where T : struct { return t; }
五、无参数构造函数约束 new()
/// new()约束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T Get<T>(T t) where T : new() { return t; }
泛型约束也能够同时约束多个,例如:
/// <summary> /// 泛型:不一样的参数类型都能进来;任何类型都能过来,你知道我是谁? /// 没有约束,也就没有自由 /// 泛型约束--基类约束(不能是sealed): /// 1 可使用基类的一切属性方法---权利 /// 2 强制保证T必定是People或者People的子类---义务 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tParameter"></param> public static void Show<T>(T tParameter) where T : People, ISports, IWork, new() { Console.WriteLine($"{tParameter.Id}_{tParameter.Name}"); tParameter.Hi(); tParameter.Pingpang(); tParameter.Work(); }
注意:有多个泛型约束时,new()约束必定是在最后。
public class Animal { public int Id { get; set; } } public class Cat : Animal { public string Name { get; set; } }
static void Main(string[] args) { #region 协变和逆变 // 直接声明Animal类 Animal animal = new Animal(); // 直接声明Cat类 Cat cat = new Cat(); // 声明子类对象指向父类 Animal animal2 = new Cat(); // 声明Animal类的集合 List<Animal> listAnimal = new List<Animal>(); // 声明Cat类的集合 List<Cat> listCat = new List<Cat>(); #endregion }
那么问题来了:下面的一句代码是否是正确的呢?
1 List<Animal> list = new List<Cat>();
可能有人会认为是正确的:由于一只Cat属于Animal,那么一群Cat也应该属于Animal啊。可是实际上这样声明是错误的:由于List
这时就能够用到协变和逆变了。
1 // 协变 2 IEnumerable<Animal> List1 = new List<Animal>(); 3 IEnumerable<Animal> List2 = new List<Cat>();
F12查看定义:
能够看到,在泛型接口的T前面有一个out关键字修饰,并且T只能是返回值类型,不能做为参数类型,这就是协变。使用了协变之后,左边声明的是基类,右边能够声明基类或者基类的子类。
协变除了能够用在接口上面,也能够用在委托上面:
Func<Animal> func = new Func<Cat>(() => null);
除了使用.NET框架定义好的觉得,咱们还能够自定义协变,例如:
/// <summary> /// out 协变 只能是返回结果 /// </summary> /// <typeparam name="T"></typeparam> public interface ICustomerListOut<out T> { T Get(); } public class CustomerListOut<T> : ICustomerListOut<T> { public T Get() { return default(T); } }
使用自定义的协变:
// 使用自定义协变 ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>(); ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();
在来看看逆变。
在泛型接口的T前面有一个In关键字修饰,并且T只能方法参数,不能做为返回值类型,这就是逆变。请看下面的自定义逆变:
/// <summary> /// 逆变 只能是方法参数 /// </summary> /// <typeparam name="T"></typeparam> public interface ICustomerListIn<in T> { void Show(T t); } public class CustomerListIn<T> : ICustomerListIn<T> { public void Show(T t) { } }
使用自定义逆变:
// 使用自定义逆变 ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>(); ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();
协变和逆变也能够同时使用,看看下面的例子:
/// <summary> /// inT 逆变 /// outT 协变 /// </summary> /// <typeparam name="inT"></typeparam> /// <typeparam name="outT"></typeparam> public interface IMyList<in inT, out outT> { void Show(inT t); outT Get(); outT Do(inT t); } public class MyList<T1, T2> : IMyList<T1, T2> { public void Show(T1 t) { Console.WriteLine(t.GetType().Name); } public T2 Get() { Console.WriteLine(typeof(T2).Name); return default(T2); } public T2 Do(T1 t) { Console.WriteLine(t.GetType().Name); Console.WriteLine(typeof(T2).Name); return default(T2); } }
使用:
IMyList<Cat, Animal> myList1 = new MyList<Cat, Animal>(); IMyList<Cat, Animal> myList2 = new MyList<Cat, Cat>();//协变 IMyList<Cat, Animal> myList3 = new MyList<Animal, Animal>();//逆变 IMyList<Cat, Animal> myList4 = new MyList<Animal, Cat>();//逆变+协变
有关可变性的注意事项
在前面咱们学习过,类中的静态类型不管实例化多少次,在内存中只会有一个。静态构造函数只会执行一次。在泛型类中,T类型不一样,每一个不一样的T类型,都会产生一个不一样的副本,因此会产生不一样的静态属性、不一样的静态构造函数,请看下面的例子:
public class GenericCache<T> { static GenericCache() { Console.WriteLine("This is GenericCache 静态构造函数"); _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff")); } private static string _TypeTime = ""; public static string GetCache() { return _TypeTime; } } public class GenericCacheTest { public static void Show() { for (int i = 0; i < 5; i++) { Console.WriteLine(GenericCache<int>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<long>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<DateTime>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<string>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<GenericCacheTest>.GetCache()); Thread.Sleep(10); } } }
Main()方法里面调用:
static void Main(string[] args) { #region 泛型缓存 GenericCacheTest.Show(); #endregion }
结果:
从上面的截图中能够看出,泛型会为不一样的类型都建立一个副本,因此静态构造函数会执行5次。 并且每次静态属性的值都是同样的。利用泛型的这一特性,能够实现缓存。
注意:只能为不一样的类型缓存一次。泛型缓存比字典缓存效率高。泛型缓存不能主动释放。
在泛型类和泛型方法中会出现的一个问题是,如何把缺省值赋给参数化类型,此时没法预先知道如下两点:
T将是值类型仍是引用类型
若是T是值类型,那么T将是数值仍是结构
对于一个参数化类型T的变量t,仅当T是引用类型时,t = null语句才是合法的; t = 0只对数值的有效,而对结构则不行。这个问题的解决办法是用default关键字,它对引用类型返回空,对值类型的数值型返回零。而对于结构,它将返回结构每一个成员,并根据成员是值类型仍是引用类型,返回零或空。下面GenericList
static void Main(string[] args) { #region 泛型代码默认关键字default // 使用非空的整数列表进行测试. GenericList<int> gll = new GenericList<int>(); gll.AddNode(5); gll.AddNode(4); gll.AddNode(3); int intVal = gll.GetLast(); // 下面一行显示5. Console.WriteLine(intVal); // 用一个空的整数列表进行测试. GenericList<int> gll2 = new GenericList<int>(); intVal = gll2.GetLast(); // 下面一行显示0. Console.WriteLine(intVal); // 使用非空字符串列表进行测试. GenericList<string> gll3 = new GenericList<string>(); gll3.AddNode("five"); gll3.AddNode("four"); string sVal = gll3.GetLast(); // 下面一行显示five. Console.WriteLine(sVal); // 使用一个空字符串列表进行测试. GenericList<string> gll4 = new GenericList<string>(); sVal = gll4.GetLast(); // 下面一行显示一条空白行. Console.WriteLine(sVal); #endregion Console.ReadKey(); }
public class GenericList<T> { private class Node { // 每一个节点都有一个指向列表中的下一个节点的引用. public Node Next; // 每一个节点都有一个T类型的值. public T Data; } // 这个列表最初是空的. private Node head = null; // 在列表开始的时候添加一个节点,用t做为它的数据值. public void AddNode(T t) { Node newNode = new Node(); newNode.Next = head; newNode.Data = t; head = newNode; } // 下面的方法返回存储在最后一个节点中的数据值列表. 若是列表是空的, 返回类型T的默认值. public T GetLast() { // 临时变量的值做为方法的值返回. // 下面的声明初始化了临时的温度 // 类型T的默认值. 若是该列表为空返回默认值. T temp = default(T); Node current = head; while (current != null) { temp = current.Data; current = current.Next; } return temp; } }
一般状况下,建议您使用泛型集合,由于这样能够得到类型安全的直接优势而不须要从基集合类型派生并实现类型特定的成员。下面的泛型类型对应于现有的集合类型:
一、List 是对应于 ArrayList 的泛型类。
二、Dictionary 是对应于 Hashtable 的泛型类。
三、Collection 是对应于 CollectionBase 的泛型类。
四、ReadOnlyCollection 是对应于 ReadOnlyCollectionBase 的泛型类。
五、Queue、Stack 和 SortedList 泛型类分别对应于与其同名的非泛型类。
六、LinkedList 是一个通用连接列表,它提供运算复杂度为 O(1) 的插入和移除操做。
七、SortedDictionary 是一个排序的字典,其插入和检索操做的运算复杂度为 O(log n),这使得它成为 SortedList 的十分有用的替代类型。
八、KeyedCollection 是介于列表和字典之间的混合类型,它提供了一种存储包含本身键的对象的方法。
参考 文档 《C#图解教程》
注:搜索关注公众号【DotNet技术谷】--回复【C#图解】,可获取 C#图解教程文件