Understand how c# generics work

Time:2021-8-2

preface

We all know the importance of generics in c#. Generics are the most important embodiment of polymorphism of the three major features in OOP language. Almost generics support the whole. Net framework. Before talking about generics, we can throw a problem. Now we need an array class with expandable capacity, which can meet all types, whether value types or reference types, without using generic methods, How?

1、 Stories before generics

  we’ll definitely think of using itobjectBecause in c# all types are based onObjectType. Therefore, object is the most basic class of all types. Our expandable array classes are as follows:

public class ArrayExpandable
 {
     private object?[] _items = null;

     private int _defaultCapacity = 4;

     private int _size;

     public object? this[int index]
     {
         get
         {
                if (index < 0 || index >= _size) 
                    throw new ArgumentOutOfRangeException(nameof(index));
                return _items[index];
         }
         set
         {
                if (index < 0 || index >= _size) 
                    throw new ArgumentOutOfRangeException(nameof(index));
                _items[index] = value;
         }
     }

     public int Capacity
     {
         get => _items.Length;
         set
         {
              if (value < _size)
              {
                  throw new ArgumentOutOfRangeException(nameof(value));
              }
              if (value != _items.Length)
              {
                  if (value > 0)
                  {
                      object[] newItems = new object[value];
                      if (_size > 0)
                      {
                          Array.Copy(_items, newItems, _size);
                      }
                      _items = newItems;
                  }
                  else
                  {
                      _items = new object[_defaultCapacity];
                  }
              }
         }
    }

    public int Count => _size;


    public ArrayExpandable()
    {
        _items = new object?[0];
    }

    public ArrayExpandable(int capacity)
    {
        _items = new object?[capacity];
    }

    public void Add(object? value)
    {
        //The array element is 0 or the array element is full
        if (_size == _items.Length) EnsuresCapacity(_size + 1);
        _items[_size] = value;
        _size++;
    }

    private void EnsuresCapacity(int size)
    {
        if (_items.Length < size)
        {
            int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2;
            if (newCapacity < size) newCapacity = size;
            Capacity = newCapacity;
        }
   }

Then let’s verify:

var arrayStr = new ArrayExpandable();
var strs = new string[] { "ryzen", "reed", "wymen" };
for (int i = 0; i < strs.Length; i++)
{
     arrayStr.Add(strs[i]);
     string value = (string)arrayStr[i];// If it is changed to int value = (int) arraystr [i], an error occurs when running
     Console.WriteLine(value);
}
Console.WriteLine($"Now {nameof(arrayStr)} Capacity:{arrayStr.Capacity}");

var array = new ArrayExpandable();
for (int i = 0; i < 5; i++)
{
     array.Add(i);
     int value = (int)array[i];
     Console.WriteLine(value);
}
Console.WriteLine($"Now {nameof(array)} Capacity:{array.Capacity}");

Output:

ryzen
reed
wymen
gavin
Now arrayStr Capacity:4
0
1
2
3
4
Now array Capacity:8

  it seems that the output result is correct and can be expanded dynamically. The same value type is supportedStructYesint32And reference type strings, but in fact, some problems will be found here, that is

  1. reference typestringVerification of type conversion is carried out
  2. Value typeint32The packing and unpacking operations are carried out, and the type conversion type is checked at the same time
  3. All this happens at run time. If the type conversion is wrong, an error can be reported at run time

The general execution model is as follows:

Reference type:

Value type:

So is there a way to avoid the above three problems? Based on the CPP template and the generic experience of Java, the generic model more suitable for. Net system was introduced in c#2.0

2、 Implementation with generics

public class ArrayExpandable
{
     private T[] _items;

     private int _defaultCapacity = 4;

     private int _size;

     public T this[int index]
     {
         get
         {
             if (index < 0 || index >= _size) 
                 throw new ArgumentOutOfRangeException(nameof(index));
             return _items[index];
         }
         set
         {
             if (index < 0 || index >= _size) 
                 throw new ArgumentOutOfRangeException(nameof(index));
             _items[index] = value;
          }
     }

     public int Capacity
     {
         get => _items.Length;
         set
         {
             if (value < _size)
             {
                 throw new ArgumentOutOfRangeException(nameof(value));
             }
             if (value != _items.Length)
             {
                 if (value > 0)
                 {
                     T[] newItems = new T[value];
                     if (_size > 0)
                     {
                         Array.Copy(_items, newItems, _size);
                     }
                     _items = newItems;
                 }
                 else
                 {
                     _items = new T[_defaultCapacity];
                 }
             }
          }
     }

     public int Count => _size;


     public ArrayExpandable()
     {
         _items = new T[0];
     }

     public ArrayExpandable(int capacity)
     {
         _items = new T[capacity];
     }
     public void Add(T value)
     {
         //The array element is 0 or the array element is full
         if (_size == _items.Length) EnsuresCapacity(_size + 1);
         _items[_size] = value;
         _size++;
     }

     private void EnsuresCapacity(int size)
     {
         if (_items.Length < size)
         {
             int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2;
             if (newCapacity < size) newCapacity = size;
             Capacity = newCapacity;
         }
     }
 }

Then the test code is rewritten as follows:

var arrayStr = new ArrayExpandable();
var strs = new string[] { "ryzen", "reed", "wymen", "gavin" };
for (int i = 0; i < strs.Length; i++)
{
     arrayStr.Add(strs[i]);
     string value = arrayStr[i];// Change to int value = arraystr [i] compilation error
     Console.WriteLine(value);
}
Console.WriteLine($"Now {nameof(arrayStr)} Capacity:{arrayStr.Capacity}");

var array = new ArrayExpandable();
for (int i = 0; i < 5; i++)
{
     array.Add(i);
     int value = array[i];
     Console.WriteLine(value);
}
Console.WriteLine($"Now {nameof(array)} Capacity:{array.Capacity}");

Output:

ryzen
reed
wymen
gavin
Now arrayStr Capacity:4
0
1
2
3
4
Now array Capacity:8

By intercepting the partArrayExpandableWhat is the essence of IL:

//Declaration class
.class public auto ansi beforefieldinit MetaTest.ArrayExpandable`1
       extends [System.Runtime]System.Object
{
  .custom instance void [System.Runtime]System.Reflection.DefaultMemberAttribute::.ctor(string) = ( 01 00 04 49 74 65 6D 00 00 )                      
} 


//Add method
.method public hidebysig instance void  Add(!T 'value') cil managed
{
  //Code size 69 (0x45)
  .maxstack  3
  .locals init (bool V_0)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldfld      int32 class MetaTest.ArrayExpandable`1::_size
  IL_0007:  ldarg.0
  IL_0008:  ldfld      !0[] class MetaTest.ArrayExpandable`1::_items
  IL_000d:  ldlen
  IL_000e:  conv.i4
  IL_000f:  ceq
  IL_0011:  stloc.0
  IL_0012:  ldloc.0
  IL_0013:  brfalse.s  IL_0024
  IL_0015:  ldarg.0
  IL_0016:  ldarg.0
  IL_0017:  ldfld      int32 class MetaTest.ArrayExpandable`1::_size
  IL_001c:  ldc.i4.1
  IL_001d:  add
  IL_001e:  call       instance void class MetaTest.ArrayExpandable`1::EnsuresCapacity(int32)
  IL_0023:  nop
  IL_0024:  ldarg.0
  IL_0025:  ldfld      !0[] class MetaTest.ArrayExpandable`1::_items
  IL_002a:  ldarg.0
  IL_002b:  ldfld      int32 class MetaTest.ArrayExpandable`1::_size
  IL_0030:  ldarg.1
  IL_0031:  stelem     !T
  IL_0036:  ldarg.0
  IL_0037:  ldarg.0
  IL_0038:  ldfld      int32 class MetaTest.ArrayExpandable`1::_size
  IL_003d:  ldc.i4.1
  IL_003e:  add
  IL_003f:  stfld      int32 class MetaTest.ArrayExpandable`1::_size
  IL_0044:  ret
} // end of method ArrayExpandable`1::Add

  the original definition used aTAs a placeholder, it serves as a template. When we instantiate type parameters, we supplement the placeholder. We can know its type at compile time without type detection at run time, and we can also compare itArrayExpandableandArrayExpandableIn the IL of value type, check whether unpacking and packing operations are carried out. The following is the IL interception part:

ArrayExpandable:

IL_0084:  newobj     instance void GenericSample.ArrayExpandable::.ctor()
  IL_0089:  stloc.2
  IL_008a:  ldc.i4.0
  IL_008b:  stloc.s    V_6
  IL_008d:  br.s       IL_00bc
  IL_008f:  nop
  IL_0090:  ldloc.2
  IL_0091:  ldloc.s    V_6
  IL_ 0093: box [system. Runtime] system. Int32 // box is a boxing operation
  IL_0098:  callvirt   instance void GenericSample.ArrayExpandable::Add(object)
  IL_009d:  nop
  IL_009e:  ldloc.2
  IL_009f:  ldloc.s    V_6
  IL_00a1:  callvirt   instance object GenericSample.ArrayExpandable::get_Item(int32)
  IL_ 00a6: unbox.any [system. Runtime] system.int32 // unbox is the unpacking operation

ArrayExpandable:

IL_007f:  newobj     instance void class GenericSample.ArrayExpandable`1::.ctor()
  IL_0084:  stloc.2
  IL_0085:  ldc.i4.0
  IL_0086:  stloc.s    V_6
  IL_0088:  br.s       IL_00ad
  IL_008a:  nop
  IL_008b:  ldloc.2
  IL_008c:  ldloc.s    V_6
  IL_008e:  callvirt  instance void class GenericSample.ArrayExpandable`1::Add(!0)
  IL_0093:  nop
  IL_0094:  ldloc.2
  IL_0095:  ldloc.s    V_6
  IL_0097:  callvirt   instance !0 class GenericSample.ArrayExpandable`1::get_Item(int32)

  we can see from IL,ArrayExpandableYesTAs a type parameter, its type has been determined in IL after compilation, so of course, there is no unpacking. Ide can detect types during compilation, so it does not need to detect types at runtime, but it does not mean that types cannot be detected at runtime (through is and as). It can also reflect the flexibility of generics through reflection, which will be described later

  actually understandArrayListandListMy friends know,ArrayExpandableandArrayExpandableThe implementation is roughly the same as them, but a lot of simplified versions can be passed hereBenchmarkDotNetTo test its performance comparison, the code is as follows:

[SimpleJob(RuntimeMoniker.NetCoreApp31,baseline:true)]
    [SimpleJob(RuntimeMoniker.NetCoreApp50)]
    [MemoryDiagnoser]
    public class TestClass
    {

        [Benchmark]
        public void EnumAE_ValueType()
        {
            ArrayExpandable array = new ArrayExpandable();
            for (int i = 0; i < 10000; i++)
            {
                array.Add(i);// Packing
                int value = (int)array[i];// Unpacking
            }
            array = null;// Ensure garbage collection
        }

        [Benchmark]
        public void EnumAE_RefType()
        {
            ArrayExpandable array = new ArrayExpandable();
            for (int i = 0; i < 10000; i++)
            {
                array.Add("r");
                string value = (string)array[i];
            }
            array = null;// Ensure garbage collection
        }

        [Benchmark]
       public void EnumAE_Gen_ValueType()
        {
            ArrayExpandable array = new ArrayExpandable();
            for (int i = 0; i < 10000; i++)
            {
                array.Add(i);
                int value = array[i];
            }
            array = null;// Ensure garbage collection;
        }

        [Benchmark]
        public void EnumAE_Gen_RefType()
        {
            ArrayExpandable array = new ArrayExpandable();
            for (int i = 0; i < 10000; i++)
            {
                array.Add("r");
                string value = array[i];
            }
            array = null;// Ensure garbage collection;
        }

        [Benchmark]
        public void EnumList_ValueType()
        {
            List array = new List();
            for (int i = 0; i < 10000; i++)
            {
                array.Add(i);
                int value = array[i];
            }
            array = null;// Ensure garbage collection;
        }


        [Benchmark]
        public void EnumList_RefType()
        {
            List array = new List();
            for (int i = 0; i < 10000; i++)
            {
                array.Add("r");
                string value = array[i];
            }
            array = null;// Ensure garbage collection;
        }

        [Benchmark(Baseline =true)]
        public void EnumAraayList_valueType()
        {
            ArrayList array = new ArrayList();
            for (int i = 0; i < 10000; i++)
            {
                array.Add(i);
                int value = (int)array[i];
            }
            array = null;// Ensure garbage collection;
        }


        [Benchmark]
        public void EnumAraayList_RefType()
        {
            ArrayList array = new ArrayList();
            for (int i = 0; i < 10000; i++)
            {
                array.Add("r");
                string value = (string)array[i];
            }
            array = null;// Ensure garbage collection;
        }
    }

  I also added the comparison between. NETCORE 3.1 and. Net5, and took the comparison of. NETCORE 3.1 as an exampleEnumAraayList_valueTypeBased on the method, the performance test results are as follows:

Present with a more intuitive bar chart:

  we can see hereListThe performance of. Net5 is the best in both reference types and value types. It is the best in terms of execution time, GC times and the size of allocated memory space. At the same time, the performance of. Net5 is better than. NETCORE 3.1 in almost all methods. I also mention here that I implement itArrayExpandableandArrayExpandablePerformance is worse thanArrayListandList, I haven’t realized it yetIListAnd all kinds of methods can only say the dotnet foundation is awesome

3、 Generic polymorphism

Polymorphic declaration

Classes, structures, interfaces, methods, and delegates can declare one or more type parameters. Let’s look at the code directly:

interface IFoo
{
   void InterfaceMenthod(InterfaceT interfaceT);
}

class Foo: IFoo
{
   public ClassT1 Field;
    
   public delegate void MyDelegate(DelegateT delegateT);

   public void DelegateMenthod(DelegateT delegateT, MyDelegate myDelegate)
   {
        myDelegate(delegateT);
   }

   public static string operator +(Foo foo,string s)
   {
        return $"{s}:{foo.GetType().Name}";
   }


   public List Property{ get; set; }
   public ClassT1 Property1 { get; set; }

   public ClassT this[int index] => Property[index];// Didn't judge cross-border


   public Foo(List classT, ClassT1 classT1)
   {
        Property = classT;
        Property1 = classT1;
        Field = classT1;
        Console. Writeline ($"constructor: Parameter1 type: {property. Gettype(). Name}, parameter2 type: {property1. Gettype(). Name}");
   }

        //Method declares multiple new type parameters
   public void Method(MenthodT menthodT, MenthodT1 menthodT1)
   {
       Console.WriteLine($"Method:{(menthodT.GetType().Name)}:{menthodT.ToString()}," +
        $"{menthodT1.GetType().Name}:{menthodT1.ToString()}");
   }

   public void Method(ClassT classT)
   {
        Console.WriteLine($"{nameof(Method)}:{classT.GetType().Name}:classT?.ToString()");
    }

    public void InterfaceMenthod(StringBuilder interfaceT)
    {
            Console.WriteLine(interfaceT.ToString());
    }
}

Console test code:

static void Main(string[] args)
{
     Test();
     Console.ReadLine();
}

static void Test()
{
     var list = new List() { 1, 2, 3, 4 };
     var foo = new Foo(list, "ryzen");

     var index = 0;
     Console.writeline ($"index: value of index {index}: {foo [index]}");
    
     Console.WriteLine($"Filed:{foo.Field}");

     foo.Method(2333);

     foo.Method(DateTime.Now, 2021);

     foo.DelegateMenthod("this is a delegate", DelegateMenthod);

     foo.InterfaceMenthod(new StringBuilder().Append("InterfaceMenthod:this is a interfaceMthod"));

      Console.writeline (foo + "overload + operator");
}

static void DelegateMenthod(string str)
{
      Console.WriteLine($"{nameof(DelegateMenthod)}:{str}");
}

The output is as follows:

Constructor: Parameter1 type: list ` 1, parameter2 type: String
Index: value of index 0: 1
Filed:ryzen
Method:Int32:classT?.ToString()
Method:DateTime:2021/03/02 11:45:40,Int64:2021
DelegateMenthod:this is a delegate
InterfaceMenthod:this is a interfaceMthod
Overload + operator: foo ` 2

We can see from the example:

  • Classes (structures can also be used), interfaces, delegates and methods can declare one or more type parameters, reflecting the declared polymorphism
  • Class function members: attributes, fields, indexes, constructors and operators can only introduce the type parameters declared by the class, but can not be declared. Only the method function member has the functions of declaring and referencing type parameters. Because it has the declaration function, it can declare and reference the same type parameters as the delegate, which also reflects the polymorphism of the method

Polymorphic inheritance

Both the parent class and the interface implementing the class or interface can be instantiated types. See the code directly:

interface IFooBase{}

interface IFoo: IFooBase
{
    void InterfaceMenthod(InterfaceT interfaceT);
}

class FooBase
{

}

class Foo: FooBase,IFoo{}

We can see from examples:

  • becauseFooBase class ofFooBaseDefined andFooHas shared type parametersClassTTherefore, you can inherit without instantiating the type
  • andFooandIFooThe interface does not define the same type parameters, so the type parameters of the interface can be instantiated during inheritanceStringBuildcome out
  • IFooandIFooBaseThe same type parameters are not defined, so the type parameters of the interface can be instantiated during inheritancestringcome out
  • The above all reflect the polymorphism of inheritance

Polymorphic recursion

We define the following class and method without error:

class D { }
    class C : D>> 
    { 
        void Foo()
        {
            var foo = new C>();
            Console.WriteLine(foo.ToString());
        }
    }

becauseTIt can determine its type when instantiating, so it also supports this kind of circular application of its own class and method definitions

4、 Generic constraints

Where constraints

Let’s start with the code:

class FooBase{ }

    class Foo : FooBase 
    {
        
    }
    
    class someClass where T:struct where K :FooBase,new()
    {

    }

    static void TestConstraint()
    {
        var someClass = new someClass();// By compiling
        //var someClass = new someClass();// Compilation failed. String is not of struct type
        //var someClass = new someClass();// Compilation failed, long is not of foobase type
    }

Change the following foo class:

class Foo : FooBase 
{
   public Foo(string str)
   {

   }
}

static void TestConstraint()
{
   var someClass = new someClass();// Compilation failed because the new () constraint class must contain a parameterless constructor. You can add a parameterless constructor to the foo class to compile
}

  we can see that throughwhereStatement can constrain type parameters, and a type parameter supports multiple constraint conditions (such as K), so that when instantiating type parameters, it must correspond to the type of the instance that meets the conditions according to the constraint conditions, andwhereThe function of conditional constraints is to constrain type parameters at compile time

Out and in constraints

  speaking ofoutandinPreviously, we can talk about covariance and inversion. In C #, only generic interfaces and generic delegates can support covariance and inversion

covariant

Let’s look at the code first:

class FooBase{ }

class Foo : FooBase 
{

}

interface IBar 
{
    T GetValue(T t);
}

class Bar : IBar
{
   public T GetValue(T t)
   {
       return t;
   }
}

static void Test()
{
    var foo = new Foo();
    FooBase fooBase = foo;// Compilation succeeded

    IBar bar = new Bar();
    IBar bar1 = bar;// Compilation failed
 }

  at this time, you may be a little strange. Why did the code fail to compile, clearlyFooClass can be implicitly converted toFooBaseBut instantiation as a generic interface type parameter can’t? useoutConstraint generic interfaceIBarT, the code will compile normally, but another compilation error will be introduced:

interface IBar 
{
    T GetValue(string str);// Compilation succeeded
    //T GetValue(T t);// Compilation fails. T cannot be input as a formal parameter. Out constraint t supports covariance, and t can be output as a return value
    
}

IBar bar = new Bar();
IBar bar1 = bar;// Normal compilation

Therefore, we can draw the following conclusions:

  • becauseFooinheritFooBase, this classFooIt contains the members that the parent class allows to access, so it can implicitly convert the parent class. This is a type safe conversion, so it is called covariance
  • For generic interfacesoutAfter identifying that its type parameter supports covariance, the get (which is also a return value method in essence) that constrains the return value of its method and property can refer to the declared type parameter, that is, as an output valueoutIt clearly highlights this meaning

Generic interfaces that support iterationIEnumerableThis is also the definition:

public interface IEnumerable : IEnumerable
    {
        new IEnumerator GetEnumerator();
    }

Inversion

Let’s change the above code to the following:

class FooBase{ }

class Foo : FooBase 
{

}

interface IBar 
{
    T GetValue(T t);
}

class Bar : IBar
{
   public T GetValue(T t)
   {
       return t;
   }
}

static void Test1()
{
    var fooBase = new FooBase();
    Foo foo = (Foo)fooBase;// If the compilation passes, an error occurs when running

    IBar bar = new Bar();
    IBar bar1 = (IBar)bar;// If the compilation passes, an error occurs when running
}

We changed ibar again and found another compilation failure

interface IBar 
{
    void GetValue(T t);// Compilation succeeded
    //T GetValue(T t);// Compilation failure t cannot be output as a return value. The in constraint t supports inversion, and t can be output as a return value
}

 IBar bar = new Bar();
 IBar bar1 = (IBar)bar;// The compilation passes and no error is reported at runtime
 IBar bar1 = bar;// The compilation passes and no error is reported at runtime

Therefore, we can draw the following conclusions:

  • becauseFooBaseyesFooThe parent class of does not contain free members of subclasses and is converted to subclassesFooThe type is unsafe, so the error of strong conversion is reported at run time, but it cannot be confirmed at compile time
  • For generic interfacesinAfter identifying its type parameters and supporting inversion,inConstraint its interface members cannot take it as the return value (output value). We will find that covariance and inversion are just a pair of antonyms
  • It is mentioned here that value types do not support covariance and inversion

Same generic delegateActionThis is an example of inversion:

public delegate void Action(T obj);

5、 Generic reflection

Let’s look at the following code first:

static void Main(string[] args)
{
    var lsInt = new ArrayExpandable();
    lsInt.Add(1);
    var lsStr = new ArrayExpandable();
    lsStr.Add("ryzen");
    var lsStr1 = new ArrayExpandable();
    lsStr.Add("ryzen");
}

Then view its IL through ILdasm, open the view – display tag value, and view the main method:

void  Main(string[] args) cil managed
{
  .entrypoint
  //Code size 52 (0x34)
  .maxstack  2
  .locals /*11000001*/ init (class MetaTest.ArrayExpandable`1/*02000003*/ V_0,
           class MetaTest.ArrayExpandable`1/*02000003*/ V_1,
           class MetaTest.ArrayExpandable`1/*02000003*/ V_2)
  IL_0000:  nop
  IL_0001:  newobj     instance void class MetaTest.ArrayExpandable`1/*02000003*//*1B000001*/::.ctor() /* 0A00000C */
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldc.i4.1
  IL_0009:  callvirt   instance void class MetaTest.ArrayExpandable`1/*02000003*//*1B000001*/::Add(!0) /* 0A00000D */
  IL_000e:  nop
  IL_000f:  newobj     instance void class MetaTest.ArrayExpandable`1/*02000003*//*1B000002*/::.ctor() /* 0A00000E */
  IL_0014:  stloc.1
  IL_0015:  ldloc.1
  IL_0016:  ldstr      "ryzen" /* 70000001 */
  IL_001b:  callvirt   instance void class MetaTest.ArrayExpandable`1/*02000003*//*1B000002*/::Add(!0) /* 0A00000F */
  IL_0020:  nop
  IL_0021:  newobj     instance void class MetaTest.ArrayExpandable`1/*02000003*//*1B000002*/::.ctor() /* 0A00000E */
  IL_0026:  stloc.2
  IL_0027:  ldloc.1
  IL_0028:  ldstr      "ryzen" /* 70000001 */
  IL_002d:  callvirt   instance void class MetaTest.ArrayExpandable`1/*02000003*//*1B000002*/::Add(!0) /* 0A00000F */
  IL_0032:  nop
  IL_0033:  ret
} // end of method Program::Main

