C#之泛型
目录
一、概述
二、C#中的泛型
继续栈的示例
三、泛型类
(一)声明泛型类
(二)创建构造类型
(三)创建变量和实例
(四)比较泛型和非泛型栈
四、类型参数的约束
(一)Where子句
(二)约束类型和次序
五、泛型方法
(一)声明泛型方法
(二)调用泛型方法
(三)泛型方法的示例
六、泛型结构
七、泛型委托
八、泛型接口
(一)使用泛型接口的示例
(二)泛型接口的实现必须唯一
一、概述
泛型是用于处理算法、数据结构的一种编程方法。泛型的目标是采用广泛适用和可交互性的形式来表示算法和数据结构,以使它们能够直接用于软件构造。泛型类、结构、接口、委托和方法可以根据它们存储和操作的数据的类型来进行参数化。泛型能在编译时提供强大的类型检查,减少数据类型之间的显示转换、装箱操作和运行时的类型检查。泛型类和泛型方法同时具备可重用性、类型安全和效率高等特性,这是非泛型类和非泛型方法无法具备的。泛型通常用在集合和在集合上运行的方法中。
泛型主要是提高了代码的重要性。例如,可以将泛型看成是一个可以回收的包装箱A。如果在包装箱A上贴上苹果标签,就可以在包装箱A里装上苹果进行发送;如果包装箱A上贴上地瓜标签,就可以在包装箱A里装上地瓜进行发送。
二、C#中的泛型
泛型(generic)特性提供了一种更优雅的方式,可以让多个类型共享一组代码。泛型允许我们声明类型参数化的代码,用不同的类型进行实例化。也就是说我们可以用“类型占位符”来写代码,然后在创建类的实例时指明真实的类型。
我们应该清楚类型不是对象而是对象的模板。同样泛型类型也不是类型,而是类型的模板。
C#提供了5种泛型:类、结构、接口、委托和方法。注意,前面4个是类型,而方法是成员。
继续栈的示例
在栈的示例中,MyIntStack和MyFloatStack两个类的声明主体差不多,只不过处理由栈保存的值类型的位置不同。
- 在 MyIntStack中,这些位置由int类型占据。
- 在MyFloatStack中,这些位置被float占据。
通过如下步骤我们可以从MyIntStack创建一个泛型类。
(1)在MyIntStack类定义中,使用类型占位符T而不是float来替换int。
(2)修改类名称为MyStact。
(3)在类名后放置<T>。
结果就是如下的泛型类声明。由尖括号和T构成的字符串表明T是类型的占位符(不一定是字符T,它可以是任何标识符)。在类声明的主体中,每一个T都会被编译器替换为实际类型。
三、泛型类
创建和使用常规的、非泛型的类有两个步骤:声明类和创建类的实例。但是泛型类不是实际的类,而是类的模板,所以我们必须先从它们构建实际的类类型,然后创建这个类类型的引用和实例。
(1)在某些类型上使用占位符来声明一个类。
(2)为占位符提供真实类型。这样就有了真实类的定义,填补了所有的“空缺”。该类型称为构造类型
(3)创建构造类型的实例。
(一)声明泛型类
声明一个简单的泛型类和声明普通类差不多,区别如下:
- 在类名之后放置一组尖括号
- 在尖括号中用逗号分隔的占位符字符串来表示需要提供的类型。这叫作类型参数(type parameter)。
- 在泛型类声明的主体中来使用类型参数来表示替代类型。
例如,如下代码声明了一个叫作SomeClass的泛型类。类型参数列在尖括号中,然后当作真实类型在声明的主体中使用。
class SomeClass <T1,T2>// T1,T2为类型参数
{public T1 SomeVar;public T2 OtherVar;
}
在泛型类的声明中并没有特殊的关键字。取而代之的是尖括号中的类型参数列表,它可以区分泛型类与普通类的声明。
(二)创建构造类型
一但声明了泛型类型,我们就需要告诉编译器能使用哪些真实类型来替代占位符(类型参数)。编译器获取这些真实类型并创建构造类型(用来创建真实对象的模板)。
创建构造类型的语法如下,包括列出类名以及在尖括号中提供真实类型来替代类型参数。替代类型参数的真实类型叫作类型实参(type argument)。
SomeClass<short, int> //尖括号内为类型实参
编译器接受了类型实参并且替换泛型类主体中的相应类型参数,产生了构造类型——从它创建真实类型的实例。
类型参数和类型实参的区别
- 泛型类声明上的类型参数用作类型的占位符
- 在创建构造类型时提供的真实类型是类型实参。
(三)创建变量和实例
在创建引用和实例方面,构造类类型的使用和常规类型差不多。例如,如下代码演示了两个类对象的创建。
MyNonGenClass MyNGC = new MyNonGenClass ();SomeClass<short, int> mySc1 = new SomeClass<short, int>();//SomeClass<short, int>为构造类
var mySc2 = new SomeClass<short, int>();
- 第一行显示了普通非泛型类型对象的创建。
- 第二行代码显示了SomeClass泛型类型对象的创建,使用short和int类型进行实例化。这种形式和上面一行差不多,只不过把普通类型名改为构造类形式。
- 第三行和第二行的语法一样,没有在等号两边都列出构造类型,而是使用var关键字让编译器使用类型引用。
(四)比较泛型和非泛型栈
非泛型栈和泛型栈之间的区别:
非泛型 | 泛型 | |
源代码大小 | 更大:需要为每一种类型编写一个新的实现 | 更小:不管构造类型的数量有多少,只需要一个实现 |
可执行文件大小 | 无论每一个版本的栈是否会被使用,都会在编译的版本中出现 | 可执行文件中只会出现有构造类型的类型 |
写的难易度 | 易于书写,因为它更具体 | 比较难写,因为它更抽象 |
维护的难易度 | 更容易出问题,因为所有修改需要应用到每一个可用的类型上 | 易于维护,因为只需要修改一个地方 |
四、类型参数的约束
在泛型栈的示例中,栈除了保存和弹出它包含的一些项之外没有做任何事情。它不会尝试添加、比较项,也不会做其他任何需要用到项本身的运算符的事情。这是有原因的。由于泛型栈不知道它们保存的项的类型是什么,所以也就不会知道这些类型实现的成员。
然而,所有的C#对象最终都从object类继承。因此,栈可以确认的是,这些保存的项都实现了object类的成员,包括ToString、Equals以及GetType方法。
只要我们的代码不访问它处理的一些类型的对象(或者只要它始终是object类型的成员),泛型类就可以处理任何类型。符合约束的类型参数叫作未绑定的类型参数。然而,如果代码尝试使用其他成员,编译器会产生一个错误信息。
(一)Where子句
约束使用where子句列出
- 每一个有约束的类型参数都有自己的where子句
- 如果形参有多个约束,它们在where子句中使用逗号分隔。
where子句的语法如下:
where Typeparam : constraint, contraint, ...
有关where子句的要点如下:
- 它们在类型参数列表的关闭尖括号之后列出。
- 它们不使用逗号或其他符号分隔。
- 它们可以以任何次序列出。
- where是上下文关键字,所以可以在其他上下文中使用。
例如,如下泛型类有3个类型参数。T1是未绑定的类型参数。对于T2,只有Customer类型的类或从Customer派生的类才能用作类型实参。而对于T3,只有实现IComparable接口的类才能用作类型实参。
class MyClass <T1, T2, T3>where T2 : Customer //T2的约束 where T3 : IComparable //T3的约束
{...
}
(二)约束类型和次序
约束类型 | 描述 |
类名 | 只有这个类型的类或从它派生的类才能用作类型实参 |
class | 任何引用类型,包括类、数组、委托和接口都可以用作类型实参 |
struct | 任何值类型都可以用作类型实参 |
接口名 | 只有这个接口或实现这个接口的类型才能用作类型实参 |
new() | 任何带有无参公共构造函数的类型都可以用作类型实参。这叫做构造函数约束 |
where子句可以以任和次序列出。然而,where子句中的约束必须有特定的顺序。
- 最多只能有一个主约束,而且必须放在第一位
- 可以有任意多的接口名称约束
- 如果存在构造函数约束,则必须放在最后。
五、泛型方法
除了定义泛型类之外,还可以定义泛型方法。在泛型方法中,泛型类型用方法声明来定义。泛型方法可以在非泛型类中定义。
与其他泛型不一样,方法是成员,不是类型。泛型方法可以在泛型和非泛型类以及结构和接口中声明。
(一)声明泛型方法
泛型方法具有类型参数列表和可选的约束
(1)泛型方法有两个参数列表
- 封闭在圆括号内的方法参数列表
- 封闭在尖括号内的类型参数列表
(2)要声明泛型方法,需要:
- 在方法名称之后和方法参数列表之前放置类型参数列表;
- 在方法参数列表后放置可选的约束子句
(二)调用泛型方法
要调用泛型方法,应该在方法调用时提供类型实参,如下所示:
下面演示了一个叫作DoStuff的泛型方法的声明,它接受两个类型参数。其后是两次方法调用,每使用不同的类型参数。编译器使用每个构造实例产生方法的不同版本。
推断类型
如果我们为方法传入参数,编译器有时可以从方法参数的类型中推断出应用作泛型方法的类型参数的类型。这样就可以使方法调用更简单,可读性更强。
例如,下面的代码声明了MyMethod,它接受了一个与类型参数同类型的方法参数。
如果我们使用int类型的变量调用MyMethod,方法调用中的类型参数的信息就多余了,因为编译器可以从方法参数中得知它是int。
由于编译器可以从方法参数中推断类型参数,我们可以省略类型参数和调用中的尖括号。
(三)泛型方法的示例
如下的代码在一个叫作Simple的非泛型类中声明了一个叫作ReverseAndPrint的泛型方法。这个方法把任意类型的数组作为其参数。Main声明了3个不同的数组类型,然后使用每一个数组调用方法两次。
第一次使用特定数组调用了方法,并显式使用类型参数。而第二次让编译器推断类型。
class Simple //非泛型类
{static public void ReverseAndPrint <T>(T[] arr) //泛型方法{ Array.Reverse(arr);foreach(T item in arr) //使用类型实参TConsole.Write($"{item.Tostring()},");Console.WrtiteLine("");}
}class Program
{static void Main(){//创建各种类型的数组 var intArray = new int[] {3, 5, 7, 9, 11};var stringArray = new string[] {"first", "second", "third"};var doubleArray = new double[] {3.567, 7.891, 2.345};Simple.ReverseAndPrint<int>(intArray); //调用方法Simple.ReverseAndPrint(intArray); //推断类型并调用Simple.ReverseAndPrint<string>(stringArray);//调用方法Simple.ReverseAndPrint(stringArray); //推断类型并调用Simple.ReverseAndPrint<double>(doubleArray);//调用方法Simple.ReverseAndPrint(doubleArray); //推断类型并调用}
}
六、泛型结构
与泛型类相似,泛型结构可以有类型参数和约束。泛型结构的规则和条件与泛型类是一样的。
例如,下面的代码声明了一个叫作PieceOfData的泛型结构,它保存和获取一块数据,其中的类型在结构类型时定义。Main创建了两个构造类型的对象——一个使用int,而另一个使用string。
struct PieceOfData<T> //泛型结构
{public PieceOfData (T value) {_data = value;}private T _data;public T Data{get {return _data;}set {_data = value;}}
}class Program
{static void Main(){var intData = new pieceOfData<int>(10);var stringData = new PieceOfData<string>("Hi there.");Console.WriteLine($"intData = {intData.Data}");Console.WriteLine($"stringData = {stringData.Data}");}
}
这段代码产生了如下的输出:
intData = 10
StringData = Hi there
七、泛型委托
泛型委托和非泛型委托非常相似,不过类型参数决定了能接受什么样的方法。
(1)要声明泛型委托,在委托名称之后、委托参数列表之前的尖括号中放置类型参数列表。
(2)注意,有两个参数列表:委托形参列表和类型参数列表。
(3)类型参数的范围包括:
- 返回类型
- 形参列表
- 约束子句
如果代码给出了一个泛型委托的示例。在Main中,泛型委托MyDelegate使用string类型的实参实例化,并且使用PrintString方法初始化。
delegate void MyDelegate<T>(T value); //泛型委托class Simple
{static public void PrintString(string s) //方法匹配委托{Console.WriteLine(s);}static public void PrintUpperString(string s) //方法匹配委托{Console.WriteLine($"{s.ToUpper()}");}}class Program
{static void Main(){var myDel = new MyDelegate<string>(Simple.PrintString);//创建委托的实例myDel += Simple.PrintUpperString; //添加方法myDel("Hi There.");}
}
这段代码产生了如下的输出:
Hi There.
HI THERE.
八、泛型接口
泛型接口允许我们编写形参和接口成员返回类型是泛型类型参数的接口。泛型接口的声明和非泛型接口的声明差不多,但是需要在接口名称之后的尖括号中放置类型参数。
例如,如下代码声明了叫作IMyIfc的泛型接口。
- 泛型类Simple实现了泛型接口
- Main实例化了泛型类的两个对象,一个是int类型,另外一个是string类型。
(一)使用泛型接口的示例
如下示例演示了泛型接口的另外两项能力:
- 与其他泛型相似,用不同类型参数实例化的泛型接口的实例是不同的接口;
- 我们可以在非泛型类型中实现泛型接口。
例如,下面的代码与前面的示例相似,但在这里,Simple是实现泛型接口的非泛型类。其实,它实现了两个IMyIfc实例。一个实例使用int类型实例化,而另一个使用string类型实例化。
(二)泛型接口的实现必须唯一
实现泛型类型接口时,必须保证类型实参的组合不会在类型中产生两个重复的接口。
例如,在下面的代码中,Simple类使用了两个ImyIfc接口的实例化。
- 第一个是构造类型,使用类型int进行实力化。
- 第二个有一个类型参数,但不是实参。
对于泛型接口,使用两个相同接口本身并没有错,问题在于这么做会产生一个潜在的冲突,因为如果把int作为类型实参来替代第二个接口中的S的话,Simple可能会有两个相同类型的接口,二这是不允许的。
interface IMyIfc<T>
{T ReturnIt(T inValue);
}class Simple<S> : IMyIfc<int>, IMyIfc<S> //错误
{public int ReturnIt(int inValue) //实现第一个接口{return inValue;}public S ReturnIt(S inValue) //实现第二个接口{ //入过它不是int类型的return inValue; //将和第一个接口一样}
}
说明:泛型接口的名字不会和非泛型冲突。例如在前面代码中我们还可以声明一个名为ImyIfc的非泛型接口。
相关文章:

C#之泛型
目录 一、概述 二、C#中的泛型 继续栈的示例 三、泛型类 (一)声明泛型类 (二)创建构造类型 (三)创建变量和实例 (四)比较泛型和非泛型栈 四、类型参数的约束 (一…...
Scrum敏捷开发管理流程+scrum工具免费
Leangoo领歌它覆盖了敏捷项目研发全流程,包括小型团队Scrum敏捷开发,规模化敏捷SAFe,Scrum of Scrums大规模敏捷。它提供了灵活的敏捷模板和极致的协作体验,可以让团队快速上手,快速落地Scrum敏捷开发管理。 首先建立产…...

【操作系统基础】Linux 中 /var/log/ 文件夹下通常有哪一些文件?分别的作用是什么?
在Linux系统中,/var/log/ 文件夹通常包含了系统日志文件,这些文件记录了系统的各种活动和事件,以便管理员进行故障排除和监控。 以下是/var/log/ 文件夹中常见的一些文件及其含义: auth.log:记录系统认证和授权相关的…...

【构造】CF1758 C
Problem - 1758C - Codeforces 题意: 思路: 思路: #include <bits/stdc.h>#define int long longusing namespace std;const int mxn2e510; const int mxe2e510;int N,x; int ans[mxn];void solve(){cin>>N>>x;if(N%x!0)…...

【etcd】docker 启动单点 etcd
etcd: v3.5.9 etcd-browser: rustyx/etcdv3-browser:latest 本文档主要描述用 docker 部署单点的 etcd, 用 etcd-browser 来查看注册到 etcd 的 key 默认配置启动 docker run -d --name ai-etcd --networkhost --restart always \-v $PWD/etcd.conf.yml:/opt/bitn…...

【单链表OJ题:反转链表】
题目来源 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* reverseList(struct ListNode* head){struct ListNode* current head;struct ListNode* newnode NULL;while(current!NULL){struc…...

Unity UGUI的LayoutRebuilder的介绍及使用
Unity UGUI的LayoutRebuilder的介绍及使用 1. 什么是LayoutRebuilder? LayoutRebuilder是Unity UGUI中的一个组件,用于自动重建布局。它可以根据UI元素的变化,自动调整其子元素的位置和大小,以保持布局的一致性。 2. LayoutReb…...

深刻理解python特性-列表推导式和生成器表达式
哈喽大家好,今天给大家介绍两个Python中特性-列表推导式和生成器表达式 今天我想向你介绍python语言的两个非常有用的特性:列表推导式和生成器表达式。这两个特性都可以让你用一行简洁的代码来创建一个序列,而不需要写循环或者函数。但是它们…...

Sentinel dashboard的使用;Nacos保存Sentinel限流规则
Sentinel dashboard的使用 往期文章 Nacos环境搭建Nacos注册中心的使用Nacos配置中心的使用Sentinel 容灾中心的使用 参考文档 Sentinel alibaba/spring-cloud-alibaba Wiki GitHub 限流结果 下载sentinel-dashboard github地址:Sentinel/sentinel-dashboar…...

vue学习之插值表达式{{}}与显示数据(v-text和v-html)
1. 记得导入 <!-- 在线导入 --> <!-- 开发环境版本,包含了用帮助的命令行警告 --> <script src"https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- 生产环境版本,优化了尺寸和速度 --> <scri…...

2,认识N(logN)的排序【p3】
认识N( logN} 的排序 2.1归并排序2.1.1代码实现归并排序2.1.1.1自己c实现归并排序2.1.1.2gptc实现归并排序2.1.1.3总结2.1.1.4比较行为 2.1.2归并排序使用master公式2.1.3归并排序的扩展2.1.3.1小和问题2.1.3.2逆序对问题 2.2快排、荷兰国旗问题2.2.1问题一2.2.2问题二(荷兰国旗…...

华为机考--服务失效判断--带答案
新2023年华为OD真题机考题库大全-带答案(持续更新)or2023华为OD统一考试(AB卷)题库清单-带答案(持续更新) 题目描述 某系统中有众多服务,每个服务用字符串(只包含字母和数字,长度<…...

C++对C的加强(全)
目录 C对C的加强 命名空间 为什么要使用命名空间 怎么使用命名空间 命名空间的定义 命名空间的使用 使用域解析符 :: 使用using声明 内联命名空间 嵌套命名空间 随时将新的成员加入命名空间 命名空间中 函数的声明和实现分开 无名命名空间 命名空间取别名 使用u…...

ES6及以上新特性
ES6(ECMAScript 2015)及以上版本引入了许多新特性,每个版本都有不同的增强和改进。以下是 ES6 及以上版本的新特性的详细描述: ES6(ECMAScript 2015): let 和 const 声明:引入块级作…...

伦敦金在非农双向挂单
对伦敦金投资有一定经验的投资者都知道,在非农时期,伦敦金市场会出现很大的波动,那么我们如何才能抓住这些波动呢?答案是很难的。但是,有些投资者在多年实践中发明了一种双向挂单的方法,这里和大家一切分享…...

【C语言】—— __attribute__((fallthrough))
__attribute__((fallthrough)) 是一个在编译器中使用的特性,用于指示在 switch 语句中的 case 标签中故意省略 break 语句时的意图。它告诉编译器,故意省略 break 是有意为之,而不是出现了错误或遗漏。 当使用 switch 语句时,通常…...

【深度学习】生成对抗网络Generative Adversarial Nets
序言 本文是GAN网络的原始论文,发表于2014年,我们知道,对抗网络是深度学习中,CNN基础上的一大进步; 它最大的好处是,让网络摆脱训练成“死模型”到固定场所处去应用,而是对于变化的场景…...

【深度学习】从现代C++中的开始:卷积
一、说明 在上一个故事中,我们介绍了机器学习的一些最相关的编码方面,例如 functional 规划、矢量化和线性代数规划。 本文,让我们通过使用 2D 卷积实现实际编码深度学习模型来开始我们的道路。让我们开始吧。 二、关于本系列 我们将学习如何…...

金融数学方法:蒙特卡洛模拟
1.方法介绍 蒙特卡洛模拟是一种基于概率和统计的数值计算方法,用于解决各种复杂问题。它以概率统计为基础,通过随机抽样和重复实验的方式进行模拟,从而得到问题的近似解。它的基本思想是通过大量的随机样本来近似计算问题的解…...

vue 文件扩展名中 esm 、common 、global 以及 mini 、 dev 、prod 、runtime 的含义
vue 文件扩展名中 esm 、common 、global 以及 mini 、 dev 、prod 、runtime 的含义 vue.js 直接用在 script 标签中的完整版本(同时包含编译器 compiler 和运行时 runtime),可以看到源码,适用于开发环境。 这个版本视图可以写在…...

微服务契约测试框架Pact-Python实战
Pact是一个契约测试框架,有多种语言实现,本文以基于pact-python探究契约测试到底是什么?以及如何实现 官网:自述文件 |契约文档 (pact.io) 契约测试步骤 1、为消费者写一个单元测试,让它通过,并生成契约…...

Linux 给用户 赋某个文件夹操作的权限(实现三权分立)
Linux 给用户 赋某个文件夹操作的权限 这里用的ubuntu16.04 一、配置网站管理员 linux文件或目录的权限分为,读、写、可执行三种权限。文件访问的用户类别分为,文件创建者、与文件创建者同组的用户、其他用户三类。 添加用户 useradd -d /var/www/htm…...

【C++入门到精通】C++入门 —— 类和对象(初始化列表、Static成员、友元、内部类、匿名对象)
目录 一、初始化列表 ⭕初始化列表概念 ⭕初始化列表的优点 ⭕使用场景 ⭕explicit关键字 二、Static成员 ⭕Static成员概念 🔴静态数据成员: 🔴静态函数成员: ⭕使用静态成员的优点 ⭕使用静态成员的注意事项 三、友…...

“深入理解Spring Boot:从入门到高级应用“
标题:深入理解Spring Boot:从入门到高级应用 摘要:本文将介绍Spring Boot的基本概念、原理和使用方法,并探讨如何在实际开发中充分发挥Spring Boot的优势。通过详细的示例代码,读者将能够深入理解Spring Boot的各个方…...

Apache Spark 的基本概念和在大数据分析中的应用
Apache Spark是一种快速、通用、可扩展的大数据处理引擎,用于大规模数据处理任务,如批处理、交互式查询、实时流处理、机器学习和图形处理等。它的主要特点包括: 1. 速度:Spark使用In-Memory计算技术,将计算结果存储在…...

Debian LNMP架构的简单配置使用
一、LNMP简介 LinuxNginxMysqlPHP组成的网站架构,常用于中小型网站服务。 二、环境 Debian 6.1.27-1kali1 (2023-05-12) Nginx/1.22.1 10.11.2-MariaDB(mysql) PHP 8.2.7 (Debian 6.1.27包含以上包,直接使用即…...

CAN转EtherNet/IP网关can协议破解服务
JM-EIP-CAN 是自主研发的一款 ETHERNET/IP 从站功能的通讯网关。该产品主要功能是将各种 CAN 总线和 ETHERNET/IP 网络连接起来。 本网关连接到 ETHERNET/IP 总线中做为从站使用,连接到 CAN 总线中根据节点号进行读写。 技术参数 ETHERNET/IP 技术参数 网关做为 …...

最适合新手的Java项目/SpringBoot+SSM项目《苍穹外卖》/项目实战、笔记(超详细、新手)[持续更新……]
小知识 软件设计中提到的UI设计中的UI是什么意思? 在软件设计中,UI设计中的UI是User Interface的简称,即用户界面。UI设计是指对软件的人机交互、操作逻辑、界面美观的整体设计。好的UI设计可以让软件变得有个性有品位,同时让操作…...

CloudDriver一款将各种网盘云盘挂在到电脑本地变成本地磁盘的工具 教程
平时我们的电脑可能由于大量的文件资料之类的导致存储空间可能不够,所以我们可以选择将网盘我们的本地磁盘用来存放东西。 CloudDrive 是一款可以将 115、阿里云盘、天翼云盘、沃家云盘、WebDAV 挂载到电脑中,成为本地硬盘的工具,支持 Window…...

行为型模式之中介者模式
中介者模式(Mediator Pattern) 中介者模式是一种行为型设计模式,旨在通过封装一系列对象之间的交互方式,使其能够独立地进行通信。 中介者模式的核心思想是将对象之间的直接通信改为通过一个中介者对象来进行间接通信,…...