C# basic family - Generic

Posted by BenMo on Sun, 06 Mar 2022 15:24:31 +0100

1, Foreword

Genericity is a style and paradigm of programming language, which allows the use of some later specified types in strongly typed languages, and indicates these classes at the time of instantiation. In C #, JAVA, Delphi and other languages, this feature is called generics; It is called parameter polymorphism in ML and Scala; In C + + language, it is called template. Generics are NET2.0, which improves the reusability and type safety of code (reduce boxing and unpacking operations, use Object type and display conversion type). How is generics in C # compiled in the compiler and implemented in the CLR (common runtime)? How to use generic classes in actual coding and the role of generic classes are summarized.

2, Use

Through the above definition of what generics are, we use a generic Demo to see how they are written in the IL language, and then find out how to run on the CLR. In the example, a unified return result class is created. The result information includes status code, message description and result data (such as null value or empty array returned without result). In this way, we can unify the return results (uniformity, convention and configuration can reduce the amount of code, easy to manage, etc.). The returned result data model is different. You can use object class for conversion, but there are performance losses, type conversion and unclear code semantics. Using generics will bring different effects. This example is just a point in generic applications. Translate the following code into IL language using tools and see the composition of the generated intermediate language.

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)
        {
       // Defining generic instances
var result = new Result<StudentInfo>(); result.Code = "100"; result.Message = "success"; result.Data = new StudentInfo() { Id = 1, Name = "user" }; Console.WriteLine(result.Data.Name); Console.ReadKey(); } }    // Define generic classes, T-type parameters, and contract generic constraints. T must be a parameter that inherits the Info class 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

The intermediate language generates the result < T > class. The compiler only generates the "generic version" of IL code and metadata for the result < T > type - it does not instantiate the generic type, and it is not a specific type. T only acts as a placeholder in the middle, and t carries the constraint information of Info class. This is the operation of the intermediate language generated by the code in the compiler compilation stage and generating IL in the compilation stage, It is equivalent to the concept of "template". All related to type parameters are replaced by 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() // Structure of generic instance generation by intermediate language
        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 "success"
        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

By creating generic instances in the Main function, it can be observed in IL that the compiler compiles into the intermediate language of "generic", and there is no specific instantiation of its incoming parameters, because the IL code and metadata of "generic" are compiled in the first stage. In the native runtime (CLR), when the JIT compiler encounters attributedemo for the first time Result`1<class AttributeDemo. StudentInfo > will replace the T in the "template" IL code and metadata with StudentInfo -- instantiating generic types. The conclusion is that generics are IL and metadata compiled into "generics" through the first stage compiler; Second, the CLR runtime provides support for "generic" IL and metadata, replacing it with type parameter instances in the JIT, and "copying" a code to generate native code. CLR generates the same code for all generic types whose type parameters are "reference type"; However, if the type parameter is value type, the CLR will generate an independent code for each different value type. Because when instantiating a generic type of reference type, its allocated size in memory is the same, but when instantiating a value type, the allocated size in memory is different.

Through the above content, we know that generics is because a lot of work has been done in IL and CLR, so how to use generics, what types and constraints do generics have?

From the above figure, you can know the usage content of generics, why generics should be used, reusability in code and security in type conversion; Types of generics (classes, structures, interfaces, delegates, methods); Generic constraints are equivalent to rights and obligations. Rights are to make generic placeholders have properties and methods using base classes through constraints. Obligations are that type parameters must be base classes or their subclasses. Covariance and inversion of generics refer to the previous article.

3, Summary

From the perspective of generic IL and CLR, we can see how to realize code reuse and type safety. For the basic content of generics, understand that the use of generics mainly includes generic types and generic constraints, covariance and inversion of generics. These are for better use of generics, such as the definition of return result class, list < T > collection class, etc. Thinking of reflective and dynamic languages, we know that the first stage compiler is compiled into IL, and the JIT in the second stage CLR or DLR generates local code from intermediate language with a large amount of workload to realize these features. The understanding of a feature, style and paradigm should be deeply explored from the above points.

Topics: C#