阅读目录
开始阅读
在微软的官方文档中这样来描述泛型,泛型将类型参数的概念引入 .NET,这样就可设计具有以下特征的类和方法:在客户端代码声明并初始化这些类或方法之前,这些类或方法会延迟指定一个或多个类型。 我个人的理解是泛型就是在声明的时候泛指一些类型、方法、接口和委托,需要在调用的时候指明。实际应用中解决的问题是把一些不同参数类型参与的重复工作,使用泛型实现来减少代码量。
本节会分别介绍泛型类、泛型方法、泛型接口和泛型委托的声明和使用,还会介绍泛型的常用约束及参数out、in修饰符。
1 //声明泛型类 2 public class GenericClass<T> 3 { 4 public void Show(T tPara) 5 { 6 Console.WriteLine($"Type:{typeof(T)},Value:{tPara}"); 7 } 8 }
1 //调用泛型类 2 static void Main(string[] args) 3 { 4 GenericClass<int> genericClass1 = new GenericClass<int>(); 5 genericClass1.Show(12345); 6 7 GenericClass<string> genericClass2 = new GenericClass<string>(); 8 genericClass2.Show("我爱你中国"); 9 10 Console.ReadKey(); 11 }
1 //泛型类继承泛型类 2 public class GenericStudent<T>: GenericClass<T> 3 { 4 } 5 6 //非泛型类继承泛型类 7 public class Student:GenericClass<string> 8 { 9 }
1 //官方继承例子 2 class BaseNode { } 3 class BaseNodeGeneric<T> { } 4 5 // concrete type 6 class NodeConcrete<T> : BaseNode { } 7 8 //closed constructed type 9 class NodeClosed<T> : BaseNodeGeneric<int> { } 10 11 //open constructed type 12 class NodeOpen<T> : BaseNodeGeneric<T> { } 13 14 15 //No error 16 class Node1 : BaseNodeGeneric<int> { } 17 18 //Generates an error 19 //class Node2 : BaseNodeGeneric<T> {} 20 21 //Generates an error 22 //class Node3 : T {} 23 24 25 class BaseNodeMultiple<T, U> { } 26 27 //No error 28 class Node4<T> : BaseNodeMultiple<T, int> { } 29 30 //No error 31 class Node5<T, U> : BaseNodeMultiple<T, U> { } 32 33 //Generates an error 34 //class Node6<T> : BaseNodeMultiple<T, U> {}
1 //声明泛型方法 2 public class GenericMothed 3 { 4 public void ShowInt(int iPara) 5 { 6 Console.WriteLine($"type:{typeof(int)},value:{iPara}"); 7 } 8 9 public void ShowString(string sPara) 10 { 11 Console.WriteLine($"type:{typeof(string)},value:{sPara}"); 12 } 13 14 public void ShowPara<T>(T tPara) 15 { 16 Console.WriteLine($"type:{typeof(T)},value:{tPara}"); 17 } 18 }
1 //调用泛型方法 2 static void Main(string[] args) 3 { 4 GenericMothed genericMothed = new GenericMothed(); 5 //调用普通方法 6 genericMothed.ShowInt(123); 7 genericMothed.ShowString("我爱你中国"); 8 //调用泛型方法 9 genericMothed.ShowPara<int>(123); 10 genericMothed.ShowPara<string>("我爱你中国"); 11 Console.ReadKey(); 12 }
官方文档建议泛型接口和泛型类结合使用,这样可以避免值类型的装箱和拆箱操作。.NET5框架里的泛型接口都定义在命名空间System.Collections.Generic。
1 //自定义声明泛型接口 2 public interface GenericInterface<T> 3 { 4 }
1 //非泛型类继承自定义泛型接口 2 public class Person: GenericInterface<string> 3 { 4 } 5 6 //泛型类继承自定义泛型接口 7 public class Person<T>: GenericInterface<T> 8 { 9 } 10 11 //继承官方的接口 12 class Stack<T> : IEnumerable<T> 13 { 14 public IEnumerator<T> GetEnumerator() 15 { 16 throw new NotImplementedException(); 17 } 18 19 IEnumerator IEnumerable.GetEnumerator() 20 { 21 throw new NotImplementedException(); 22 } 23 }
官方解释委托可以定义它自己的类型参数。 引用泛型委托的代码可以指定类型参数以创建封闭式构造类型,就像实例化泛型类或调用泛型方法一样。 我个人的理解就是委托使用泛型参数就是泛型委托。
1 //声明调用泛型委托 2 public class GenericDelegate 3 { 4 delegate void Show<T>(T tPara); 5 public void Test() 6 { 7 Show<string> showT = Sing; 8 showT.Invoke("我爱你中国"); 9 } 10 public void Sing<T>(T tPara) 11 { 12 } 13 }
官方解释约束告知编译器类型参数必须具备的功能。 在没有任何约束的情况下,类型参数可以是任何类型。 编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。 如果客户端代码使用不满足约束的类型,编译器将发出错误。 通过使用 where 上下文关键字指定约束。
1 //单个泛型约束 2 public class GenericConstraints<T> where T : struct 3 { 4 } 5 6 //多个泛型约束 7 public class GenericConstraints<T, K> 8 where T : struct 9 where K : class 10 { 11 }
先看看in修饰符官方解释对于泛型类型参数,in 关键字可指定类型参数是逆变的。逆变使你使用的类型可以比泛型参数指定的类型派生程度更小。 这样可以隐式转换实现协变接口的类以及隐式转换委托类型。 引用类型支持泛型类型参数中的协变和逆变,但值类型不支持它们。仅在类型定义方法参数的类型,而不是方法返回类型时,类型可以在泛型接口或委托中声明为逆变。
1 // 逆变接口 2 interface IContravariant<in A> { } 3 4 // 扩展逆变接口 5 interface IExtContravariant<in A> : IContravariant<A> { } 6 7 // 实现逆变接口 8 class Sample<A> : IContravariant<A> { } 9 10 //使用逆变接口 11 class Program 12 { 13 static void Test() 14 { 15 IContravariant<Object> iobj = new Sample<Object>(); 16 IContravariant<String> istr = new Sample<String>(); 17 18 // You can assign iobj to istr because 19 // the IContravariant interface is contravariant. 20 istr = iobj; 21 } 22 }
1 // 逆变委托 2 public delegate void DContravariant<in A>(A argument); 3 4 // 与委托签名匹配的方法 5 public static void SampleControl(Control control) 6 { } 7 public static void SampleButton(Button button) 8 { } 9 10 // 使用逆变委托 11 public void Test() 12 { 13 14 // Instantiating the delegates with the methods. 15 DContravariant<Control> dControl = SampleControl; 16 DContravariant<Button> dButton = SampleButton; 17 18 // You can assign dControl to dButton 19 // because the DContravariant delegate is contravariant. 20 dButton = dControl; 21 22 // Invoke the delegate. 23 dButton(new Button()); 24 }
再来看看out修饰符官方解释对于泛型类型参数,out 关键字可指定类型参数是协变的。协变使你使用的类型可以比泛型参数指定的类型派生程度更大。 这样可以隐式转换实现协变接口的类以及隐式转换委托类型。 引用类型支持协变和逆变,但值类型不支持它们。具有协变类型参数的接口使其方法返回的类型可以比类型参数指定的类型派生程度更大。
1 // 协变泛型接口 2 interface ICovariant<out R> { } 3 4 // 扩展协变泛型接口 5 interface IExtCovariant<out R> : ICovariant<R> { } 6 7 // 实现协变泛型接口 8 class Sample<R> : ICovariant<R> { } 9 10 // 使用协变泛型接口 11 class Program 12 { 13 static void Test() 14 { 15 ICovariant<Object> iobj = new Sample<Object>(); 16 ICovariant<String> istr = new Sample<String>(); 17 18 // You can assign istr to iobj because 19 // the ICovariant interface is covariant. 20 iobj = istr; 21 } 22 }
1 // 协变泛型委托 2 public delegate R DCovariant<out R>(); 3 4 // 与委托签名匹配的方法 5 public static Control SampleControl() 6 { return new Control(); } 7 8 public static Button SampleButton() 9 { return new Button(); } 10 11 // 使用协变泛型委托 12 public void Test() 13 { 14 // Instantiate the delegates with the methods. 15 DCovariant<Control> dControl = SampleControl; 16 DCovariant<Button> dButton = SampleButton; 17 18 // You can assign dButton to dControl 19 // because the DCovariant delegate is covariant. 20 dControl = dButton; 21 22 // Invoke the delegate. 23 dControl(); 24 }
由于泛型的不同泛型类型都会生成一个副本,我们就可以利用这个原理实现一个简单的内存缓存。为了使全局只有一个实例,可以把缓存的字段设置为静态的。
1 //定义泛型缓存类 2 public class GenericCache<T> where T:class 3 { 4 private static readonly List<T> _list = new List<T>(); 5 public static void Add(T tPara) 6 { 7 _list.Add(tPara); 8 } 9 public static void Remove(T tPara) 10 { 11 if(_list.Contains(tPara)) 12 { 13 _list.Remove(tPara); 14 } 15 } 16 public static List<T> GetAll() 17 { 18 return _list; 19 } 20 }
1 //定义泛型缓存用的模型 2 public class Demo 3 { 4 public int Id { get; set; } 5 public string Name { get; set; } 6 }
1 //使用泛型缓存类 2 static void Main(string[] args) 3 { 4 GenericCache<string>.Add("我爱你中国"); 5 GenericCache<string>.Add("春眠不觉晓"); 6 GenericCache<string>.Add("处处闻啼鸟"); 7 GenericCache<string>.Add("我爱你中国"); 8 9 GenericCache<Demo>.Add(new Demo { Name = "C#" }); 10 GenericCache<Demo>.Add(new Demo { Name = "JAVA" }); 11 GenericCache<Demo>.Add(new Demo { Name = "Python" }); 12 Console.WriteLine("*****打印字符串缓存数据*****"); 13 foreach (var item in GenericCache<string>.GetAll()) 14 { 15 Console.WriteLine(item); 16 } 17 Console.WriteLine("*****打印自定义类缓存数据*****"); 18 foreach (var item in GenericCache<Demo>.GetAll()) 19 { 20 Console.WriteLine(item.Name); 21 } 22 23 Console.ReadKey(); 24 }
泛型 - C# 编程指南 https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/
泛型类 - C# 编程指南 https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/generic-classes
泛型方法 - C# 编程指南 https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/generic-methods
泛型接口 - C# 编程指南 https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/generic-interfaces
泛型委托 - C# 编程指南 https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/generic-delegates
where(泛型类型约束)- C# 参考 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/where-generic-type-constraint
类型参数的约束(C# 编程指南) https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters
out 关键字(泛型修饰符) - C# 参考 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/out-generic-modifier
in(泛型修饰符) - C# 参考 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/in-generic-modifier
泛型中的协变和逆变 https://docs.microsoft.com/zh-cn/dotnet/standard/generics/covariance-and-contravariance