我执行了以下代码:
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
我有另一个Generic类'GClass'。在泛型类中,我有两个属性和一个方法(Display)。在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'作为类型参数传递给T1,并使用此类型声明Name属性。当使用Console.Writeline显示名称属性时,它将装箱名称(IL_000c:box!T1)。你可以在IL找到这个。
为什么拳击会发生,尽管它是一种字符串类型?
答案 0 :(得分:6)
编译器必须生成可以跨所有泛型类型工作的IL。编译器无法知道您始终使用GCClass
实例<string, int>
。它必须应对T1
是值类型的可能性。
但是,我希望引用类型上的box
是无操作。 JIT从Display
方法的IL生成不同的机器代码,用于引用和值类型。对于参考类型,我希望消除box
指令。
如果您确定T1
永远不会是值类型,则可以向其添加: class
约束,这将删除该box
指令。
答案 1 :(得分:6)
这是因为编译器不确定T1
和T2
始终是引用类型还是值类型。因此,对于T1或T2两种情况,它们都是默认情况下将它们放入Object中,其中任何一种都是值类型或引用类型。
Object
类型可以采用二元性。当它是引用类型时,它可以 box-unbox for value-types ,保持对任何子类类型实例的引用。
因此,如果T1是字符串,它实际上不是装箱,它持有字符串实例的引用,因为Object是字符串类型的基类,实际上是任何.Net类型。
并且在T2为int的情况下,它是简单的装箱拆箱。
答案 2 :(得分:4)
在分区III第4.1节中,关于box
指令:
如果typeTok是值类型,则为框 指令将val转换为其盒装 形成。当typeTok是不可为空的 类型(§1.8.2.4),这是通过 创建一个新对象并复制 从val到新分配的数据 宾语。如果它是可以为空的类型,那么 通过检查val的HasValue来完成 属性;如果为false,则为null 引用被推入堆栈; 否则,拳击val的结果 价值属性被推到了 堆。 如果typeTok是参考 类型,框指令什么都不做
因此,仅当泛型类型参数实际上是值类型时才会发生装箱。如果它是引用类型,则该指令无效。