一、泛型的概念,定义,运行原理,优势
1.泛型的概念
泛型(generic)是C# 2.0推出的新语法,并不是语法糖,它是专门为处理多段代码在不同的数据类型上执行相同的指令的情况而设计的。
即泛型让不同的数据类型支持相同的业务逻辑。
泛型是一个复合类型,把多个类型混合一起作用,比如:方法和泛型混到一起,叫泛型方法,类和泛型混在一起叫泛型类,接口和泛型混到一起,叫泛型接口,等等。
2.泛型该如何定义
泛型定义语法格式:<T>或<T,K,......> 其中T,K指未知类型。
语句:Class Show<T>
使用:Show<类型> show=new Show<类型>();
泛型定义时,是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到了调用的时候才指定参数类型。
3.泛型的运行原理
程序最终会编译成XXX.exe,XXX.exe被点击的时候,会经过JIT的编译,生成二进制代码,才被计算机执行。使用泛型以后,VS自带的编译器又做了升级,升级之后编译时遇到泛型,会做特殊的处理:先生成占位符。再次经过JIT编译的时候,会把上面编译生成的占位符替换成具体的数据类型。(两次编译)
注解:JIT:Just In Time称为即时编译器
4.泛型的优势
1.最大限度地重用代码(支持多种数据类型)。
2.保护类型的安全以及提高性能(和object相比,object会使用到装箱操作)。
3.语法优美
二、泛型的应用范围
泛型方法,泛型接口,泛型类,泛型委托,泛型结构
但在C#中应用比较广的泛型:泛型方法,泛型类,泛型接口
泛型可以提供多种数据类型的占位符
internal class Program
{
static void Main(string[] args)
{
//这里把T变为int,K变为float,
Stort<int,float> stack=new Stort<int,float>();
}
}
//这里T和K都为占位符
class Stort<T,K>
{
public T Value { get; set; }
public K Key { get; set; }
}
1.泛型方法
泛型方法是通过类型参数声明的方法,调用时泛型参数类型可以省略
泛型方法:Show<T>是方法名称 <T>T是未知类型,不区分大小写,但建议使用大写,且尽量使用这些字母:T,K,V,M,N
public static void Show<T>(T i)
{
Console.WriteLine($"方法所在的类名:{typeof(Common).Name},参数的类型名称:{i.GetType().Name},参数值:{i}");
}
泛型方法参数的类型在编译时可以确定( 时机:编译时,运行时)
定义时注意一下3点:
1.参数的未知类型在方法名称后面的<> 中定义。
2.参数的未知类型在方法局部作用域中可以使用。
3.参数的未知类型能定义几个?理论上是无限
例子:有一个静态类,它中有三个方法,分别显示类的类型名称,方法参数的类型名称,方法参数值
// 三个方法:拥有相同的业务逻辑(相同指令),缺点:代码冗余,重复
public static void ShowInt(int i) {
Console.WriteLine($"方法所在的类名:{typeof(Common).Name},参数的类型名称:{i.GetType().Name},参数值:{i}");
}
public static void ShowString(string i)
{
Console.WriteLine($"方法所在的类名:{typeof(Common).Name},参数的类型名称:{i.GetType().Name},参数值:{i}");
}
public static void ShowDateTime(DateTime i)
{
Console.WriteLine($"方法所在的类名:{typeof(Common).Name},参数的类型名称:{i.GetType().Name},参数值:{i}");
}
// 获取类型有两种方式:
// a. typeof一般用来获取引用类型的变量类型。
// b. GetType()是通用的。
public static void ShowInt(int i, string str)
{
// typeof判断引用类型
Console.WriteLine(typeof(Common).Name);
Console.WriteLine(typeof(Common).Namespace);
Console.WriteLine(typeof(Common).FullName);
Console.WriteLine(typeof(Common).GUID);
Console.WriteLine("---------------------");
Console.WriteLine(str.GetType().Name);
Console.WriteLine(str.GetType().Namespace);
Console.WriteLine(str.GetType().FullName);
Console.WriteLine(str.GetType().GUID);
Console.WriteLine("---------------------");
// GetType()方法是通用的,可以获取值类型或引用类型的具体类型。
Console.WriteLine(i.GetType().Name);
Console.WriteLine(i.GetType().Namespace);
Console.WriteLine(i.GetType().FullName);
Console.WriteLine(i.GetType().GUID);
}
2.泛型类
泛型类封装不特定于特定数据类型的操作。 泛型类最常见用法是用于链接列表、哈希表、堆栈、队列和树等集合。 无论存储数据的类型如何,添加项和从集合删除项等操作的执行方式基本相同。
创建泛型类是从现有具体类开始,然后每次逐个将类型更改为类型参数,直到泛化和可用性达到最佳平衡。
泛型类是 C# 中一个非常重要的概念,它们在实际编程中被广泛使用,特别是在需要处理集合和数据结构时。
例子:用泛型类进行排序,可以对实现了IComparable<T>
接口的任何类型进行排序
using System;
using System.Collections.Generic;
namespace 泛型类
{
internal class GenericSorter<T> where T : IComparable<T>
{
private List<T> list;
public GenericSorter()
{
list = new List<T>();
}
public void Add(T item)
{
list.Add(item);//将对象添加到 List<T> 的结尾处。也就是list
}
public void Sort()
{
list.Sort();// 使用.NET内置的Sort方法,它利用了IComparable<T>接口
}
public List<T> Getlist()
{
return list;
}
}
}
using System;
namespace 泛型类
{
internal class Program
{
static void Main(string[] args)
{
GenericSorter<int> intSorter = new GenericSorter<int>();
intSorter.Add(5);
intSorter.Add(2);
intSorter.Add(1);
intSorter.Add(4);
intSorter.Sort();
Console.WriteLine("排好的顺序为"+string.Join(" ",intSorter.Getlist()));//排好的顺序为1 2 4 5
Console.ReadLine();
}
}
}
3.泛型接口
泛型接口和泛型类定义方式一样,在接口名称后使用<T>未知类型,为泛型集合类或表示集合中的项的泛型类定义接口通常很有用处。
使用了泛型定义的接口就是泛型接口。
格式:修饰符 interface 接口名<T>{ }
三、泛型约束
泛型的目的:相同的业务逻辑,支持了不同类型。但一味的滥用,则对代码安全性不利。
引入泛型约束主要控制泛型支持的类型在有限的范围内。
所谓的泛型约束,实际上就是约束的类型T 。使T必须遵循一定的规则。比如T必须继承自某个类,或者T必须实现某个接口等等。
给泛型指定约束:需要where关键字,加上约束的条件。如:where T: class
泛型约束的好处: 让代码异常出现在编译期间,而非运行期间,从而增强代码的安全性。
五种常用的泛型约束:
T:class 引用类型约束保证T一定是引用类型的。
T:结构 值类型约束保证T一定是值类型的。
T:new() 无参数构造函数约束保证T必须有无参数构造函数
T:<基类名> 基类约束时,基类不能是密封类,即不能是sealed类。sealed类表示该类不能被继承,在这里用作约束就无任何意义,因为sealed类没有子类。
T:<接口名称> 接口约束保证T必须实现接口
泛型约束可以同时约束多个。
如:where T : People, ISports 要求T必须是People或People的子类,或同时实现ISports接口。
四、泛型的协变和逆变
协变和逆变是在.NET 4.0的时候出现的,只能放在接口或者委托的泛型参数前面。
out :协变(covariant),用来修饰返回值;
in:逆变(contravariant),用来修饰传入参数。
C#类型转换发生在单一类型中,如:小范围类型的int隐式转换成大范围类型long,大范围类型long强制转换成小范围类型int。而泛型的协变,发生在泛型中。
泛型不是单一类型。如:List<T> List是数据类型,T也是数据类型。
泛型可以认为是复合类型。
协变:类似于隐式类型转换,但有所不同。协变控制的是返回值的类型。如:public interface IEnumerable<out T>
逆变:类似于强制类型转换,但有所不同。逆变控制的是传入参数的类型。如:public delegate bool Predicate<in T>(T obj)
协变和逆变只能放在接口或者委托的泛型参数前面,类型转换没有此限制。
使用了协变以后,=左边声明的是基类,右边可以声明基类或者基类的子类。简单理解:即小转大。
如:协变
IEnumerable<Bird> birdList1 = new List<Bird>();
IEnumerable<Bird> birdList2 = new List<Sparrow>();
使用了逆变之后,=左边声明是子类,右边可以声明子类或者子类的基类。简单理解:即大转小。