Open the metadata table and list the metadata definition table and type specification table mentioned above:

metainfo:

-----------Definition part
TypeDef #2 (02000003)
-------------------------------------------------------
	TypDefName: MetaTest.ArrayExpandable`1  (02000003)
	Flags     : [Public] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit]  (00100001)
	Extends   : 0100000C [TypeRef] System.Object
	1 Generic Parameters
		(0) GenericParamToken : (2a000001) Name : T flags: 00000000 Owner: 02000003
	
	Method #8 (0600000a) 
	-------------------------------------------------------
		MethodName: Add (0600000A)
		Flags     : [Public] [HideBySig] [ReuseSlot]  (00000086)
		RVA       : 0x000021f4
		ImplFlags : [IL] [Managed]  (00000000)
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		1 Arguments
			Argument #1:  Var!0
		1 Parameters
		(1) ParamToken : (08000007) Name : value flags: [none] (00000000)
		

------Type specification section
TypeSpec #1 (1b000001)
-------------------------------------------------------
	Typespec: genericinst class metatest. Arrayexpandable ` 1 < I4 > // 14 represents int32
	MemberRef #1 (0a00000c)
	-------------------------------------------------------
		Member: (0a00000c) .ctor: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		No arguments.
	MemberRef #2 (0a00000d)
	-------------------------------------------------------
		Member: (0a00000d) Add: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		1 Arguments
			Argument #1:  Var!0

