这是一个相当简单的泛型类。通用参数被约束为引用类型。 IRepository
和DbSet
也包含相同的约束。
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
指令。这是发布版本(调试版本也包含它)。
.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:
对于那些感兴趣的人,这里是调试器中显示的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警告使用装箱/拆箱。我很好奇为什么它在一些通用类中发现拳击,现在很明显。
答案 0 :(得分:15)
在查看生成BOX指令的C#编译器源代码时,我遇到了一个非常相关的注释。 fncbind.cpp源文件具有此注释,与此特定代码无直接关系:
//注意:对于标志,我们必须使用EXF_FORCE_UNBOX(不是EXF_REFCHECK),即使是 //我们知道类型是引用类型。 验证者需要的所有代码 // 键入参数,使其行为就像type参数是值类型一样。
//抖动应该很聪明......
所以它就在那里,因为验证者需要它。
是的,抖动很聪明。它根本不会为BOX指令发出任何代码。
答案 1 :(得分:12)
ECMA规范说明了box
指令:
堆叠转换:
..., val -> ..., obj
...
如果typeTok是泛型参数,则box指令的行为取决于运行时的实际类型。如果此类型[...]是引用类型,则
val
不是 改变。
它的含义是编译器可以假设对box
引用类型是安全的。因此,对于泛型,编译器有两种选择:发出保证无论泛型类型受限制如何工作的代码,或优化代码并省略冗余指令,以证明它们是不必要的。
Microsoft C#编译器通常倾向于选择更简单的方法并将所有优化保留在JIT阶段。对我来说,看起来你的例子就是这样:没有优化的东西,因为实现优化需要时间,而保存这个box
指令在实践中可能没什么价值。
C#允许将无约束的泛型类型值与null
进行比较,因此编译器必须支持这种一般情况。实现这种一般情况的最简单方法是使用box
指令,它执行处理引用,值和可空类型的所有繁重操作,正确地将引用或空值推送到堆栈上。因此编译器最容易做的就是发出box
而不管约束如何,然后将值与零(brtrue
)进行比较。