.Net泛型详解
引言
在我们使用.Net进行编程的过程中经常遇到这样的场景:对于几乎相同的处理,由于入参的不同,我们需要写N多个重载,而执行过程几乎是相同的。更或者,对于几乎完成相同功能的类,由于其内部元素类型的不同,我们需要写N多个同质化的类。对于这样的场景,我们可以通过使用泛型来进行处理。
泛型的概念
泛型允许我们对类、接口、委托、方法使用一个或多个占位符来延迟声明,在实际调用的时候再补充这些占位符,以便让编译器对不同类型的占位符进行不同的类型编译,这是一种延迟声明的思想体现。
当然,以上是笔者本人的理解,我们也可以采用一些大牛的理解:可利用泛型创建一个数据结构,该数据结构能够进行特化以处理特定的类型,程序员定义这种"参数化类型",使泛型类型的每个变量具有相同内部算法,但数据类型和方法签名可随类型参数而变。
我们经常看到泛型声明使用的占位符"T",而这个占位符就称为这个泛型的"类型参数",而实际调用这个泛型的时候这个"类型参数"又称为"类型实参"。
好了,概念就到这里,反正我是觉得只看概念越看越懵。
泛型的作用
泛型的作用本人理解就是能够减少代码量,给予特定的类或者方法或者委托的等一种通用的代码,而一套通用的代码可以适用于处理各种数据类型,以避免为每种数据类型的处理声明一个类或者方法。
当然,这还是容易理解,要理解他的作用,我们先做出这样的考虑:如果没有泛型会怎样?
下面比如我们有一个这样的需求:
我们需要写一个方法,就是在控制台打印入参(当然,尽管这可能是毫无意义的),假设这个方法需要支持int,string,double三个类型。
面对这样的需求,我们有两种解决方案,第一种,就是定义一个方法三个重载,分别打印int、string、double类型的入参,代码如下:
static void PrintParameter(int parameter){Console.WriteLine(parameter);}static void PrintParameter(string parameter){Console.WriteLine(parameter);}static void PrintParameter(double parameter){Console.WriteLine(parameter);}
以上解决方式肯定不科学,因为这样的一个简单的处理我们竟然写了三个重载,代码量太多了。那么我们可以采取另外一种方式:将方法的入参声明为object类型,因为object类型是所有类型的基类,于是能够支持各种类型的入参,进而打印。
static void PrintParameter(object parameter){Console.WriteLine(parameter);}
这种方式虽然能够解决问题,但是有一个性能弊端,比如我们传入的类型是int,double或者结构类型这些值类型呢?那么难免程序会有拆装箱操作,而这样的操作是对性能有损失的。
那么有既能避免性能损失,又能避免代码冗余的方式吗?
泛型的作用就这样体现了!
我们可以定义一个如下的方法:
static void PrintParamter<T>(T parameter){Console.WriteLine(parameter);}
以上代码我们使用了尖括号包裹的"类型参数"T作为占位符,在实际调用的时候我们只需要指定T的类型,编译器就能够将这些占位符编译成对应的类型,并且既是类型安全的又避免了拆装箱。
所以,在泛型的众多应用中,主要作用是两个方面1:提高性能。2:避免代码冗余。当然,还有很多其他的作用,这个我们在具体编程的时候能够体会到。
泛型的分类和对应的定义语法
泛型根据其主体的不同可以分为泛型类、泛型方法、泛型接口、泛型委托四种类型,下面分别是四种类型的定义:
//这是一个泛型类public class GenericClass<T>{//这是一个泛型方法public void GenericMethod<U,P>(U para1,P para2){}//这是一个泛型委托public delegate K GenericDelegate<K>();}//这是一个泛型接口public interface IGenericInterface<T>{}
通过观察以上简略的定义我们可以发现如下规律:
1:类型参数不只是可以为T,也可以为其他字符或者字符串,因为只是一个占位符而已,叫什么名字不重要。
2:一个泛型的类型参数可以不止一个,可以有,两个、三个甚至更多。而一个泛型的类型参数的个数我们称之为“元数”。以上案例中的GenericMethod显然是一个二元泛型方法。
3:我们为类指定了类型参数之后还能为类中的方法或者委托指定另外的类型参数,当然,实际这种使用的情况很少。
泛型约束
在一般的使用中,我们使用T类型参数来代表一切类型,但是在特定的场景下我们还是希望对类型参数进行进一步的限定,以进一步的保证类型安全,并获得对类型参数的更高的访问权限。
这里"更高的访问权限"可能暂时无法理解,我们举个例子:
比如我们定义了一个动物类Animal,代码如下:
/// <summary>/// 定义有两个属性的动物类/// AnimalTypeName:动物的种类名称,如:老虎/// AnimalHeight:动物的身高,如:10.2/// </summary>public class Animal{public string AnimalTypeName { get; set; }public double AnimalHeight { get; set; }}
然后我们在控制台中定义了一个方法ShowName,用来显示传入对象的名字,用T作为类型参数,因为传入的有可能是动物,有可能是人:
/// <summary>/// 事实上这个方法在语法上是不通过的,因为无法确定入参t,拥有AnimalTypeName属性/// </summary>/// <typeparam name="T"></typeparam>/// <param name="t"></param>static void ShowName<T>(T t){Console.WriteLine(t.AnimalTypeName);}
以上ShowName方法在语法上绝对是错的,因为我们无法确保传入的类型实参一定是Animal类型的或者说是一定拥有AnimalTypeName属性。那么既然代码已经写了,我们是否可以限定类型参数的范围呢,限定T必须为Animal类型。当然可以,代码如下:
/// <summary>/// 使用where T:Animal来约束类型参数T必须是Animal类型或者其子类/// </summary>/// <typeparam name="T"></typeparam>/// <param name="t"></param>static void ShowName<T>(T t) where T: Animal{Console.WriteLine(t.AnimalTypeName);}
我们在ShowName方法后面加入约束where T:Animal,这样就限定了类型参数必须是Animal或者其子类。这个约束,就叫做"基类约束"。
那么除了基类约束还有那些约束呢?下面我们用代码予一一举例:
接口约束(约束类型实参必须继承于某接口)
/// <summary>/// 接口约束,约束类型实参必须继承于某接口/// </summary>/// <typeparam name="T"></typeparam>/// <param name="t"></param>static void ShowName<T>(T t) where T : ICloneable{object obj=t.Clone();}
引用类型约束(约束类型实参必须是引用类型)
/// <summary>/// 引用类型约束:约束类型实参必须是引用类型/// </summary>/// <typeparam name="T"></typeparam>/// <param name="t"></param>static void ShowName<T>(T t) where T : class{}
值类型约束(约束类型实参必须是值类型)
/// <summary>/// 值类型约束:约束类型实参必须是值类型/// </summary>/// <typeparam name="T"></typeparam>/// <param name="t"></param>static void ShowName<T>(T t) where T : struct{}
无参构造函数约束(约束类型实参必须拥有无参的构造函数)
/// <summary>/// 无参构造函数约束:约束类型实参必须拥有无参的构造函数/// </summary>/// <typeparam name="T"></typeparam>/// <param name="t"></param>static void ShowName<T>(T t) where T : new(){}
多重约束(约束多个方面)
/// <summary>/// 多重约束:以下案例约束了类型参数必须为引用类型,且必须继承接口ICloneable,且必须有无参的构造函数/// </summary>/// <typeparam name="T"></typeparam>/// <param name="t"></param>static void ShowName<T>(T t) where T : class, ICloneable,new(){}
协变和逆变论述
看了很多资料,就很少发现把协变和逆变讲清楚了的,这里本人自己总结一下吧,如果总结的不好或者有偏差望朋友们指出。
所谓协变,本人认为就是和谐的变,这里指子类向父类的转变。任何子类实例都可以赋值给一个父类变量,这就是协变。
所谓逆变,就是不和谐的变,这里指父类向子类的转变,父类实例给一个子类变量。
泛型中,我们只可以对接口和委托使用协变和逆变。分别用关键字out和in,用out修饰的类型参数只能用作返回,用in修饰的类型参数只能用作输入。
下面是协变案例(虽然是毫无意义的代码),实现协变:
//假设我们有两个类,Dog类是Animal的子类public class Animal{public virtual void Write(){Console.WriteLine("我是基类");}}public class Dog : Animal{public override void Write(){Console.WriteLine("我是小小狗");}}
然后我们定义一个支持协变的接口和一个类(类是用来后面创建实例的,因为接口无法直接new),用out关键字修饰类型参数:
//支持协变的接口,用out关键字修饰public interface ICovariant<out T>{}//写一个类继承于ICovariantpublic class Covariant<T> : ICovariant<T>{}
然后我们会发现,IConveriant<Dog>的实例能够给一个IConveriant<Animal>的变量,这就称为协变。子类给父类。
ICovariant<Dog> dogCovariant = new Covariant<Dog>();ICovariant<Animal> animalCovariant = dogCovariant;//协变的体现
然后根据以上规律,我们狭义的去总结协变:所谓协变就是当用out关键字修饰了接口或者委托的类型参数之后,对于该泛型,子类类型参数的实例能够赋值给一个该泛型父类类型参数的变量。用伪代码表示就是IConvariant<FatherClass> father=new IConvariant<ChildClass>();
下面我们去理解逆变,逆变是刚好相反的:
同样是上面的代码,我们把IConvariant的类型参数修饰的关键字改成in:
//支持协变的接口,用out关键字修饰public interface ICovariant<in T>//修改了类型参数的关键字{}//写一个类继承于ICovariantpublic class Covariant<T> : ICovariant<T>{}
然后我们写下如下代码:
ICovariant<Animal> animalCovariant = new Covariant<Animal>();ICovariant<Dog> dogCovariant = animalCovariant;//逆变的体现
我们会发现以上两句代码居然能够编译通过,经过in关键字修饰了类型变量的泛型。其父类类型变量的泛型实例能够给一个使用了子类类型参数的泛型变量。刚好反过来了....。这就是逆变,用伪代码表示:IConvariant<ChildClass> child=new IConvariant<FatherClass>()。
一些重点:
一:对于泛型,也许我们对于如下代码会产生疑问:
public interface ICovariant<in T>{}//写一个类继承于ICovariantpublic class Covariant<T> : ICovariant<T>{}
对于Convariant<T>,到底是继承于IConvariant的所有可能的类型参数,还是只继承于相同类型参数。举个例子:
Covariant<Animal>到底只继承于IConvariant<Animal>还是既继承于IConvariant<Animal>又继承于IConvariant<Dog>。这里本人的理解是,泛型和泛型之间的继承,只有相同的类型参数才有继承关系。就是Covariant<Animal>到底只继承于IConvariant<Animal>
二:父类的泛型约束能够被子类继承吗?
答案显然是否定的,继承关系我们只听说继承属性,方法,字段,没听说能继承约束的。
相关文章:
.Net泛型详解
引言 在我们使用.Net进行编程的过程中经常遇到这样的场景:对于几乎相同的处理,由于入参的不同,我们需要写N多个重载,而执行过程几乎是相同的。更或者,对于几乎完成相同功能的类,由于其内部元素类型的不同&…...
C++ 教程(10)——存储类
存储类定义 C 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。下面列出 C 程序中可用的存储类: autoregisterstaticexternmutablethread_local (C11) 从 C 17 开始,auto 关键字不再是 C 存储…...
vue3+vite+element-plus创建项目,修改主题色
element-plus按需引入,修改项目的主题色 根据官方文档安装依赖 npm install -D unplugin-vue-components unplugin-auto-import vite.config.js配置 // vite.config.ts import { defineConfig } from vite import AutoImport from unplugin-auto-import/vite …...
mysql select是如何一步步执行的呢?
mysql select执行流程如图所示 server侧 在8.0之前server存在查询语句对应数据的缓存,不过在实际使用中比较鸡肋,对于更新比较频繁、稍微改点查询语句都会导致缓存无法用到 解析 解析sql语句为mysql能够直接执行的形式。通过词法分析识别表名、字段名等…...
找到距离最近的点,性能最好的方法
要找到距离最近的点并且性能最好,一种常用的方法是使用空间数据结构来加速搜索过程。以下是两个常见的数据结构和它们的应用: KD树(KD-Tree):KD树是一种二叉树数据结构,用于对k维空间中的点进行分割和组织…...
vue基础--重点
!1、vue的特性 !2、v-model 双向数据绑定指令 (data数据源变化,页面变化; 页面变化,data数据源也变化) 1、v-model 会感知到 框中数据变化 2、v-model 只有在表单元素中使用,才能…...
HarmonyOS元服务端云一体化开发快速入门(上)
一、前提条件 您已使用已实名认证的华为开发者帐号登录DevEco Studio。 请确保您的华为开发者帐号余额充足,账户欠费将导致云存储服务开通失败。 二、选择云开发模板 1.选择以下任一种方式,打开工程创建向导界面。 如果当前未打开任何工程,…...
leetcode 279.完全平方数
题目描述 给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 …...
Spring boot ApplicationContext
https://www.geeksforgeeks.org/spring-applicationcontext/ AnnotationConfigApplicationContext container 对象直接标注annotation: Configuration, Component ApplicationContext context new AnnotationConfigApplicationContext(AppConfig.class, AppConf…...
【Python实战】Python采集王者皮肤图片
前言 我们上一篇介绍了,如何采集王者最低战力,本文就来给大家介绍如何采集王者皮肤,买不起皮肤,当个桌面壁纸挺好的。下面,我和大家介绍如何获取数据。 环境使用 python 3.9pycharm 模块使用 requests 模块介绍 re…...
很详细的Django开发入门详解(图文并茂)
1.Django概述 Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型M,视图V和模版T。 Django 框架的核心组件有: 用于创建模型的对象关系映射;为最终用户设计较好的管理界面;…...
Ansible 部署
ansible 自动化运维工具,可以实现批量管理多台(成百上千)主机,应用级别的跨主机编排工具 特性: 无agent的存在,不要在被控制节点上安装客户端应用 通过ssh协议与被控制节点通信 基于模块工作的,…...
【操作系统】计算机操作系统知识点总结
文章目录 前言一、操作系统的概念与发展二、操作系统的结构与功能1、操作系统的结构2、操作系统的功能 三、进程管理1、进程2、进程的创建3、进程管理的实现4、进程控制块 四、内存管理1、内存2、内存管理3、内存管理的实现 五、文件系统1、文件系统2、文件系统的主要任务3、文…...
springmvc整合thymeleaf
概述 Thymeleaf提供了一组Spring集成,使您可以将其用作Spring MVC应用程序中JSP的全功能替代品。 这些集成将使您能够: Controller像使用JSP一样,将Spring MVC 对象中的映射方法转发到Thymeleaf管理的模板。在模板中使用Spring表达式语言&…...
Redis 内存管理机制
Redis作为一个内存数据库,内存资源非常珍贵。因此,Redis引入了3种内存管理机制来释放不必要的内存,包括定期删除、惰性删除和内存淘汰机制。 定期删除 定期删除是Redis内存管理机制的一种,它用于删除过期的键值对。Redis每隔 10…...
Apache Zeppelin系列教程第九篇——Zeppelin NoteBook数据缓存
背景 在使用Zeppelin JDBC Intercepter 对于Hive 数据进行查询过程中,如果遇到非常复杂的sql,查询效率是非常慢 比如: select dt,count(*) from table group by dt做过数据开发的同学都知道,在hive sql查询过程中,hive…...
用代码实现一个简单计算器
作者主页:paper jie的博客_CSDN博客-C语言,算法详解领域博主 本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 本文录入于《C语言》专栏,本专栏是针对于大学生,编程小白精心打造…...
运维圣经:挖矿木马应急响应指南
目录 挖矿木马简介 挖矿流程 挖矿木马应急响应 一. 隔离被感染主机 二. 确定挖矿进程 三. 挖矿木马清除 1、阻断矿池地址的连接 2、清除挖矿定时任务、启动项等 3、禁用可疑用户 4、定位挖矿木马文件的位置并删除 5、全盘杀毒、加固 挖矿木马简介 挖矿:…...
【Flutter】Flutter 如何获取安装来源信息
文章目录 一、 前言二、 安装来源信息的基本概念1. 什么是安装来源信息2. 为什么我们需要获取安装来源信息 三、 如何在 Flutter 中获取安装来源信息1. 准备工作2. 安装必要的依赖库3. 编写代码获取安装来源信息 四、 完整示例代码五、总结 一、 前言 在这篇文章中,…...
Stimulsoft Reports用户手册:Report Designer介绍
Stimulsoft Reports.Net是一个基于.NET框架的报表生成器,能够帮助你创建结构、功能丰富的报表。StimulReport.Net 的报表设计器不仅界面友好,而且使用便捷,能够让你轻松创建所有报表;该报表设计器在报表设计过程中以及报表运行的过…...
跨模态检索论文阅读:Dissecting Deep Metric Learning Losses for Image-Text Retrieval(GOAL)
Dissecting Deep Metric Learning Losses for Image-Text Retrieval 剖析图像文本检索中的深度度量学习损失 2022.10 视觉语义嵌入(VSE)是图像-文本检索中的一种流行的应用方法,它通过学习图像和语言模式之间的联合嵌入空间来保留语义的相似性…...
贪心算法part5 | ● 435. 无重叠区间 ● 763.划分字母区间 ● 56. 合并区间
文章目录 435. 无重叠区间思路思路代码困难 763.划分字母区间思路官方题解代码困难 56. 合并区间思路思路代码 今日收获 435. 无重叠区间 思路 重叠问题都需要先排好序,再贪心 思路代码 func eraseOverlapIntervals(intervals [][]int) int {sort.Slice(interva…...
IMX6ULL裸机篇之SPI实验-ICM20608代码实现
一. SPI 实验 SPI实验:学习如何使用 I.MX6U 的 SPI 接口来驱动 ICM-20608,读取 ICM-20608 的六轴数据。 本文学习 SPI通信实验中,涉及从设备的 SPI代码编写。 之前学习了 SPI 主控芯片代码的编写,如下所示: IMX6ULL…...
51单片机读取DS18B20温度传感器
1.首先我们知道DS18B20是单总线协议,只有一根数据线。所以Data数据线即使发送端又是接收端,同时DS18B20内部接了弱上拉电阻(如图一所示),数据线默认为高电平。有了这些概念,我们就能进行下一步。 图一&…...
set/map学习
我们要开始学习map和set的使用,虽然使用更加复杂,但是STL整体的设计,本身就具有很强的前瞻性和延续性,比如说迭代器等,我们顺着文档来看。这也是除了vector之外最重要的容器,当然还有unordered_map 和 unor…...
JavaScript Web APIs学习总结
以后声明变量我们有限使用哪一个? const 有了变量先给const,如果发现它后面是要被修改的,再改为let 为什么const声明的对象可以修改里面的属性? 因为对象是引用类型,里面存储的是地址,只要地址不变&…...
萤石摄像头RTSP流获取(黑屏解决)
前言 在获取萤石摄像头RTSP视频流时,视频流获取不成功,黑屏并且一直显示缓冲中。下面对获取过程中查阅的资料和解决方案做一下汇总。 打开RTSP 在萤石云视频APP中打开RTSP,【我的】-【工具】-【局域网设备预览】-【开始扫描】-【选择摄像头…...
ThreadLocal引发的内存泄漏分析
预备知识(引用) Object o new Object(); 这个o,我们可以称之为对象引用,而new Object()我们可以称之为在内存中产生了一个对象实例。 当写下 onull时,只是表示o不再指向堆中object的对象实例,不代表这个…...
银行数据治理:数据质量管理实践
现代商业银行日常经营活动中积累了大量数据,这些数据除了支持银行前台业务流程运转之外,越来越多地被用于决策支持领域,风险控制、产品定价、绩效考核等管理决策过程也都需要大量高质量数据支持。银行日常经营决策过程的背后,实质…...
2.7V至25V宽输入电压15A 峰值电流
HT7179是一款高功率异步升压转换器,集成 20mΩ功率开关管,为便携式系统提供高效的 小尺寸解决方案。 HT7179具有2.7V至25V宽输入电压范围,可为 采用单节或两节锂电池,或12V铅酸电池的应 用提供支持。该器件具备15A开关电流能力&a…...
网络工作室适合做什么/站长工具seo优化
最近跟一位牛人学java项目的搭建,才知道这个EGit的功能很强大。安装的话就参考这个下面的连接http://www.cnblogs.com/zhxiaomiao/archive/2013/05/16/3081148.html详细的有关具体的操作指示请看下面两个链接:https://www.eclipse.org/egit/http://www.v…...
小购物网站建设/网店运营流程步骤
2019独角兽企业重金招聘Python工程师标准>>> 按照gdal预编译库sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable sudo apt-get update sudo apt-get install libgdal20按照python绑定pip install gdal如果在python console中使用import gdal出现sqlite3_c…...
电脑上如何做课程视频网站/宁波网站建设公司哪家好
有人可以提供一个示例或引用,它提供了一种方法,可以使用Jackson库将嵌套JAVA对象转换为JSON输出.我没有转换平面JAVA对象的问题.但是,JSON库显示嵌套对象名称和类型而不是其子对象.我几乎利用了http://www.mkyong.com/java/jackson-2-convert-java-object-to-from-json/提供的相…...
个性网站制作/百度搜索引擎推广步骤
我有一个表单验证问题。下面是提交时发生的逻辑(至少是其中的一部分)。在for循环中,我们检查一个站点访问者可以注册的可能事件数组。如果用户没有检查任何事件(这些是复选框,因为用户可以注册多个事件),我们应该在下面输入第二条if语句&…...
网站建设中/windows优化大师免费
配置项 全局配置项 我们来看下全局配置项有哪些。在学习具体的配置项之前,先来看下pyecharts生成的图由哪几个部分组成。 针对以上每个部分,都有相应的配置项来进行配置。所有的配置类,都是放到pyecharts.options中。 InitOpts:初始化配置项 可以配置诸如图像宽度,高…...
成都市网站建设公司/网络营销的盈利模式
参数传递:将主程序变量传递给子例程形式参数传递类型值传:子例程中参数变量的值的改变,不影响外部程序实际变量的值. DATA:A TYPE I VALUE 3,B TYPE I VALUE 6,C TYPE I. WRITE:A,A,B,B,C,C. PERFORM ADD USING A B CHANGING C. WRITE:/ SY-…...