【问题标题】:C# Generic Type is boxed?C# Generic Type 是装箱的?
【发布时间】:2011-03-18 21:40:12
【问题描述】:

我执行了以下代码:

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 方法打印它们。它打印没有任何问题。 main方法的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 方法中,我显示两个属性的方式与在 Main 方法中显示局部变量的方式相同。 Generic Class Display方法的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 中找到它。

为什么即使它是字符串类型也会发生装箱?

【问题讨论】:

标签: c# .net


【解决方案1】:

之所以如此因为编译器不确定T1T2 是否总是引用类型或值类型。因此,当 T1 或 T2 为值类型或引用类型时,默认情况下将它们放入 Object 中。

Object 类型可以对偶。当它是引用类型时,它可以对值类型进行拆箱保存对任何子类的实例的引用

所以如果 T1 是字符串,它实际上不是装箱,它持有字符串实例的引用,因为 Object 是字符串类型的基类,实际上是任何 .Net 类型。

如果 T2 为 int,则为简单的装箱-拆箱。

【讨论】:

  • 您好,感谢您的回答。我认为,它仍然是一个盒子,但是当它试图装箱时,它会发现类型为引用类型,所以正如 Thomas Levesque 所提到的那样。
【解决方案2】:

编译器必须生成可以跨所有泛型类型工作的 IL。编译器无法知道您总是用&lt;string, int&gt; 实例化GCClass。它必须应对T1 是值类型的可能性。

但是,我希望引用类型上的 box 是无操作的。 JIT 从 Display 方法的 IL 生成不同的机器代码,用于引用和值类型。对于引用类型,我希望消除 box 指令。

如果您确定 T1 永远不会是值类型,则可以向其添加 : class 约束,这将删除该 box 指令。

【讨论】:

  • 蒂姆,这是有道理的。感谢您的回答。
【解决方案3】:

查看CLI specification

在第三部分,第 4.1 节,关于 box 指令:

如果 typeTok 是值类型,则该框 指令将 val 转换为其装箱 形式。当 typeTok 是不可为空的 类型(§1.8.2.4),这是由 创建一个新对象并复制 从 val 到新分配的数据 目的。如果它是可空类型,则此 通过检查 val 的 HasValue 来完成 财产;如果为假,则为空 引用被压入堆栈; 否则,装箱 val 的结果 值属性被推到 堆。 如果 typeTok 是一个引用 type,box指令什么都不做

所以装箱只有在泛型类型参数实际上是值类型时才会发生。如果是引用类型,则该指令无效。

【讨论】:

  • 您好托马斯,感谢您的回答。我相信在 JIT 期间,它会根据操作数的实际类型决定是否装箱。我说的对吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-27
  • 2021-03-27
  • 1970-01-01
  • 2016-02-01
  • 2021-04-15
  • 2013-01-22
相关资源
最近更新 更多