为什么'box'指令是针对泛型发出的?

 劲吻2502877607 发布于 2023-02-09 11:10

这是一个相当简单的泛型类.通用参数被约束为引用类型.IRepository并且DbSet还包含相同的约束.

public class Repository : IRepository
    where TEntity : class, IEntity
{
    protected readonly DbSet _dbSet;
    public void Insert(TEntity entity)
    {
        if (entity == null) 
        throw new ArgumentNullException("entity", "Cannot add null entity.");
        _dbSet.Add(entity);
    }
}

编译的IL包含box指令.这是发布版本(虽然调试版本也包含它).

.method public hidebysig newslot virtual final 
    instance void  Insert(!TEntity entity) cil managed
{
  // Code size       38 (0x26)
  .maxstack  8
  IL_0000:  ldarg.1
  >>>IL_0001:  box        !TEntity
  IL_0006:  brtrue.s   IL_0018
  IL_0008:  ldstr      "entity"
  IL_000d:  ldstr      "Cannot add null entity."
  IL_0012:  newobj     instance void [mscorlib]System.ArgumentNullException::.ctor(string,
                                           string)
  IL_0017:  throw
  IL_0018:  ldarg.0
  IL_0019:  ldfld      class [EntityFramework]System.Data.Entity.DbSet`1 class Repository`1::_dbSet
  IL_001e:  ldarg.1
  IL_001f:  callvirt   instance !0 class [EntityFramework]System.Data.Entity.DbSet`1::Add(!0)
  IL_0024:  pop
  IL_0025:  ret
} // end of method Repository`1::Insert

更新:

有了object.Equals(entity, default(TEntity))它看起来更糟糕:

  .maxstack  2
  .locals init ([0] !TEntity CS$0$0000)
  IL_0000:  ldarg.1
  >>>IL_0001:  box        !TEntity
  IL_0006:  ldloca.s   CS$0$0000
  IL_0008:  initobj    !TEntity
  IL_000e:  ldloc.0
  >>>IL_000f:  box        !TEntity
  IL_0014:  call       bool [mscorlib]System.Object::Equals(object,
                                object)
  IL_0019:  brfalse.s  IL_002b

UPDATE2:

对于那些感兴趣的人,这里是调试器中显示的jit编译的代码:

0cd5af28 55              push    ebp
0cd5af29 8bec            mov     ebp,esp
0cd5af2b 83ec18          sub     esp,18h
0cd5af2e 33c0            xor     eax,eax
0cd5af30 8945f0          mov     dword ptr [ebp-10h],eax
0cd5af33 8945ec          mov     dword ptr [ebp-14h],eax
0cd5af36 8945e8          mov     dword ptr [ebp-18h],eax
0cd5af39 894df8          mov     dword ptr [ebp-8],ecx
    //entity reference to [ebp-0Ch]
0cd5af3c 8955f4          mov     dword ptr [ebp-0Ch],edx
    //some debugger checks
0cd5af3f 833d9424760300  cmp     dword ptr ds:[3762494h],0
0cd5af46 7405            je      0cd5af4d  Branch
0cd5af48 e8e1cac25a      call    clr!JIT_DbgIsJustMyCode (67987a2e)
0cd5af4d c745fc00000000  mov     dword ptr [ebp-4],0
0cd5af54 90              nop

    //comparison or entity ref with  zero
0cd5af55 837df400        cmp     dword ptr [ebp-0Ch],0
0cd5af59 0f95c0          setne   al
0cd5af5c 0fb6c0          movzx   eax,al
0cd5af5f 8945fc          mov     dword ptr [ebp-4],eax
0cd5af62 837dfc00        cmp     dword ptr [ebp-4],0
    //if not zero, jump further
0cd5af66 7542            jne     0cd5afaa  Branch
    //throwing exception here      

这个问题的原因实际上是NDepend警告使用装箱/拆箱.我很好奇为什么它在一些通用类中发现拳击,现在很清楚.

2 个回答
  • ECMA规范对此说明进行了box说明:

    堆栈转换: ..., val -> ..., obj

    ...

    如果typeTok是泛型参数,则box指令的行为取决于运行时的实际类型.如果此类型[...]是引用类型,则val不会更改.

    它的含义是编译器可以假设它对box引用类型是安全的.因此,对于泛型,编译器有两种选择:发出保证工作的代码,无论泛型类型如何受约束,或者优化代码并省略冗余指令,证明它们是不必要的.

    通常,Microsoft C#编译器倾向于选择更简单的方法并将所有优化保留在JIT阶段.对我来说,看起来你的例子就是这样:没有优化,因为实现优化需要时间,而保存这box条指令在实践中可能没什么价值.

    C#甚至允许将无约束的泛型类型值进行比较null,因此编译器必须支持这种一般情况.实现这种一般情况的最简单方法是使用box指令,该指令完成处理引用,值和可空类型的所有繁重操作,正确地将引用或空值推送到堆栈上.所以编译器最容易做的就是box不管有什么约束都要发出,然后将值与零(brtrue)进行比较.

    2023-02-09 11:12 回答
  • 在查看生成BOX指令的C#编译器源代码时,我遇到了一个非常相关的注释.fncbind.cpp源文件包含此注释,与此特定代码无直接关系:

    //注意:对于标志,我们必须使用EXF_FORCE_UNBOX(而不是EXF_REFCHECK),即使
    //我们知道该类型是引用类型.验证程序期望
    // 类型参数的所有代码都表现为类型参数是值类型.
    //抖动应该是聪明的....

    所以它就在那里,因为验证者需要它.

    是的,抖动很明智.它根本不会为BOX指令发出任何代码.

    2023-02-09 11:12 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有