c#笔记-泛型

news2024/12/23 14:15:23

泛型方法

假设我们要编写一个方法,它可以获取任意类型数组中的最大值,
并返回该值。我们可能会这样写:

static int GetMax(int[] array)
{
    Array.Sort(array);
    return array[array.Length - 1];
}

这个方法可以实现我们的需求,但是它只能处理整数类型。
如果我们想获取字符串数组或浮点数数组中的最大值,就需要重新定义一个新的方法,如下:

static string GetMax(string[] array)
{
    Array.Sort(array);
    return array[array.Length - 1];
}

static double GetMax(double[] array)
{
    Array.Sort(array);
    return array[array.Length - 1];
}

这样做显然很冗余,而且每增加一种数据类型,就需要增加一个新的方法。
这样不仅增加了代码量,也增加了出错和维护的难度。

声明和使用

就像我们把变量作为参数一样,泛型是类型版本的参数。
泛型的“形参”称为类型占位符,泛型的“实参”称为类型参数。

声明一个泛型的形参声明方法是在函数名和函数参数之间写一对尖括号。
尖括号中你可以任意起名字,多个标识符使用逗号隔开。
然后在这个函数中,以及参数和返回值中,这个类型会被认为是有效的类型。

static T GetMax<T>(T[] array)
{
    Array.Sort(array);
    return array[array.Length - 1];
}

这个方法使用了一个类型参数 T 来代替具体类型。
这个方法可以获取任意类型数组中的最大值,

要使用一个泛型方法,我们需要在方法名后面使用尖括号指定类型实参。

int maxInt = GetMax<int>(new int[] { 1, 2, 3 }); // 获取整数数组中的最大值
string maxString = GetMax<string>(new string[] { "a", "b", "c" }); // 获取字符串数组中的最大值

这些调用都指定了类型实参为 int 或 string,并且传递了相应类型的数组作为参数。
这样,编译器就可以根据类型实参来检查类型安全,并且生成相应的执行代码。

类型推断

如果泛型方法在使用时,仅靠参数就能推断出所有的泛型参数,可以省略类型实参。
类似var,或default,或null,等值是无法推断出类型的。

_ = Enum.TryParse("Red", out Color color); // 省略了类型实参
_ = Enum.TryParse<Color>("Red", out var color2);//无法从var推断出类型

enum Color { Red, Green, Blue }

泛型类

泛型类是一种在类型声明时使用泛型类型参数的类,这样可以创建泛型字段和泛型属性。
泛型类的泛型参数数量也视为类型签名之一。签名不同的类可以共存。

class NonGenericList<T>
{
	T[] items;
	public T this[int index]
	{
		get => items[index];
		set => items[index] = value;
	}
	public T SetItems(params T[] items)
	{
		this.items = items;
		return items[0];
	}
}
class NonGenericList
{
	int[] items;
	public int this[int index]
	{
		get => items[index];
		set => items[index] = value;
	}
	public int SetItems(params int[] items)
	{
		this.items = items;
		return items[0];
	}
}

泛型类的构造器在声明时不需要(不能)加泛型类型参数。
但在调用时必须要加泛型类型参数,即使可以从参数中推断出来。

静态字段

不同泛型参数的泛型类之间是不同的。他们各自拥有自己的静态成员。

StaticValue<int>.Value = 10;
StaticValue<double>.Value = 20;
Console.WriteLine(StaticValue<int>.Value + StaticValue<double>.Value + StaticValue<string>.Value);//35

static class StaticValue<T>
{
	public static int Value = 5;
}

继承

泛型类可以继承其他类,也可以被继承。
在被继承时,需要有效的类型参数,可以是实际的类型,也可以是另一个泛型类的占位符。

class Base<T>
{

}

class Derive : Base<int>
{

}
class Derive<T> : Base<T>
{

}

泛型约束

泛型约束是指在定义泛型类型或方法时,对泛型类型参数进行一些限制,
使其具有更多的功能和操作。

如果不使用泛型约束,泛型类型参数会被假设为任何类型(object),
这样会导致只能使用所有类型都共有的功能(如ToString()方法),非常受限。
在这里插入图片描述
为了解决这个问题,我们可以为泛型类型参数添加一个或多个约束。
虽然这样会减少能兼容的类型范围,但是可以增加可用的操作。

如何声明约束

在函数的参数列表后,或泛型类的泛型类型参数后使用where关键字+:指定约束。
一个类型参数的多个约束之间使用逗号隔开。
为多个类型参数指定约束需要使用多个where,没有分隔符

class Foo<T> where T : class
{
	public void Boo<E, F>() where E : class, new() where F : class
	{

	} 
}

