【C#学习】--泛型
.1 泛型概述
默认命名空间是
.1.1 特点
泛型是一种程序特性,声明时对类型不作明确规定,使用时必须明确规定且规定后不可再更改
.1.2 应用
泛型集合(常用的List<T>、Dictionary<k,v>都属于泛型集合,而ArrayList和Hashtable则属于非泛型);
泛型方法;
泛型类;
泛型委托(重中之重)
.1.3 实现
以List进行展示,在我们声明List元素数据类型的时候,就已经包含了string、int等多种基础数据类型(声明时范围很宽泛,数据类型多样)
但在使用时,只能从众多数据类型中选取一个,如下面的三个列表,各自只能选择string、int或double,而不能同时使用几种数据类型
.1.4 补充
关于在概述里提到的非泛型ArrayList和Hashtable(哈希表)
在定义界面可以看到,
相比较泛型所在的System.Collections.Generic命名空间,
ArrayList和Hashtable(哈希表)都属于System.Collections命名空间,
其中ArrayList实现了IList、ICollection、IEnumerable、ICloneable接口,特性是会根据需要动态改变容量
而Hashtable(哈希表)实现了IDictionary、ICollection等接口,特性是键值对基于键的hashcode进行组织
ArrayList可以调用的Add()方法的参数列表中的数据类型为object,这也就说明了ArrayList类的对象可以是object下包含的各种具体数据类型
虽然这种方式对于用户添加不同类型的元素很方便,但对于数据本身来说是非常不安全的,在开发过程中我们应该尽可能减少这种不可控状况的存在
除此之外,还这种方式涉及拆装箱的问题
装箱:将值类型的元素,放到集合中会被转换成object类型(如上图Add方法所示:value -> object),这一过程成为装箱
拆箱:将一个集合中的元素取出,但这个元素本质是值类型,因此必须强制类型转换
一旦涉及拆装箱,由于反复的类型转换,在存储大量数据的情境中,性能会受到明显的影响,
非泛型集合的弊端由此体现,并且在实际应用中,在一个集合中不对数据类型进行限定属于罕见情况,这就进一步缩减了非泛型集合的优势,
用户的主流需求:不希望在一个容器中添加不同类型的数据,但希望容器能根据我们的需求,随时决定能够添加哪一种具体类型的数据
即容器内存储数据的类型在某一个时间点应该是确定的但又可以随时更改(要么是A,要么是B,想改成A或者B都可以,但是A和B在同一时间不允许被容器同时接受——>因此如概述中说的那样,对于类型声明时不作明确规定,使用时必须明确规定)
因此后面推出了泛型。
由此,在上面的泛型集合和非泛型集合的对比上,反而有种“泛型正统并不在泛型集合而在非泛型集合”的感觉,从非泛型集合身上感受到的才是从声明到使用自始至终唯一不变的自由(题外话)
.2 泛型方法
.2.1 诞生意义
已知一个现有方法A,当我们需要一个与A相比实现逻辑完全相同,仅仅是参数列表里的参数所属数据类型不同的方法B时,如何创建?
传统方式
在面对多种数据类型的时候,这种方式未免过于死板笨重,为了改进,我们可以定义泛型方法
从左到右分别对方法的返回值类型、被方法用于运算的参数的数据类型及参数列表里各个参数的数据类型进行了泛型处理
(逻辑上应该是先确定方法所使用的参数类型为T,由此在方法名FFC后用<T>进行泛型处理,然后参数列表里的各个参数也应该随之属于T数据类型,既然参与运算的参数都是T类型,那么最终的返回值也应该是T类型)
这里虽然报错了但是问题在于我们对参数进行泛型处理后系统无法确定此时的数据类型一定能参与+运算,如果想不报错:
方法一 重写+的运算方法,这里仅用于演示如何利用泛型优化上面的情境
方法二 使用dynamic延迟这里对参与运算的变量对应的数据类型的解析,具体实现请看.4.3的补充
.2.2 实现
分别使用泛型对类的成员变量的定义类型、方法的参数类型、方法的返回值类型进行处理
基于上面的泛型类声明一个测试方法
.3 泛型中的default关键字
.3.1 应用场景
在使用泛型处理类的成员变量后,我们无法通过常规的手段对变量进行直接赋值
因为泛型变量类型是否为引用类型或值类型都无法确定,因此针对于引用类型的实例化和针对值类型的直接赋值都不能使用,如果存在直接赋值的需求则需要用到default关键字
情况一 无法实例化:
情况二 无法直接赋值
.3.2 实现
.4 泛型约束
.4.1 应用场景
对泛型类型设置数据类型方面的要求(约束)或其他的条件
例如.3里提到的,要求某一个泛型T1必须是值类型/T2必须是引用类型/T1可以实例化
.4.2 实现
在添加约束前,无法对T3类型进行实例化
在类名后的位置对T3添加new()约束后,就可以解决这个问题(此时要求T3必须通过这种方式来实例化)
同样,如果需要对某个泛型类型添加必须为引用类型/必须为值类型的约束,则通过添加class和struct约束解决
毫无疑问,添加约束后的泛型从无拘无束变得有条件限制了,但是这也正为我们在特定条件里的应用提供了便利
上面通过约束已经将T1限制在了struct这一较小范围,但仍不是精确数据类型,
但我们在编写泛型方法时难免需要在这种情境中传递值,而直接方法或使用var都无法正常传值
此时使用dynamic基于约束进行进一步的值传递
直接传值(但由于T1包括但不限于int而失败):
var(var的推断逻辑对于非精确类型T1无用):
dynamic:
与生活中的谣言传递类似,使用dynamic系统不会在编译阶段(即信息在人群中的层层传递过程中)去解析对象类型(核实信息的真实与否),在运行时才会解析(在谣言影响到一定程度时候才会去验证真假),但谣言应该越早终结越好,这里泛型的环境下需要这种延迟处理来保证编译阶段泛型变量的值传递
.4.3 实例测试
基于前面的铺垫,按照下面要求写一个实例测试
一个课程订购系统,包含课程名称,课程周期,教师姓名,教师代课数目这几个信息供学生查询
分析:
分别写一个课程Course类和一个教师Teacher类
Course类包含课程名称CourseName和课程周期Period两个属性字段
Teacher类包含教师名称TeacherName和教师代课数目CourseCount两个属性字段
再将上面两个类填充到前面添加完约束的泛型类MyGenericClass2里(这里需要理清思路,可以反复看看这部分)
在main函数里的运行结果:
补充:动态类型dynamic
基于dynamic改进之前未完成的泛化处理Add(int a,int b)方法
在此再次强调dynamic的应用场景:
在参数列表中变量声明为泛型(即我们不希望参与运算的参数有明确的数据类型)但具体的计算又要求有明确的数据类型,这时候使用dynamic来声明一个动态变量存储泛型变量的值,延迟对数据类型的解析到程序运行阶段
比如之前的BuyCourse(T courseId)方法中的courseId,在后续的运算中要求必须是int类型,这时候用dynamic关键字声明一个动态变量index并将courseId的值赋给index,由于index不参与精确数据类型解析,所以可以替代courseId参与后面的运算
又比如上面的GetRsultGeneric方法,其实存在的问题有两个,一个是a和b各自为泛型,无法明确是否为同一精确数据类型(暂时称为条件1),第二个才是如果ab为同一数据类型这个数据类型能否参与加法运算(暂时称为条件2)
这里我们用dynamic声明了两个变量a1、b1,保证了a1、b1为同一数据类型(达成条件1)的同时延迟了解析(达成条件2),最终实现了对原本局限在int数据类型参数加法运算方法的泛型处理。