C#之泛型

news/2024/5/1 9:46:22/文章来源:https://blog.csdn.net/m0_67296957/article/details/131917731

目录

一、概述

二、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的非泛型接口。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_337090.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

golangd\pycharm-ai免费代码助手安装使用gpt4-免费使用--[推荐]

golangd-ai免费代码助手安装使用,pycharm可以使用&#xff0c;估计只要是xx的ide都是可以使用这个插件 目前GPT4以及gpt的大规模使用&#xff0c;如何快速掌握以及在ide中快速使用的办法&#xff0c;今天安装一款golangd编辑器的插件已经使用 一、安装以及使用 1.在golangd中…

骨传导耳机是什么?为什么不用塞到耳朵里?

骨传导耳机其实就跟它的名字一样&#xff0c;用骨传导声音的耳机&#xff0c;整个声音传导过程都是开放双耳的&#xff0c;不接触耳膜&#xff0c;佩戴非常舒适的耳机。 为什么不需要塞进耳朵里&#xff0c;首先咱们要先知道骨传导的原理&#xff1a; 如上图所示&#xff0c;骨…

Linux环境搭建(XShell+云服务器)

好久不见啊&#xff0c;放假也有一周左右了&#xff0c;简单休息了下&#xff08;就是玩了几天~~&#xff09;&#xff0c;最近也是在学习Linux&#xff0c;现在正在初步的学习阶段&#xff0c;本篇将会简单的介绍一下Linux操作系统和介绍Linux环境的安装与配置&#xff0c;来帮…

七、用户画像

目录 7.1 什么是用户画像7.2 标签系统7.2.1 标签分类方式7.2.2 多渠道获取标签 7.3 用户画像数据特征7.3.1 常见的数据形式7.3.2 文本挖掘算法7.3.3 嵌入式表示7.3.4 相似度计算方法 7.4 用户画像应用 因此只基于某个层面的数据便可以产生部分个体面像&#xff0c;可用于从特定…

软件测试/测试开发丨Selenium环境安装与使用

Selenium 官方网站&#xff1a; www.selenium.dev/ 简介&#xff1a; 用于web浏览器测试的工具&#xff1b;支持的浏览器包括IE&#xff0c;Firefox&#xff0c;Safari&#xff0c;Chrome&#xff0c;Edge等&#xff1b;使用简单&#xff0c;可使用Java&#xff0c;Python等…

派森编程软件python好学吗,派森语言python干什么的

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;派森编程软件python有什么用&#xff0c;派森编程软件python好学吗&#xff0c;现在让我们一起来看看吧&#xff01; 1、python真的值得学吗&#xff1f; 不建议学python的原因&#xff1a; 1、语言性能差 对于C老手…

【Python】logging模块笔记

目录 日志级别 四个组件 记录器 处理器 处理器 格式化器 格式 用法1&#xff1a;小项目可以采用编程的方法 用法2&#xff1a;建议采用配置文件的方式 用法3&#xff1a; 字典配置 日志级别 #默认的日志输出为warning # 使用baseConfig() 来指定日志输出级别 # 同时&#x…

尚医通9:医院列表功能+GateWay网关

内容介绍 1、医院列表功能&#xff08;接口&#xff09; 4、医院列表功能&#xff08;前端&#xff09; 5、更新医院上线状态功能 6、医院详情 7、GateWay网关 8、医院排班管理需求分析 9、查看医院所有科室接口、前端 医院列表功能&#xff08;接口&#xff09; 接口…

空中出租车运营公司【Flewber Global】申请纳斯达克IPO上市

猛兽财经获悉&#xff0c;总部位于美国纽约的空中出租车运营公司Flewber Global Inc&#xff0c;近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯达克IPO上市&#xff0c;股票代码为&#xff08;FLYF&#xff09;,Flewber Global计划通…

英伟达 H100 vs. 苹果M2,大模型训练,哪款性价比更高?

M1芯片 | Uitra | AMD | A100 M2芯片 | ARM | A800 | H100 关键词&#xff1a;M2芯片&#xff1b;Ultra&#xff1b;M1芯片&#xff1b;UltraFusion&#xff1b;ULTRAMAN&#xff1b;RTX4090、A800;A100&#xff1b;H100&#xff1b;LLAMA、LM、AIGC、CHATGLM、LLVM、LLM、LLM…

基于深度强化学习的DQN模型实现自动玩俄罗斯方块游戏(附详细代码讲解)