常用的约束

派生自类

如果要求泛型类型参数代表的类型派生自某一类型,
只需要直接写类型名。如果只要求是一个引用类型,那么使用class
显然,类型约束不能是密封类或静态类。类型名可以是另一个泛型类型参数

具有类型约束后,可以调用此类型有权限访问的实例方法和属性。

是结构

是一种值类型,使用struct
不能是可为空的值类型使用notnull
必须是非托管类型使用unmanaged

非托管类型是指不包含任何引用类型字段的值类型,
且它的字段的字段,一直递归下去,都不包含。

实现接口

实现接口只需要直接写接口名
但是正如继承时的语法一样,接口约束必须写在类约束或结构约束之后。

默认情况下,可以通过泛型类型参数隐式地调用接口的所有方法
也就是说可以直接从参数中调用接口的方法。
在多个接口存在同名方法时需要转化为接口再调用。
在这里插入图片描述
如果接口存在静态抽象方法,则可以通过泛型类型参数访问静态方法。

void Boo<T>(T t) where T : IInterface
{
	T.aa(); 
}
interface IInterface
{
	public static abstract void aa();
}

其他

new()约束:这个约束要求目标类型具有公共无参构造器。具有此约束的类型可以在函数中调用这个构造器。
new()约束必须放在约束列表的最后一个。
在这里插入图片描述
default约束:这个约束名字没起好,改为base约束更容易理解。他的实际作用是在重写或显式实现接口时
表示基方法或基类的约束,但class和struct约束除外

互斥的约束

不能同时存在的约束,要么是因为不兼容,要么是因为有包含关系。
不能兼容的约束:类约束和结构约束。
有包含的约束:unmanaged约束一定是一种struct约束,struct约束一定是一种new()约束。

这是我对你的表述的优化版本,希望对你有帮助。如果你还有其他需要,请告诉我。😊

协变逆变

泛型赋值

泛型列表是一种可以存储任意类型数据的集合。
你可以把它看作是一个可以动态扩展大小的数组。
例如,下面是两个泛型列表,一个存储字符串类型(string),一个存储对象类型(object)。

List<string> strList = new List<string>() { "123", "456", "789" };
List<object> objList = new List<object>(3);

现在我们想把第一个列表中的元素复制到第二个列表中。
我们可以用一个循环来遍历第一个列表,并把每个元素赋值给第二个列表对应的位置。

for (int i = 0; i < strList.Count&&i<objList.Count; i++)
{
	objList[i]= strList[i];
}

这样做是没有问题的,因为string类型是object的子类,所以可以把字符串类型赋值给对象类型。
但是如果我们不访问元素,直接把整个列表赋值给另一个列表呢?像这样:

List<string> strList = new List<string>() { "123", "456", "789" };
List<object> objList = strList;

这样是不行的。但是数组却可以这样做:

string[] strArr = new string[] { "123", "456", "789" };
object[] objArr = strArr;

objArr[0] = 12;

这里我们把一个字符串数组赋值给了一个对象数组,
然后试图把一个整数赋值给对象数组的第一个元素。
这在编译时是可以通过的,但实际运行起来,会出现类型转换失败的异常。

这是因为object数组实际上还是只能储存string类型的元素。不能接收int值。

协变和逆变

泛型具有严格的限制,像上面那种使用方式是不允许的。
而协变和逆变就是用来解开这种限制的。
但是协变和逆变也有限制,不能对类使用,只能对接口或委托使用。
因为他们不会储存字段,只有方法。

协变

协变:和谐的变化,子类随着时间流逝会当父类。泛型填的是子类,可以迎合父类。
使用out关键字修饰泛型占位符,表示输出。修饰的泛型占位符仅能作为返回类型。

interface IOut<out T> 
{
	T GetValue();
}
class COut : IOut<string>
{
	public string GetValue() => "hello";
}
IOut<string> out1 = new COut();
IOut<object> out2 = out1;

因为这个接口中只有输出的泛型。
在输出的时候把string作为object看待是没有问题的。

逆变

逆变:大逆不道的变化,要父类当子类。泛型填的是父类,却要迎合子类。
使用in关键字修饰泛型占位符,表示输入。修饰的泛型占位符仅能作为参数类型。

interface IIn<in T> {

	void SetValue(T value);
} 
class CIn : IIn<object>
{
	public void SetValue(object value) { }
}
IIn<object>in1=new CIn();
IIn<string> in2 = in1;

因为这个接口中只有参数的泛型,
在作为参数的时候,把string作为object看待是没有问题的。

