C#基础系列-泛型
一、前言
泛型是程序设计语言的一种风格和范式,允许在强类型语言中使用一些以后才在指定的类型,在实例化的时候指明这些类。在C#、JAVA、Delphi等语言中存在这种特性称为泛型;在ML、Scala中称为参数多态;在C++语言中称为模板。泛型是.NET2.0引入的一种风格和范式,提高了代码的重用性、类型安全(减少装箱和拆箱操作,使用Object类型、显示转换类型)。对于C#中泛型是如何在编译器中编译的、在CLR(公共运行时)中实现该特性?实际编码中如何使用泛型类、泛型类的作用等问题,对于这些问题进行一个小结。
二、使用
通过上述定义泛型是什么,我们通过一个泛型的Demo,查看其IL语言中是如何编写的,然后找出在CLR上怎么运行的。示例中创建一个统一的返回结果类,结果信息包含状态码、消息描述、结果的数据(如无结果返回null值或者空数组),这样我们就统一了返回结果(统一性、约定、配置可以减少代码量、易于管理等)。对返回的结果数据模型是不一样的,可以使用object类进行转换,但是存在性能上损失,类型转换,代码语义不清晰,使用泛型就带来不一样的效果。这个例子只是泛型应用中一点、把下述的代码使用工具翻译成IL语言看看其生成的中间语言的构成。
using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace AttributeDemo { class Program { static void Main(string[] args) {
// 定义泛型实例 var result = new Result<StudentInfo>(); result.Code = "100"; result.Message = "成功"; result.Data = new StudentInfo() { Id = 1, Name = "user" }; Console.WriteLine(result.Data.Name); Console.ReadKey(); } } // 定义泛型类、T类型参数、并且约定泛型约束,T必须是继承了Info类的参数 public class Result<T> where T:Info { public string Code { get; set; } public string Message { get; set; } public T Data { get; set; } } public interface Info { int Id { get; set; } string Name { get; set; } } public class StudentInfo : Info { public int Id { get; set; } public string Name { get; set; } } }
.class public auto ansi beforefieldinit AttributeDemo.Result`1<(AttributeDemo.Info) T> extends [mscorlib]System.Object { // Fields .field private string '<Code>k__BackingField' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .field private string '<Message>k__BackingField' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .field private !T '<Data>k__BackingField' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Methods .method public hidebysig specialname instance string get_Code () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x20b2 // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld string class AttributeDemo.Result`1<!T>::'<Code>k__BackingField' IL_0006: ret } // end of method Result`1::get_Code .method public hidebysig specialname instance void set_Code ( string 'value' ) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x20ba // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stfld string class AttributeDemo.Result`1<!T>::'<Code>k__BackingField' IL_0007: ret } // end of method Result`1::set_Code .method public hidebysig specialname instance string get_Message () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x20c3 // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld string class AttributeDemo.Result`1<!T>::'<Message>k__BackingField' IL_0006: ret } // end of method Result`1::get_Message .method public hidebysig specialname instance void set_Message ( string 'value' ) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x20cb // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stfld string class AttributeDemo.Result`1<!T>::'<Message>k__BackingField' IL_0007: ret } // end of method Result`1::set_Message .method public hidebysig specialname instance !T get_Data () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x20d4 // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld !0 class AttributeDemo.Result`1<!T>::'<Data>k__BackingField' IL_0006: ret } // end of method Result`1::get_Data .method public hidebysig specialname instance void set_Data ( !T 'value' ) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x20dc // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stfld !0 class AttributeDemo.Result`1<!T>::'<Data>k__BackingField' IL_0007: ret } // end of method Result`1::set_Data .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x20e5 // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Result`1::.ctor // Properties .property instance string Code() { .get instance string AttributeDemo.Result`1::get_Code() .set instance void AttributeDemo.Result`1::set_Code(string) } .property instance string Message() { .get instance string AttributeDemo.Result`1::get_Message() .set instance void AttributeDemo.Result`1::set_Message(string) } .property instance !T Data() { .get instance !0 AttributeDemo.Result`1::get_Data() .set instance void AttributeDemo.Result`1::set_Data(!0) } } // end of class AttributeDemo.Result`1
中间语言生成Result<T>类,编译器只为Result<T>类型产生“泛型版”的IL代码与元数据——并不进行泛型的实例化,还不是具体的类型,T在中间只充当占位符,并且T带着Info类的约束信息,这就是代码在编译器编译阶段生成的中间语言,在编译阶段生成IL所做的操作,相当于一份“模板”的概念,所有与类型参数有关都是T来代替。
.class private auto ansi beforefieldinit AttributeDemo.Program extends [mscorlib]System.Object { // Methods .method private hidebysig static void Main ( string[] args ) cil managed { // Method begins at RVA 0x2050 // Code size 78 (0x4e) .maxstack 5 .entrypoint IL_0000: newobj instance void class AttributeDemo.Result`1<class AttributeDemo.StudentInfo>::.ctor() // 中间语言对泛型实例生成的结构 IL_0005: dup IL_0006: ldstr "100" IL_000b: callvirt instance void class AttributeDemo.Result`1<class AttributeDemo.StudentInfo>::set_Code(string) IL_0010: dup IL_0011: ldstr "成功" IL_0016: callvirt instance void class AttributeDemo.Result`1<class AttributeDemo.StudentInfo>::set_Message(string) IL_001b: dup IL_001c: newobj instance void AttributeDemo.StudentInfo::.ctor() IL_0021: dup IL_0022: ldc.i4.1 IL_0023: callvirt instance void AttributeDemo.StudentInfo::set_Id(int32) IL_0028: dup IL_0029: ldstr "user" IL_002e: callvirt instance void AttributeDemo.StudentInfo::set_Name(string) IL_0033: callvirt instance void class AttributeDemo.Result`1<class AttributeDemo.StudentInfo>::set_Data(!0) IL_0038: callvirt instance !0 class AttributeDemo.Result`1<class AttributeDemo.StudentInfo>::get_Data() IL_003d: callvirt instance string AttributeDemo.StudentInfo::get_Name() IL_0042: call void [mscorlib]System.Console::WriteLine(string) IL_0047: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() IL_004c: pop IL_004d: ret } // end of method Program::Main .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x20aa // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Program::.ctor } // end of class AttributeDemo.Program
通过在Main函数中创建泛型实例,在IL中可以观察到编译器编译成“泛型”的中间语言,并没有具体的实例化其传入的参数,因为在第一阶段所编译的是“泛型”的IL代码和元数据,在本机运行的时候(CLR)中当JIT编译器第一次遇到AttributeDemo.Result`1<class AttributeDemo.StudentInfo>时,将用StudentInfo替换“范型版”IL代码与元数据中的T——进行泛型类型的实例化。总结就是泛型是通过第一阶段编译器编译成“泛型”的IL和元数据;第二CLR运行时提供对“泛型”IL和元数据的支持在JIT中替换成类型参数实例,“复制”一份代码生成本机代码。CLR为所有类型参数为“引用类型”的泛型类型产生同一份代码;但是如果类型参数为“值类型”,对每一个不同的“值类型”,CLR将为其产生一份独立的代码。因为实例化一个引用类型的泛型,它在内存中分配的大小是一样的,但是当实例化一个值类型的时候,在内存中分配的大小是不一样的。
通过上述的内容,我们知道了泛型是因为在IL和CLR做了大量的工作,那么如何使用泛型,泛型有哪些类型、那些约束?
通过上图可以大概知道泛型的使用内容,为什么要使用泛型,在代码上的复用性、在类型转换中的安全性;泛型的类型(类、结构、接口、委托、方法);泛型约束相当于权利与义务,权利是通过约束让泛型占位符拥有使用基类的属性和方法,义务是类型参数必须是基类或者其子类。泛型的协变和逆变参考上一篇内容。
三、总结
从泛型的IL和CLR角度查看其如何实现代码的复用的,类型安全的。对于泛型的基本内容了解泛型的使用主要是泛型类型和泛型约束、泛型的协变和逆变,这些都是为了更好的使用泛型,具体使用比如返回结果类的定义,List<T>集合类等。联想到反射、动态语言,知道第一阶段编译器编译成IL、第二阶段CLR或DLR中JIT把中间语言生成本地代码所做的大量工作实现这些特性,对一个特性,风格、范式的理解都应该从上述点进行深入探索。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?