一、DQN&#xff08;Deep Q-Network&#xff09;方法概述 DQN&#xff08;Deep Q-Network&#xff09;是一种强化学习方法&#xff0c;通过结合Q-learning算法和深度神经网络来解决强化学习问题。它是深度强化学习的里程碑之一&#xff0c;由DeepMind在2013年提出&#xff0c;被…

AI绘画StableDiffusion实操教程:可爱头像奶茶小女孩(附高清图片)

本教程收集于&#xff1a;AIGC从入门到精通教程汇总 今天继续分享AI绘画实操教程&#xff0c;如何用lora包生成超可爱头像奶茶小女孩 放大高清图已放到教程包内&#xff0c;需要的可以自取。 欢迎来到我们这篇特别的文章——《AI绘画StableDiffusion实操教程&#xff1a;可爱…

FPGA2-采集OV5640乒乓缓存后经USB3.0发送到上位机显示

1.场景 基于特权A7系列开发板&#xff0c;采用OV5640摄像头实时采集图像数据&#xff0c;并将其经过USB3.0传输到上位机显示。这是验证数据流能力的很好的项目。其中&#xff0c;用到的软件版本&#xff0c;如下表所示&#xff0c;基本的硬件情况如下。该项目对应FPGA工程源码…

VMware虚拟机中配置静态IP

目录 环境原因基础概念VMnet网络IPV4网络私有地址范围Vmnet8的作用网路通信的过程解决方法1&#xff1a;修改k8s组件重新启动解决方法2&#xff1a;配置静态IP系统网卡设置设置虚拟机网关修改虚拟机网卡 环境 本机系统&#xff1a;windows11虚拟机系统&#xff1a;CentOS-7-x8…

iOS - Apple开发者账户添加新测试设备

获取UUID 首先将设备连接XCode&#xff0c;打开Window -> Devices and Simulators&#xff0c;通过下方位置查看 之后登录(苹果开发者网站)[https://developer.apple.com/account/] &#xff0c;点击设备 点击加号添加新设备 填写信息之后点击Continue&#xff0c;并一路继续…

【EI/SCOPUS会议征稿】第四届机器学习与计算机应用国际学术会议(ICMLCA 2023)

ICMLCA 2023 第四届机器学习与计算机应用国际学术会议 2023 4th International Conference on Machine Learning and Computer Application 第四届机器学习与计算机应用国际学术会议(ICMLCA 2023)定于2023年10月27-29日在中国杭州隆重举行。本届会议将主要关注机器学习和计算…

iOS开发-CAShapeLayer与UIBezierPath实现微信首页的下拉菜单效果

iOS开发-CAShapeLayer与UIBezierPath实现微信首页的下拉菜单效果 之前开发中遇到需要使用实现微信首页的下拉菜单效果。用到了CAShapeLayer与UIBezierPath绘制菜单外框。 一、效果图 二、CAShapeLayer与UIBezierPath 2.1、CAShapeLayer是什么&#xff1f; CAShapeLayer继承自…

《Elasticsearch 源码解析与优化实战》第5章:选主流程

《Elasticsearch 源码解析与优化实战》第5章&#xff1a;选主流程 - 墨天轮 一、简介 Discovery 模块负责发现集群中的节点&#xff0c;以及选择主节点。ES 支持多种不同 Discovery 类型选择&#xff0c;内置的实现称为Zen Discovery ,其他的包括公有云平台亚马逊的EC2、谷歌…

shell中按照特定字符分割字符串,并且在切分后的每段内容后加上特定字符(串),然后再用特定字符拼接起来

文件中的内容&#xff0c;可以这么写&#xff1a; awk -F, -v OFS, {for(i1;i<‌NF;i){$i$i"_suffix"}}1 input.txt-F,&#xff1a;设置输入字段分隔符为逗号&#xff08;,&#xff09;&#xff0c;这将使awk按照逗号分割输入文本。-v OFS‘,’&#xff1a;设置输…

Banana Pi BPI-CM4 评测(计算模块 4),更快性能,旨在替换树莓派CM4

如果您正在寻找可靠的单板计算机来提升您的下一个项目&#xff0c;但无法找到满足您需求的 Raspberry Pi&#xff0c;请看看我是否可以提供帮助。在这篇详细的评论中&#xff0c;我将向您介绍 Banana Pi CM4&#xff0c;这是一款适用于各种任务的多功能且强大的解决方案。从经验…