而泛型类中的字段,是既能作为输入,又能作为输出的存在。
所以协变和逆变对泛型类不可用。

协变和逆变不能把泛型参数装箱拆箱

所有的值类型不能参与协变和逆变。

class CT<T> : IOut<T>, IIn<T>
{
	T IOut<T>.GetValue() => throw new NotImplementedException();
	void IIn<T>.SetValue(T value) => throw new NotImplementedException();
}
CT<Stream> ct = new CT<Stream>();
IIn<FileStream> Iin = ct;
IOut<object> Iout = ct;

Iout = new CT<int>();
IIn<int> Iin2 = new CT<object>();

在这里插入图片描述
这是因为值类型需要装箱和拆箱才能转换成对象类型或其他值类型,
而协变和逆变不会进行装箱和拆箱操作。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【虚拟机】VMware16保姆级安装教程

大家好&#xff0c;我是雷工&#xff01; 工作中需要用到各种各样的工控软件&#xff0c;有时候甚至需要不同版本的软件&#xff0c;但频繁装卸软件比较麻烦&#xff0c;而且像WinCC和博图软件对系统要求比较严格&#xff0c;卸载重装可能就出问题&#xff0c;此时就不得不重装…

MODBUS通讯程序设计流程图

MODBUS通讯一般来说是基于RS485电平的通讯&#xff0c;RS485是半双工&#xff0c;很多单片机工程师做的通讯不是稳定&#xff0c;主要原因是流程没有掌控好。 我以前也犯过和他们一样错误&#xff0c;觉得很容易。在QQ群里&#xff0c;有位老工程师和大家一样犯了同样的毛病。…

宣布!BNB Greenfield 测试网上的部署

我们很高兴地宣布&#xff0c;4EVERLAND Hosting 现在支持在 BNB Greenfield Testnet 上部署&#xff0c;为开发和托管去中心化应用程序 (dApp) 提供更具可扩展性和安全性的选项。 这种集成使项目团队能够轻松且经济地利用 4EVERLAND 在去中心化托管方面的尖端技术和专业知识&…

No module named ‘torch_geometric‘解决办法——PyG(PyTorch Geometric)安装教程

1、查询已安装的torch版本 一定要先查询自己的torch版本&#xff0c;也不同版本的依赖跟库都是不一样的。 import torch print("torch版本&#xff1a;",torch.__version__)输出结果&#xff1a; 2、安装依赖 2.1 进入官网下载依赖。 官网下载链接&#xff1a;…

word查找与替换

比如我有一个需求&#xff1a;将word中符号和中文为全角&#xff0c;但英文字符和数字为半角。 这种我们应该怎么办&#xff1f; 可以通过查找和替换解决。 按ctrl H即可看到查找替换框&#xff0c;注意word中查找替换不是ctrl F 查找 功能&#xff1a;在以下选项中查找 我们…

WiFi(Wireless Fidelity)基础(十)

目录 一、基本介绍&#xff08;Introduction&#xff09; 二、进化发展&#xff08;Evolution&#xff09; 三、PHY帧&#xff08;&#xff08;PHY Frame &#xff09; 四、MAC帧&#xff08;MAC Frame &#xff09; 五、协议&#xff08;Protocol&#xff09; 六、安全&#x…

Springboot +Flowable,各种历史信息如何查询(二)

一.简介 正在执行的流程信息是保存在以 ACT_RU_ 为前缀的表中&#xff0c;执行完毕的流程信息则保存在以 ACT_HI_ 为前缀的表中&#xff0c;也就是流程历史信息表。 假设有一个流程&#xff0c;流程图如下&#xff1a; 当这个流程执行完毕后&#xff0c;以 ACT_RU_ 为前缀的…

【ubuntu】alias命令

1 alias的作用 alias允许用户为命令创建简单的名称或缩写。如果需要在窗口输入复杂且较多的命令时&#xff0c;alias就可以减轻用户的记忆负担&#xff0c;发挥作用啦。 2 语法 &#xff08;1&#xff09;简单命令 alias [name”value”] 举例&#xff1a;alias ppwd 命令…

学习网络安全有哪些就业方向?网络安全就业前景!

在当下&#xff0c;网络安全是非常重要的&#xff0c;与我们的生活息息相关&#xff0c;因此网络安全课程也成为了热门技术&#xff0c;引发众多人学习。那么学习网络安全有哪些就业方向?我想很多人都不太了解&#xff0c;接下来我们一起来看看吧。 首先说一下什么是网络安全…

科罗拉多州立大学发布CSU-MLP模型,用随机森林预测中期恶劣天气