TypeSpec #2 (1b000002)
-------------------------------------------------------
	TypeSpec : GenericInst Class MetaTest.ArrayExpandable`1< String>
	MemberRef #1 (0a00000e)
	-------------------------------------------------------
		Member: (0a00000e) .ctor: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		No arguments.
	MemberRef #2 (0a00000f)
	-------------------------------------------------------
		Member: (0a00000f) Add: 
		CallCnvntn: [DEFAULT]
		hasThis 
		ReturnType: Void
		1 Arguments
		Argument #1:  Var!0

  at this time, we can see that the metadata is a generic classArrayExpandableDefine a definition table and generate two specifications, that is, when you instantiate the type parameter asintandstringTwo specification codes are generated respectively, and the following phenomena are also found:

var lsInt = new ArrayExpandable();// It refers to the member 0a00000c. Ctor construction of type specification 1b000001
lsInt.Add(1);// It refers to the member 0a00000d add of type specification 1b000001
    
var lsStr = new ArrayExpandable();// The reference is a member 0a00000e. Ctor construct of type specification 1b000002
lsStr.Add("ryzen");// It refers to the member 0a00000f add of type specification 1b000002
var lsStr1 = new ArrayExpandable();// Same as lsstr
lsStr.Add("ryzen");// Same as lsstr

  it’s wonderful when you instantiate two same type parametersstring, share a type specification, that is, share a local code. Therefore, the above code is roughly as follows in the thread stack and managed heap:

Because generic types also have metadata, they can be reflected:

Console.WriteLine($"-----------{nameof(lsInt)}---------------");
Console.WriteLine($"{nameof(lsInt)} is generic?:{lsInt.GetType().IsGenericType}");
Console.WriteLine($"Generic type:{lsInt.GetType().GetGenericArguments()[0].Name}");
Console.WriteLine("---------Menthods:");
foreach (var method in lsInt.GetType().GetMethods())
{
      Console.WriteLine(method.Name);
}
Console.WriteLine("---------Properties:");
foreach (var property in lsInt.GetType().GetProperties())
{
      Console.WriteLine($"{property.PropertyType.ToString()}:{property.Name}");
}


Console.WriteLine($"\n-----------{nameof(lsStr)}---------------");
Console.WriteLine($"{nameof(lsStr)} is generic?:{lsStr.GetType().IsGenericType}");
Console.WriteLine($"Generic type:{lsStr.GetType().GetGenericArguments()[0].Name}");
Console.WriteLine("---------Menthods:");
foreach (var method in lsStr.GetType().GetMethods())
{
      Console.WriteLine(method.Name);
}
Console.WriteLine("---------Properties:");
foreach (var property in lsStr.GetType().GetProperties())
{
      Console.WriteLine($"{property.PropertyType.ToString()}:{property.Name}");
}

Output:

-----------lsInt---------------
lsInt is generic?:True
Generic type:Int32
---------Menthods:
get_Item
set_Item
get_Capacity
set_Capacity
get_Count
Add
GetType
ToString
Equals
GetHashCode
---------Properties:
System.Int32:Item
System.Int32:Capacity
System.Int32:Count


-----------lsStr---------------
lsStr is generic?:True
Generic type:String
---------Menthods:
get_Item
set_Item
get_Capacity
set_Capacity
get_Count
Add
GetType
ToString
Equals
GetHashCode
---------Properties:
System.String:Item
System.Int32:Capacity
System.Int32:Count

6、 Summary

As a very important programming idea in. Net system, generic programming mainly has the following highlights:

  • The type is determined during compilation to avoid the disassembly box of value type and unnecessary runtime type inspection. Similarly, the runtime can passisandasConduct type inspection
  • Scope of instantiation of type parameters by constraints
  • At the same time, at the IL level, a local code is shared when instantiating the same type parameters
  • Due to the existence of metadata, it can also be reflected at run time to enhance its flexibility

reference resources

Design and Implementation of Generics for the .NET Common Language Runtime

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/

CLR via c# 4th Edition

“What you have to know. Net (Second Edition)”

Recommended Today

Programming Xiaobai must understand the network principle

How is the network composed? Why can we easily surf the Internet now?Whether you are a computer major or not, you may always have such questions in your heart!And today we will solve this matter and tell you the real answer! Basic composition of network First, let’s look at this sentence Connect all computers together […]