为什么为通用发出“盒”指令? [英] Why is 'box' instruction emitted for generic?

查看:77
本文介绍了为什么为通用发出“盒”指令?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个相当简单的泛型类。通用参数被限制为引用类型。 IRepository DbSet 也包含相同的约束。

Here is fairly simple generic class. Generic parameter is constrained to be reference type. IRepository and DbSet also contain the same constraint.

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

已编译的IL包含 box 指令。这是发行版本(调试版本也包含该版本)。

Compiled IL contains box instruction. Here is the release version (debug version also contains it though).

.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<!0> class Repository`1<!TEntity>::_dbSet
  IL_001e:  ldarg.1
  IL_001f:  callvirt   instance !0 class [EntityFramework]System.Data.Entity.DbSet`1<!TEntity>::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:

对于有兴趣的人,h ere是调试器中显示的jit编译的代码:

For those who are interested, here is the code compiled by jit shown in debugger:

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警告使用装箱/拆箱。我很好奇为什么它会在某些通用类中找到拳击包,现在很清楚了。

The reason of this question is actually that NDepend warns about using boxing/unboxing. I was curious why it found boxing in some generic classes, and now it's clear.

推荐答案

ECMA规范对此进行了说明, code> box 指令:

The ECMA spec states this about the box instruction:


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

...

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

If typeTok is a generic parameter, the behavior of box instruction depends on the actual type at runtime. If this type [...] is a reference type then val is not changed.

这是说编译器可以假设 box 一个引用类型是安全的。因此,对于泛型而言,编译器有两种选择:不管泛型类型如何受到约束,都发出可以保证工作的代码;或者优化代码并在可能证明不必要的地方省略多余的指令。

What it's saying is that the compiler can assume that it's safe to box a reference type. So with generics, the compiler has two choices: emit the code that is guaranteed to work regardless of how the generic type is constrained, or optimize the code and omit redundant instructions where it can prove them to be unnecessary.

通常,Microsoft C#编译器倾向于选择更简单的方法,而将所有优化工作留在JIT阶段。对我来说,您的示例似乎就是这样:不优化某些内容,因为实现优化需要花费时间,并且保存此 box 指令在实践中可能几乎没有价值。

The Microsoft C# compiler, in general, tends to choose the simpler approach and leave all optimization to the JIT stage. To me, it looks like your example is exactly that: not optimizing something because implementing an optimization takes time, and saving this box instruction probably has very little value in practice.

C#甚至可以将不受约束的泛型值与 null 进行比较,因此编译器必须支持这种一般情况。实现这种一般情况的最简单方法是使用 box 指令,该指令完成了处理引用,值和可空类型的所有繁重工作,正确地推送了引用或在堆栈上的空值。因此,对于编译器而言,最简单的操作是发出 box 而不考虑约束,然后将该值与零进行比较( brtrue )。

C# allows even an unconstrained generic-typed value to be compared to null, so the compiler must support this general case. The easiest way to implement this general case is to use the box instruction, which does all the heavy-lifting of handling reference, value and nullable types, correctly pushing either a reference or a null value onto the stack. So the easiest thing for the compiler to do is to issue box regardless of the constraints, and then compare the value to zero (brtrue).

这篇关于为什么为通用发出“盒”指令?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