2010-07-21 62 views
15

我执行以下代码:C#泛型类型是盒装?

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(); 
     } 
    } 
} 

我有在主函数两个局部变量他们的姓名和年龄。我使用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'。在泛型类中,我有两个属性和一个方法(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 

我传递“字符串”作为类型参数T1和使用这种类型的声明名称属性。当使用Console.Writeline显示名称属性时,它将装箱名称(IL_000c:box!T1)。你可以在IL中找到它。

为什么拳击发生,尽管它是一个字符串类型?

+1

这是一个帖子中有很多代码。您应该考虑将IL片段缩小到显示您问题的线条。 – 2010-07-21 10:05:28

+0

也检查此:http://stackoverflow.com/questions/32664/c-generic-constraint-for-only-integers – 2010-07-21 10:17:32

回答

6

这是如此,因为编译器不确定T1T2将始终是引用类型或值类型。因此在T1或T2时,它们都是默认值,这两种情况都是值类型或引用类型。

该类型Object可以在双重性行事。它可以是对于值类型的box-unbox在引用类型时保留对任何子类类型的实例的引用。

所以在T1是字符串的情况下,它实际上不是装箱,它保存字符串实例的引用,因为Object是字符串类型的基类,实际上是任何.Net类型。

并且在T2是int的情况下,它是简单的装箱 - 拆箱。

+0

嗨, 感谢您的帮助。我认为,它仍然是一个盒子,但是当它试图装箱时,它发现类型是参考类型,因此没有托马斯·列维斯克所说的那样。 – RAM 2010-07-21 10:32:48

6

编译器必须生成可跨所有通用类型工作的IL。编译器无法知道你总是用<string, int>安装GCClass。它必须应对T1是一种价值型的可能性。

但是,我认为box在参考类型是一个无操作。 JIT从Display方法的IL中为参考和值类型生成不同的机器代码。对于参考类型,我期望box指令被消除。

如果您确定T1永远不会是值类型,您可以添加一个: class约束条件,它将删除box指令。

+0

蒂姆,这是有道理的。感谢你的回答。 – RAM 2010-07-21 10:25:17

4

检查出CLI specification

在分区III,第4.1节,关于box指令:

如果typeTok为值类型,盒 指令转换VAL其盒装 形式。当typeTok是不可空的 类型(§1.8.2。4),这是通过 创建新对象并将来自val的 数据复制到新分配的 对象中完成的。如果它是可空类型,则通过检查val的HasValue 属性完成此 ;如果它是假的,则将一个空的 引用推入堆栈; 否则,装箱val的结果 Value属性被压入 堆栈。 如果typeTok是引用 型,箱指令不

所以拳击发生只有在泛型类型参数实际上是一个值类型。如果它是参考类型,则该指令不起作用。

+0

Hi Thomas, 感谢您的回答。我相信在JIT期间,它会根据操作数的实际类型决定是否进行装箱。我对吗? – RAM 2010-07-21 10:34:16

+0

@RAM是的,看到我的答案 – 2010-07-21 10:37:06