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

posted @ 2022-03-06 22:07  tuqunfu  阅读(77)  评论(0编辑  收藏  举报