本文首发自 HyperAI超神经微信公众号~ 内容一览&#xff1a;近期&#xff0c;来自美国科罗拉多州立大学与 SPC 的相关学者联合发布了一个基于随机森林的机器学习模型 CSU-MLP&#xff0c;该模型能够对中期 (4-8天) 范围内恶劣天气进行准确预报。目前该成果刊已发表在《Weather …

进销存是什么意思?值得推荐的进销存软件有哪些?

在这个消费不断升级、直播带货和电商不断冲击的时代&#xff0c;实体店的生存变得越来越艰难&#xff0c;如何提高门店管理效率、降低管理成本、提升门店客流量&#xff0c;是实体店当下急需解决的几大问题。 进销存软件就是为了帮助实体店解决以上几大难题而设计的&#xff0c…

Xline v0.4.0: 一个用于元数据管理的分布式KV存储

Xline是什么&#xff1f;我们为什么要做Xline&#xff1f; Xline是一个基于Curp协议的&#xff0c;用于管理元数据的分布式KV存储。现有的分布式KV存储大多采用Raft共识协议&#xff0c;需要两次RTT才能完成一次请求。当部署在单个数据中心时&#xff0c;节点之间的延迟较低&a…

对STM32栈的理解Stack_Size EQU 0x00000400

对STM32栈的理解Stack_Size EQU 0x00000400 Stack_Size EQU 0x00000400表示什么意思可以通过查找flash内存的方式定位存储1.flash2.RAM内部 本人主要为个人参考网络及个人总结而来比较&#xff0c;如有雷同请告知&#xff0c;即刻删除 以下引用网上资料 理解堆和栈的区别 &…

RK3588 设备树pinctrl gpio子系统解析,解决GPIO无法正确拉高拉低的问题,RK3588设备树详解

一、RK3588设备树结构 firefly的官方说明文档RK3588gpio系统说明 function {group {rockchip,pin <bank gpio func &ref>;}; };其中&#xff0c;bank是所属的组&#xff0c;Core-3588J 有 5 组 GPIO bank&#xff1a;GPIO0-GPIO4&#xff0c;每组又以 A0-A7, B0-B…

DiffDock源码解析

DiffDock源码解析 数据预处理 数据输入方式 df pd.read_csv(args.protein_ligand_csv), 使用的是csv的方式输入&#xff0c; 格式&#xff1a; 不管受体还是配体&#xff0c; 输入可以是序列或者3维结构的文件 如果蛋白输入的是序列&#xff0c;需要计算蛋白的三维结构&am…

Type-C接口在显示器上有什么作用?Type-C 显示器方案介绍

一显示器的Type-C口&#xff0c;是未来显示器的接口的“终极形态”&#xff0c;未来显示器可以不要USB-A&#xff0c;不要HDMI&#xff0c;不要3.5音频&#xff0c;甚至不要DP口&#xff0c;但Type-C口一定会越来越多。 二显示器Type-C有什么用&#xff0c;有什么类型 1只可以…

大势智慧软硬件技术答疑第三期

1.重建大师6.0试用版&#xff0c;怎么导出DOM、DEM&#xff1f; 答&#xff1a;需要先生成三维模型&#xff0c;然后再提交产品选择DOM和DEM。 2.麻烦问下&#xff0c;修模出来贴的纹理图片&#xff0c;导出osgb后再打开就模糊了是什么情况&#xff1f; 答&#xff1a;拿高清…

OushuDB × 东方证券:数据仓库信创国产化最佳实践

前言&#xff1a;东方证券是一家综合类证券公司&#xff0c;成立于 1998 年&#xff0c;总部设在上海。经过 20 多年的发展&#xff0c;东方证券现有分支机构 177 家、管理 3200 亿资产&#xff0c;服务上亿客户。与一个大型金融机构相匹配的&#xff0c;正是东方证券当前管理的…

想利用业余时间当一名黑客?要具备什么能力,确定不来看看?

几十年前刚有小型电脑的时候&#xff0c;产生了一个由程序专家和部分网络名人所组成的文化社群。该社群的成员创造出了hacker这个词&#xff0c;也就是人们常说的“黑客”。这些黑客们建立了后来的Internet&#xff0c;以及发明了电脑的操作系统。 如果有人对这种文化做出了贡…

OpenResty(Nginx)示例

Nginx Nginx概念&#xff1a; 聊到Nginx,先简单讲一下Nginx的基本概念 Nginx是一个高性能的、开源的 Web 服务器和反向代理服务器软件&#xff0c;由 Igor Sysoev 开发。它可以作为 HTTP 服务器使用&#xff0c;也可以作为负载均衡器、HTTP 缓存、反向代理和邮件代理等其他功…