С# Общий тип в коробке?
Я выполнил следующий код:
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, инструкция в поле ничего не делает
Таким образом, бокс происходит только в том случае, если общий тип типа фактически является типом значения. Если это ссылочный тип, эта инструкция не имеет эффекта.