• 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把中间语言生成本地代码所做的大量工作实现这些特性,对一个特性,风格、范式的理解都应该从上述点进行深入探索。

  • 相关阅读:
    Qt编写控件属性设计器12-用户属性
    C#中通过三边长判断三角形类型(三角形测试用例)
    C#中通过Selenium定位<a>标签的问题
    SharePoint自动化系列——Manage "Site Subscriptions" using PowerShell
    SharePoint API测试系列——Records.BypassLocks测试
    SharePoint API测试系列——对Recorded Item做OM操作(委托的妙用)
    放松时刻——C#分割字符串
    链表——PowerShell版
    栈——PowerShell版
    队列——PowerShell版
  • 原文地址:https://www.cnblogs.com/tuqunfu/p/14768626.html
Copyright © 2020-2023  润新知