繁体   English   中英

盒装C#Generic Type?

[英]C# Generic Type is boxed?

我执行了以下代码:

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函数中有两个局部变量,它们是name和age。 我正在使用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找到这个。

为什么拳击发生虽然它是一个字符串类型?

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

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

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

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

Object类型可以在二元性中起作用 它可以为值类型进行box-unbox,并在它是引用类型时保持对任何子类类型的实例的引用。

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

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

查看CLI规范

在分区III,第4.1节中,关于box说明:

如果typeTok是值类型,则box指令将val转换为其盒装形式。 当typeTok是一个非可空类型(§1.8.2.4)时,这是通过创建一个新对象并将数据从val复制到新分配的对象来完成的。 如果它是可空类型,则通过检查val的HasValue属性来完成; 如果为false,则将空引用推送到堆栈; 否则,装箱val的Value属性的结果被压入堆栈。 如果typeTok是引用类型,则box指令不执行任何操作

因此, 仅当泛型类型参数实际上是值类型才会发生装箱。 如果它是引用类型,则此指令无效。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM