1. 本文构建思路
- 什么是泛型
- 泛型是解决什么问题的(为什么会出现泛型)?
用一个demo说明问题:功能概述打印输出不同类型值,分别写三个方法分别实现,详见正文部分 - 通过这个例子用三种不同的实现方法,得出结论,泛型 是一种比较优的方法,
泛型的其他主要应用场景- 泛型集合
- 泛型类
- 泛型方法
- 泛型接口
- 泛型委托
- 进阶
- 泛型约束
- 逆变与协变
- 泛型缓存
- 泛型的好处与原理
2. 正文
2.1 什么是泛型
1. 理解:泛型:广泛的变量类型(类型包括:int、double等系统类型,自定义的类型(自定义类))
* 相同的处理逻辑,不同类型的参数,在处理时需要分别处理,在这种情况下,需要有一种类型可以用来替代所有类型进行逻辑
* 设计思想:声明的时候不指定变量类型,在调用的时候再指定类型,延迟声明(将参数的类型的声明推迟到调用),理解是单身狗(遇到的时候再声明类型)与已婚男士(提前声明变量类型)的区别 ---(拓展:延迟思想)
* 下面demo3中的泛型方法:private static void PrintPara<T>(T para) 中的T
T:占位符,这个占位符满足C#命名规范即可,但是约定俗成用 T
T:表示为通用数据类型,在使用的时候,用实际类型代替
2. 好处:增加类型安全,便于维护
2.2 泛型是解决什么问题的(为什么会出现泛型)?
用一个demo说明问题:功能概述打印输出不同类型值,分别写三个方法分别实现,详见正文部分
- 方法1:用基础方法实现(用时14ms),下面为实现代码
#region 方法1:基础方法实现打印输出不同的数据类型
public static void Main(string[] args)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
PrintPara(12);
PrintPara("123");
PrintPara(DateTime.Now);
stopWatch.Stop();
Console.WriteLine(stopWatch.ElapsedMilliseconds);
Console.ReadLine();
}
private static void PrintPara(int Para)
{
Console.WriteLine($"这个参数的类型是{Para.GetType().Name},值为{Para}");
}
private static void PrintPara(string Para)
{
Console.WriteLine($"这个参数的类型是{Para.GetType().Name},值为{Para}");
}
private static void PrintPara(DateTime dateTime)
{
Console.WriteLine($"这个值得数据类型为{dateTime.GetType().Name},值为{dateTime}");
}
#endregion
评价:这样写造成代码冗余,不易维护
解决:用Object类替代吧,原因是:
1). Object是所有类型的基类
2). 根据继承的原则,所有父类出现的地方,父类均可以被子类替换
- 方法2:用Object方法实现(用时16ms),用一个方法即可实现,下面为实现代码
public static void Main(string[] args)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
PrintPara(12);
PrintPara("123");
PrintPara(DateTime.Now);
stopWatch.Stop();
Console.WriteLine(stopWatch.ElapsedMilliseconds);
Console.ReadLine();
}
private static void PrintPara(Object Para)
{
Console.WriteLine($"这个参数的类型是{Para.GetType().Name},值为{Para}");
}
评价:Object 实现后,通过对比两者实现所用的时间,可以看出Object效率低
原因是:存在 拆箱装箱 问题
不能为了便于维护,就牺牲效率吧,那样的话,没有什么意义啊,
有没有一种即便于维护又可以兼顾效率的写法呢?
有!!!!
泛型(带着音乐与光环,自己想象一下....)
注: 拆箱装箱详解
- 泛型实现打印输出不同的数据类型 (用时11ms)(一个小技巧<计算程序运行时间方法>)
public static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();// 这方法可以获取程序运行时间,便于监控程序性能
stopwatch.Start();
PrintPara(12);
PrintPara("123");
PrintPara(DateTime.Now);
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
Console.ReadLine();
}
private static void PrintPara<T>(T para)
{
Console.WriteLine($"这个参数的数据类型{para.GetType().Name},值为{para}");
}
**评价:只用一个方法实现,而且效率比方法一还要高,优选
在这个方法里面介绍了泛型集合的使用方法**
2.3 泛型约束
- default 关键字:如果要给 T 设置初始值,不能直接设置,则使用default,用法如下
public class GenericClassdefault<T1, T2>
{
//T1 obj = null; // 这种写法是错误的,这里设置默认值时,不能直接赋值,可以使用default
T1 obj2 = default(T1);// 这种写法,会根据T的具体类型进行默认值的初始化
//如果是引用类型则是Null,int 类型的则0,String 类型则为""
}
- where 关键字:对调用端传入参数的限制,如下例子
public class GenericYueShu<T1, T2, T3,T4>
where T1 : struct //参数类型为值类型
where T2 : class //参数类型为引用类型
where T3 : new() //参数类型必须有一个无参数的构造方法
where T4 : ISports // 接口约束
where T5 :People //基类约束,保证传入值是非密封基类或者基类的子类
{
}
2.4 逆变与协变
- 什么是协变
父类 a = new 子类();正确
List<父类> a = new List<子类>();错误,原因为,两者间不存在父子关系
可以进行转化,方法为:
List<父类> a = new List<子类>().Select(c=>(子类)c).ToList();
???
2.5 泛型缓存
- 以泛型类为例,在调用时候,系统会对一个不同类型的调用,产生新的副本,如下代码:
//如下为泛型类
public class CacheClass<T>
{
static CacheClass()
{
if (_typeName == null)
{
_typeName = string.Format($"{"类型为:" + typeof(T) + "当前时间为:" + DateTime.Now.ToString("yyyyMMddHHmmssfff")}");
}
}
private static string _typeName;
public string ReturnTypeName()
{
return _typeName;
}
}
//如下为泛型类的调用:
string str_Return;
CacheClass<int> a = new CacheClass<int>();
str_Return=a.ReturnTypeName();
Console.WriteLine(str_Return);
Thread.Sleep(300);
CacheClass<string> b = new CacheClass<string>();
str_Return=b.ReturnTypeName();
Console.WriteLine(str_Return);
Thread.Sleep(300);
CacheClass<DateTime> c = new CacheClass<DateTime>();
str_Return=c.ReturnTypeName();
Console.WriteLine(str_Return);
Thread.Sleep(300);