С# Общий тип в коробке?

Я выполнил следующий код:

using System;
using System.Collections.Generic;

namespace TestReleaseAndDebug
{
    public class GClass<T1, T2>
    {
        public T1 Name { get; set; }      
        public T2 Age { get; set; }

        public void Display()
        {
            Console.WriteLine("Name: " + Name);           
            Console.WriteLine("Age: " + Age);
        }
    }

    class Program
    {        
        static void Main(string[] args)
        {
            GClass<string, int> person = new GClass<string, int>();
            person.Name = "RAM";         
            person.Age = 34;
            string name = "RAM";          
            int age = 34;

            Console.WriteLine("Name: " + name);         
            Console.WriteLine("Age: " + age);           
            person.Display();

            Console.Read();
        }
    }
}

У меня есть две локальные переменные в функции Main, они - имя и возраст. Я печатаю их с помощью метода console.writeline. Он печатает без проблем. IL основного метода, как показано ниже:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       90 (0x5a)
  .maxstack  2
  .locals init ([0] class TestReleaseAndDebug.GClass`2<string,int32> person,
           [1] string name,
           [2] int32 age)
  IL_0000:  nop
  IL_0001:  newobj     instance void class TestReleaseAndDebug.GClass`2<string,int32>::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldstr      "RAM"
  IL_000d:  callvirt   instance void class TestReleaseAndDebug.GClass`2<string,int32>::set_Name(!0)
  IL_0012:  nop
  IL_0013:  ldloc.0
  IL_0014:  ldc.i4.s   34
  IL_0016:  callvirt   instance void class TestReleaseAndDebug.GClass`2<string,int32>::set_Age(!1)
  IL_001b:  nop
  IL_001c:  ldstr      "RAM"
  IL_0021:  stloc.1
  IL_0022:  ldc.i4.s   34
  IL_0024:  stloc.2
  IL_0025:  ldstr      "Name: "
  IL_002a:  ldloc.1
  IL_002b:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_0030:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0035:  nop
  IL_0036:  ldstr      "Age: "
  IL_003b:  ldloc.2
  IL_003c:  box        [mscorlib]System.Int32
  IL_0041:  call       string [mscorlib]System.String::Concat(object,
                                                              object)
  IL_0046:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_004b:  nop
  IL_004c:  ldloc.0
  IL_004d:  callvirt   instance void class TestReleaseAndDebug.GClass`2<string,int32>::Display()
  IL_0052:  nop
  IL_0053:  call       int32 [mscorlib]System.Console::Read()
  IL_0058:  pop
  IL_0059:  ret
} // end of method Program::Main

У меня есть еще один класс GClass Generic. В родовом классе у меня есть два свойства и один метод (Display). В методе отображения я показываю два свойства так же, как я отобразил локальные переменные в методе Main. IL-код метода отображения общего класса приведен ниже:

.method public hidebysig instance void  Display() cil managed
{
  // Code size       56 (0x38)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "Name: "
  IL_0006:  ldarg.0
  IL_0007:  call       instance !0 class TestReleaseAndDebug.GClass`2<!T1,!T2>::get_Name()
  IL_000c:  box        !T1
  IL_0011:  call       string [mscorlib]System.String::Concat(object,
                                                              object)
  IL_0016:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_001b:  nop
  IL_001c:  ldstr      "Age: "
  IL_0021:  ldarg.0
  IL_0022:  call       instance !1 class TestReleaseAndDebug.GClass`2<!T1,!T2>::get_Age()
  IL_0027:  box        !T2
  IL_002c:  call       string [mscorlib]System.String::Concat(object,
                                                              object)
  IL_0031:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0036:  nop
  IL_0037:  ret
} // end of method GClass`2::Display

Я передаю 'string' в качестве параметра type для T1 и используя этот тип для объявления свойства Name. Когда свойство Name отображается с помощью Console.Writeline, оно боксирует имя (IL_000c: box! T1). Вы можете найти это в IL.

Почему бокс происходит, хотя это тип строки?

Ответы

Ответ 1

Это так , потому что компилятор не уверен, что оба T1 и T2 всегда будут ссылочным типом или значением типа. поэтому он помещает их в Object по умолчанию для обоих случаев, когда T1 или T2, любой из них является типом значений или ссылочным типом.

Тип Object может действовать в дуальности. Он может box-unbox для типов значений и удерживать ссылки на экземпляры любых типов подкласса, когда он является ссылочным типом.

Итак, в случае, когда T1 является строкой, на самом деле это не бокс, он содержит ссылку на экземпляр строки, потому что Object является базовым классом типа строки, фактически любым типом .Net.

а в случае, когда T2 является int, это просто бокс-распаковка.

Ответ 2

Компилятор должен генерировать ИЛ, который может работать во всех родовых типах. Компилятор не может знать, что вы всегда устанавливаете GCClass с помощью <string, int>. Он должен справиться с вероятностью, что T1 - тип значения.

Однако я бы ожидал, что box в ссылочном типе будет no-op. JIT генерирует различный машинный код из метода IL Display для ссылочных и типов значений. Для ссылочных типов я бы ожидал, что команда box будет устранена.

Если вы уверены, что T1 никогда не будет типом значения, вы можете добавить к нему ограничение : class, которое удалит эту инструкцию box.

Ответ 3

Ознакомьтесь с спецификацией CLI

В разделе 4.1 раздела 4.1 о инструкции box:

Если типTok - тип значения, поле инструкция преобразует val в свою коробку форма. Когда typeTok не является нулевым типа (§1.8.2.4), это делается создание нового объекта и копирование данные из val в недавно выделенные объект. Если это тип с нулевым значением, это делается путем проверки vals HasValue имущество; если оно ложно, значение null ссылка помещается в стек; в противном случае результат бокса Свойство Value переносится на стек. Если typeTok является ссылкой type, инструкция в поле ничего не делает

Таким образом, бокс происходит только в том случае, если общий тип типа фактически является типом значения. Если это ссылочный тип, эта инструкция не имеет эффекта.