第壹章第15节 C#和TS语言对比-泛型
C#提供了泛型的完整支持,不仅在编译时,运行时仍然保留泛型的类型信息,同时提供了更加丰富的泛型约束和更加全面的协变逆变支持。TS的泛型,在语法表现形式上,和C#差不多,但本质上两者是不一样的。TS的泛型,和Java一样,使用类型擦除机制,泛型只存在于编译时,在运行时,泛型的类型信息会被移除。
一、泛型究竟有啥意义
1.1 泛型应用的经典案例
正常情况下,我们还没真正懂泛型的时候,已经大量使用到(注意,是使用,不是定义),比如List、Func、Repository<MyClass,long>等等。下面是一个经典例子,大家参考一下,我觉得要到自己去封装一个泛型仓储时,才会真正领会到泛型的强大,这部分内容要到AspNetCore一章。下例中,假设我们需要创建一个数据存储类,它可以存储和检索任何类型的数据,我们使用泛型来实现这个需求。
//1、C#实现==========================================================================
//定义类时,使用泛型(类型的占位符)
public class Storage<T>
{//存储数据的字段_dataprivate T _data;//保存数据的方法public void Store(T data){_data = data;Console.WriteLine("Data stored: " + data);}//获取数据的方法public T Retrieve(){Console.WriteLine("Data retrieved: " + _data);return _data;}
}
//实例化类时,确定泛型的具体类型
public class Program
{public static void Main(){//保存和读取整数类型数据Storage<int> intStorage = new Storage<int>();intStorage.Store(42);int retrievedInt = intStorage.Retrieve();//保存和读取字符串类型数据Storage<string> stringStorage = new Storage<string>();stringStorage.Store("Hello, World!");string retrievedString = stringStorage.Retrieve();}
}//2、TS实现==========================================================================
class Storage<T> {//存储数据的字段_dataprivate data: T;//保存数据的方法store(data: T): void {this.data = data;console.log("Data stored: " + data);}//读取数据的方法retrieve(): T {console.log("Data retrieved: " + this.data);return this.data;}
}
//保存和读取整数类型数据
const intStorage = new Storage<number>();
intStorage.store(42);
const retrievedInt = intStorage.retrieve();
//保存和读取字符串类型数据
const stringStorage = new Storage<string>();
stringStorage.store("Hello, World!");
const retrievedString = stringStorage.retrieve();
1.2 泛型的作用总结
- 上例中我们进行了整数和字符串的保存和读取,实际上可以进行任意类型数据的保存和读取。如果没有泛型,我们需要定义StorageInt、StorageString等等超多个class,如果方法逻辑变了,还得一个个修改。减少代码重复、增强类型安全,这是泛型的最大意义,即使像TS、Java这样的类型擦除机制的泛型,都能实现。
- 实际开发中,除了大量使用到框架为我们定义好的泛型类型,比如语言框架提供的List、Array、Action、Func等,也有系统框架为提供的,比如ABP的Repository、Vue的defineProps()等等。我们自己也时常会定义自己的泛型,特别是在有数据传输场景,由于无法提前获知数据的具体类型,通常需要封装一个通用方法,比如使用TS开发前端应用时,一般需要使用泛型二次封装一下axios。
- 有人说,泛型是类型的参数,非常对,但不好理解。我觉得,说泛型是类型的占位符,会更易理解。比如上例中的_data字段,我们定义时,无法确定具体类型,那就先用一个符号代替,用的时候,再确定具体类型。这个占位符可以是本例的T,也可以是V,也可以是TValue,它只是一个占位的符号。
- 补充说一下类型擦除:Java和TS的泛型,都使用了类型擦除机制,泛型只存在于编译时,运行时泛型会被移除。这会造成什么影响呢?举个例子,由于泛型类型被移除,List和List,在运行时,通过反射获取类型时,结果是一样的,无法区分,这会在一定程度上限制反射的能力,同时也增加了一些不可预测的运行时类型错误的风险。总之,C#的泛型肯定是可以将Java按在地上摩擦的,除此之后,还有许多语言特性,一样有此能力,比如反射、LINQ、属性、事件,async/await…
二、泛型类、接口和方法
2.1 泛型类的定义和使用
2.1.1 C#中定义和使用泛型类
//定义泛型类
public class GenericClass<T>
{private T _value; //字段类型public GenericClass(T value) //构造方法的参数{_value = value;}public T GetValue() //方法返回值{return _value;}public void SetValue(T value) //方法参数{_value = value;}
}
//使用泛型类
GenericClass<int> intInstance = new GenericClass<int>(10);
int value = intInstance.GetValue(); // 10
2.1.1 TS中定义和使用泛型类
//定义泛型类
class GenericClass<T> {private value: T;constructor(value: T) {this.value = value;}getValue(): T {return this.value;}setValue(value: T): void {this.value = value;}
}
//使用泛型类
let intInstance = new GenericClass<number>(10);
let value = intInstance.getValue(); // 10
2.2 泛型方法的定义和使用
2.2.1 C#中定义和使用泛型方法
//定义泛型方法
public class GenericMethods
{public T GenericMethod<T>(T parameter){return parameter;}
}
//使用泛型方法
GenericMethods gm = new GenericMethods();
int intValue = gm.GenericMethod<int>(5); // 5
string stringValue = gm.GenericMethod<string>("hello"); // hello
2.2.2 TS中定义和使用泛型方法
//定义泛型方法
class GenericMethods {genericMethod<T>(parameter: T): T {return parameter;}
}
//使用泛型方法
let gm = new GenericMethods();
let intValue = gm.genericMethod<number>(5); // 5
let stringValue = gm.genericMethod<string>("hello"); // hello
2.3 泛型接口的定义和使用
2.3.1 C#中定义和使用泛型接口
//定义泛型接口=======================================================================
public interface IGenericInterface<T>
{T GetValue();void SetValue(T value);
}//实现泛型接口时,类依然是泛型类,接口的类型依然没有确定===============================
//类的泛型T和接口泛型T是对应着的
public class GenericClass<T> : IGenericInterface<T>
{private T _value;public GenericClass(T value){_value = value;}public T GetValue(){return _value;}public void SetValue(T value){_value = value;}
}//实现泛型接口时,并确定接口具体的类型================================================
public class SpecificClass : IGenericInterface<string>
{private string _value;public GenericClass(string value){_value = value;}public string GetValue(){return _value;}public void SetValue(string value){_value = value;}
}//实现多个泛型接口===================================================================
public class MultipleRepository<T> : IRepository<T>, IAnotherRepository<T>{...}
2.3.2 TS中定义和使用泛型接口
//定义泛型接口=======================================================================
interface IGenericInterface<T> {getValue(): T;setValue(value: T): void;
}//实现泛型接口时,类依然是泛型类,接口的类型依然没有确定===============================
//类的泛型T和接口泛型T是对应着的
class GenericClass<T> implements IGenericInterface<T> {private value: T;constructor(value: T) {this.value = value;}getValue(): T {return this.value;}setValue(value: T): void {this.value = value;}
}//和C#一样,实现泛型接口时,也可以确定接口的类型======================================
public class SpecificClass implements IGenericInterface<string>{...}//实现多个泛型接口===================================================================
class MultipleRepository<T> implements IRepository<T>, IAnotherRepository<T> {...}
2.4 泛型约束
2.4.1 C#中的泛型约束
//1、T必须是一个值类型,包括结构和枚举================================================
public class StructConstraint<T> where T : struct
{public T Value { get; set; }
}//2、T必须是一个引用类型,包括类、接口、委托或数组=====================================
public class ClassConstraint<T> where T : class
{public T Value { get; set; }
}//3、T必须有一个无参构造函数==========================================================
public class NewConstraint<T> where T : new()
{public T CreateInstance(){return new T();}
}//4、T必须是指定基类的派生类==========================================================
public class BaseClass {}
public class BaseClassConstraint<T> where T : BaseClass
{public T Value { get; set; }
}//5、T必须实现指定的接口=============================================================
public interface IMyInterface {}
public class InterfaceConstraint<T> where T : IMyInterface
{public T Value { get; set; }
}//7、多重约束=======================================================================
public class MultipleConstraints<T> where T : class, IMyInterface, new()
{public T CreateInstance(){return new T();}
}
2.4.2 TS中的泛型约束
TS的类型约束和C#差异比较大,更加灵活。差异原因,主要是它们的类型系统不一样。
//1、类型约束========================================================================
//T必须包含指定字面量对象、接口或类的属性(类型兼容)
//1.1 通过字面量对象,传入的参数必须包含length属性-----------------------
function logLength<T extends { length: number }>(arg: T): void {console.log(arg.length);
}
logLength("hello"); // 输出: 5
logLength([1, 2, 3]); // 输出: 3
//调用logLength时,省略了泛型,等价于:logLength<string>("hello")//1.2 通过接口来实现---------------------------------------------------
interface Lengthwise {length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length);return arg;
}
loggingIdentity({ length: 10, value: 3 });//1.3 通过类来实现-----------------------------------------------------
//只要类型能够兼容就可以,详见多态,不举例了//2、联合类型约束====================================================================
//T必须是多个类型之一
function combine<T extends number | string>(a: T, b: T): T {if (typeof a === 'number' && typeof b === 'number') {return (a + b) as T;}if (typeof a === 'string' && typeof b === 'string') {return (a + b) as T;}throw new Error("Invalid arguments");
}
console.log(combine(1, 2)); // 输出: 3
console.log(combine("hello", " world")); // 输出: "hello world"//3、带有索引签名的约束==============================================================
//keyof接收一个对象类型,生成其键名的联合类型
//下列中,keyof obj,生成的是"a"|"b"|"c"
function getProperty<T, K extends keyof T>(obj: T, key: K) {return obj[key];
}
let obj = { a: 1, b: 2, c: 3 };
console.log(getProperty(obj, "a")); // 输出: 1
2.5 泛型默认值(C#)和默认类型(TS)
2.5.1 C#中的泛型【默认值】
使用default关键字来为泛型类型参数提供默认值。default关键字根据类型参数是值类型还是引用类型,返回相应的默认值。值类型,返回值类型的默认值,如int为0,bool为false;引用类型返回null
public class GenericClass<T>
{private T _value;public GenericClass(){_value = default(T); // 使用default关键字设置默认值}public T GetValue(){return _value;}}public class Program
{public static void Main(){// 对于值类型GenericClass<int> intInstance = new GenericClass<int>();Console.WriteLine(intInstance.GetValue()); // 输出: 0// 对于引用类型GenericClass<string> stringInstance = new GenericClass<string>();Console.WriteLine((stringInstance.GetValue() ?? "null")); // 输出:null}
}
2.5.2 TS中的泛型【默认类型】
TS没有默认值的概念,但有一个默认类型。
class GenericClass<T = string> {private value: T;constructor(value: T) {this.value = value;}getValue(): T {return this.value;}setValue(value: T): void {this.value = value;}
}// 使用默认类型
let defaultInstance = new GenericClass("Hello");
console.log(defaultInstance.getValue()); // 输出: Hello// 显式提供类型参数
let numberInstance = new GenericClass<number>(42);
console.log(numberInstance.getValue()); // 输出: 42
三、泛型的协变和逆变
C#中的协变和逆变是两个非常抽象的概念,先硬记住下面两个用法,至于如何定义,试过两次,没绕出来,放弃了,反正实际开发过程中,从来没有自己定义过。本质上,它是泛型的多态,可以实现泛型类型之间的类型兼容。而TS的类型兼容是鸭子类型,灵活很多,不需要协变和逆变的特性。以下案例为C#中,协变和逆变的使用,不涉及定义环节。由于TS类型兼容的特性,下面代码稍微改下,放到TS中,也是成立的。
//协变,允许将泛型类型参数的子类型分配给泛型类型参数。泛型集合用到======================
IEnumerable<string> strings = new List<string> { "Hello", "World" };
IEnumerable<object> objects = strings; // 协变
foreach (var obj in objects)
{Console.WriteLine(obj); // 输出: Hello World
}//逆变,允许将泛型类型参数的基类型分配给泛型类型参数。泛型委托用到======================
Action<object> actObject = obj => Console.WriteLine(obj);
Action<string> actString = actObject; // 逆变
actString("Hello, World!"); // 输出: Hello, World!相关文章:
第壹章第15节 C#和TS语言对比-泛型
C#提供了泛型的完整支持,不仅在编译时,运行时仍然保留泛型的类型信息,同时提供了更加丰富的泛型约束和更加全面的协变逆变支持。TS的泛型,在语法表现形式上,和C#差不多,但本质上两者是不一样的。TS的泛型&a…...
苹果电脑下载vite包错
苹果电脑下载vite包错/Users/lili/.npm/_cacache/index-v5/c5/50/b451703d03b3802b9ee6b7ff2b0bde4de7f26830eb52c904d6911c137cf8包错解决方式 解决方式:sudo chown -R 501:20 "/Users/wangxin/.npm"...
自动化测试git的使用
git是一款分布式的配置管理工具。本文主要讲git如何在自动化测试中安装,上传及拉取下载代码。 1 、git 介绍 每天早上到公司,从公司的git服务器上下载最新的代码,白天在最新的代码基础上,编写新的代码,下班时把“代码…...
MyBatis系列四: 动态SQL
动态SQL语句-更复杂的查询业务需求 官方文档基本介绍案例演示if标签应用实例where标签应用实例choose/when/otherwise应用实例foreach标签应用实例trim标签应用实例[使用较少]set标签应用实例[重点]课后练习 上一讲, 我们学习的是 MyBatis系列三: 原生的API与配置文件详解 现在…...
Jenkins构建 Maven项目(微服务)并自动发布
前面讲了docker 安装Jenkins和gitlab代码管理工具,接下来我们讲一下Jenkins怎么构建 Maven项目。 1. 首先Jenkins配置下面3中工具类 首先是在本地安装三个jenkins自动配置相关的工具 1.1 JDK 由于我们使用docker来启动jenkins,其自带有jdk,…...
简单易用的多功能图床Picsur
什么是 Picsur ? Picsur 是一款易于使用、可自行托管的图片分享服务,类似于 Imgur,并内置转换功能。支持多种格式的图片,包括 QOI、JPG、PNG、WEBP(支持动画)、TIFF、BMP、GIF(支持动画…...
数据库-查询语句习题
SELECT Sname 姓 名,year of birth: 出生年,YEAR(GETDATE())-Sage BIRTHYEAR,LOWER(SNAME) SNAME --起别名 没有特殊字符不需要引号,有特殊字符要加引号;别名(解释作用显示给用户看)用空格或as连接 FROM STUDENT; --消除重复行 DI…...
进程间通信以及线程的同步互斥机制
1.进程间通信机制 常用的六种通信机制: 管道、消息队列、共享内存、信号灯集、信号、Socket 管道(Pipe)和无名管道(匿名管道): 管道是一种半双工的通信方式,数据只能单向流动,通常…...
优思学院|做车企的质量工程师转行跳槽能干嘛?
前言 质量工程师,是现代制造业和服务业中不可或缺的重要角色。他们负责制定和执行提高产品质量和优化业务流程的战略。这不仅涉及设立质量标准、开发测试系统,还包括记录生产过程中的问题并找到解决方案。尤其在汽车行业,由于对质量的高度要…...
ctfshow-web入门-命令执行(web53-web55)
目录 1、web53 2、web54 3、web55 1、web53 这里的代码有点不一样,说一下这两种的区别: (1)直接执行 system($c); system($c);这种方式会直接执行命令 $c 并将命令的输出直接发送到标准输出(通常是浏览器ÿ…...
【INTEL(ALTERA)】make: nios2-swexample-create:未找到命令
目录 说明 解决方法 说明 由于外部内存接口英特尔 Stratix 10 FPGA IP 出现问题,如果在 Windows 平台上使用英特尔 Quartus Prime Pro Edition Software v20.4 或更早版本的"使用软Nios处理器进行片上调试"选项,编译Nios II 片上处理器调试…...
一周刷爆leetcode!(b站视频)
文章目录 一、排序思想的题目二、使用步骤1. 一、排序思想的题目 跟着b站一周刷爆leetcode这个视频开始刷一下leetcode的题目 进行一下记录啥的 二、使用步骤 1. 315. 计算右侧小于当前元素的个数 代码如下: 写了一下暴力解法,没有通过 使用归并排序…...
1.xshell传不了文件输出0000如何解决.....2.k8s中metalLB文件内容
xshell传不了文件输出0000如何解决 centos版本 1,因为没有工具下载即可 yum -y install lrzszk8s中metalLB文件内容 2.metalLB文件内容 cat metallb-native.yaml apiVersion: v1 kind: Namespace metadata:labels:pod-security.kubernetes.io/audit: privilegedpod-securit…...
01- ES6语法
1.ES6相关概念 1.1 什么是ES6 1.1.1 简介 ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版。 ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 Ja…...
STM32MP135裸机编程:配置RCC,修改主频到1GHz
0 工具准备 STM32CubeMX v6.11.1 STM32CubeIDE v1.15 STM32CubeProgrammer v2.16.0 STM32MP13xx参考手册 STM32MP13勘误手册 STM32MP135AD数据手册 正点原子stm32MP135开发板 1 确认时钟源 本例使用的时钟源均由外部晶振提供,分别是24MHz的HSE、32.768KHz的LSE。原…...
观察 jvm 运行时数据区内存大小(native memory tracking)
jvm 运行时数据区 jvm 运行时数据区包括且不限于以下几个部分: 堆(heap): 用于存储对象实例和数组。堆内存的分配和释放由垃圾回收器进行管理。方法区(method area): 用于存储类的信息、静态变量、常量等。jdk 8 后方法区位于 metaspace。虚拟机栈(vm stack): 用于存储方法的…...
【论文阅读】-- 时间空间化:用于深度分类器训练的可扩展且可靠的时间旅行可视化
Temporality Spatialization: A Scalable and Faithful Time-Travelling Visualization for Deep Classifier Training 摘要1 引言2 动机3 问题定义4 方法论4.1 时空复合体4.2 复数约简 5 实验6 相关工作7 结论参考文献 摘要 时间旅行可视化回答了深度分类器的预测是如何在训练…...
Windows系统部署本地SQL_Server指引
Windows系统部署本地SQL_Server指引 此指引文档环境为Windows10系统,部署SQL_Server 2019为例,同系列系统软件安装步骤类似。 一、部署前准备; 下载好相关镜像文件;设备系统启动后,将不必要的软件停用,避…...
Aptos Builder Jam 亚洲首站|议程公布,无限畅想 Aptos 生态未来
作为一个新兴的 Layer1 公链,Aptos 自诞生之日起的理想便是 “A Layer 1 for everyone” 当 Web3 深陷熊市阴影之时,Aptos 奋力为开发者找到了全新的技术路径,正有 200 项目正在开发,并且已有大量 DeFi 项目落实部署工作ÿ…...
Vue3使用component动态展示组件
前言: 最近在研究gitHub中的一个项目并将与自己之前完成的项目进行结合,其中有一个功能就是需要使用根据不同的字段,渲染不同的组件,查阅资料发现可以使用component完成这个功能,在实现的过程中也会遇见一些坑&#x…...
保姆级教程:在Windows上用Cherry Studio和Grafana MCP服务打通本地监控数据(STDIO模式详解)
保姆级教程:在Windows上用Cherry Studio和Grafana MCP服务打通本地监控数据(STDIO模式详解) 你是否曾在调试大模型时,需要反复切换窗口查看服务器监控数据?或是苦恼于无法将Grafana的实时监控直接整合到AI对话流程中&a…...
三轴 MEMS 加速度传感器在工业预测性维护中的关键应用
1. 三轴MEMS加速度传感器如何成为工业设备的"听诊器" 想象一下医生用听诊器检查病人心跳的场景。三轴MEMS加速度传感器在工业领域扮演着类似的角色,只不过它"听诊"的对象换成了电机、风机这些设备。这个火柴盒大小的装置(303019mm&…...
Unity Input System手势识别避坑指南:为什么你的双指缩放总是不跟手?
Unity Input System手势识别避坑指南:为什么你的双指缩放总是不跟手? 当你在Unity中实现双指缩放功能时,是否遇到过这样的问题:用户手指明明在屏幕上流畅滑动,但画面却像卡顿了一样,或者缩放比例突然跳变&a…...
STM32F407定时器TIMER进阶:从PWM生成到输入捕获的实战应用
1. STM32F407定时器基础回顾与进阶方向 在开始深入探讨PWM生成和输入捕获之前,我们先快速回顾一下STM32F407定时器的基本特性。这款芯片内置了多达14个定时器,分为高级控制定时器、通用定时器和基本定时器三大类。其中通用定时器(TIM2-TIM5, TIM9-TIM14)…...
铜钟音乐:专注纯净听歌体验的终极免费音乐平台指南
铜钟音乐:专注纯净听歌体验的终极免费音乐平台指南 【免费下载链接】tonzhon-music 铜钟 (Tonzhon.com): 免费听歌; 没有直播, 社交, 广告, 干扰; 简洁纯粹, 资源丰富, 体验独特!(密码重置功能已回归) 项目地址: https://gitcode.com/GitHub_Trending/…...
ClawdBot个人AI助手5分钟快速部署:零基础搭建本地智能聊天机器人
ClawdBot个人AI助手5分钟快速部署:零基础搭建本地智能聊天机器人 1. 项目介绍 ClawdBot是一个可以在本地设备上运行的个人AI助手,基于vLLM提供后端模型能力。这个开源项目让用户能够快速搭建自己的智能聊天机器人,无需复杂的配置过程。 1.…...
离散状态观测器
-伺服(实用)A川伺服--模型追踪控制(末端低频振动抑制-pmsm 完全自己搭建,原理清晰,效果可靠,可实际验证包含: (1)详细原理性推导 (2)仿真基于离散化模型以及离…...
工业自动化场景信捷 PLC EtherNet/IP 转 TCP/IP 通信方案
EtherNet/IP转TCP/IP网关应用:信捷PLC工业自动化数据采集实战案例一、项目背景本次项目落地于国内某大型3C电子精密组装工厂,聚焦智能手机中框自动化组装产线,属于当前工业自动化领域高增速、高前景的主流场景,也是工业物联网落地…...
开箱即用:ANIMATEDIFF PRO预置镜像部署,快速开启AI视频创作
开箱即用:ANIMATEDIFF PRO预置镜像部署,快速开启AI视频创作 1. 为什么选择ANIMATEDIFF PRO镜像 如果你正在寻找一个能快速生成电影级AI视频的解决方案,ANIMATEDIFF PRO预置镜像可能是目前最省心的选择。这个基于AnimateDiff架构和Realistic…...
LangChain4j向量化实战避坑:OpenAI、本地模型、Qdrant选哪个?我的踩坑记录
LangChain4j向量化实战避坑指南:OpenAI、本地模型与Qdrant的深度抉择 当Java开发者尝试构建基于大语言模型的应用时,LangChain4j框架中的向量化组件往往成为技术栈选型的第一个分水岭。我在三个实际项目中分别尝试了不同组合方案后,发现每个…...
