T:クラスの制約を持つジェネリックメソッドがボクシングを行うのはなぜですか? [複製]
質問
この質問にはすでに回答があります:
- C#でジェネリックを使用する場合のボクシング 2つの答え
Tをクラスに制約する一般的なメソッドでは、MSILコードの生成にボクシング命令が含まれるのはなぜですか?
確かにTは参照型に制限されているため、生成されたコードはボクシングを実行する必要がないため、これには非常に驚きました。
C#コードは次のとおりです。
protected void SetRefProperty<T>(ref T propertyBackingField, T newValue) where T : class
{
bool isDifferent = false;
// for reference types, we use a simple reference equality check to determine
// whether the values are 'equal'. We do not use an equality comparer as these are often
// unreliable indicators of equality, AND because value equivalence does NOT indicate
// that we should share a reference type since it may be a mutable.
if (propertyBackingField != newValue)
{
isDifferent = true;
}
}
生成されたILは次のとおりです。
.method family hidebysig instance void SetRefProperty<class T>(!!T& propertyBackingField, !!T newValue) cil managed
{
.maxstack 2
.locals init (
[0] bool isDifferent,
[1] bool CS$4<*>)
L_0000: nop
L_0001: ldc.i4.0
L_0002: stloc.0
L_0003: ldarg.1
L_0004: ldobj !!T
L_0009: box !!T
L_000e: ldarg.2
L_000f: box !!T
L_0014: ceq
L_0016: stloc.1
L_0017: ldloc.1
L_0018: brtrue.s L_001e
L_001a: nop
L_001b: ldc.i4.1
L_001c: stloc.0
L_001d: nop
L_001e: ret
}
ボックス!! T の指示に注意してください。
これが生成される理由
これを避ける方法
解決
引数が参照型である場合、 box
命令は何もしないため、 box
命令のパフォーマンスの低下について心配する必要はありません。 box
命令が作成されたのはまだ奇妙ですが(コード生成時の怠zyさやデザインのしやすさなど)。
他のヒント
ボクシングが発生している理由がわかりません。ボクシングを回避する1つの可能な方法は、ボクシングを使用しないことです。ボクシングなしで再コンパイルするだけです。例:
.assembly recomp_srp
{
.ver 1:0:0:0
}
.class public auto ansi FixedPBF
{
.method public instance void .ctor() cil managed
{
}
.method hidebysig public instance void SetRefProperty<class T>(!!T& propertyBackingField, !!T newValue) cil managed
{
.maxstack 2
.locals init ( bool isDifferent, bool CS$4 FixedPBF TestFixedPBF = new FixedPBF();
TestFixedPBF.SetRefProperty<string>(ref TestField, "test2");
)
ldc.i4.0
stloc.0
ldarg.1
ldobj !!T
ldarg.2
ceq
stloc.1
ldloc.1
brtrue.s L_0001
ldc.i4.1
stloc.0
L_0001: ret
}
}
...ファイルrecomp_srp.msilに保存する場合は、次のように単純に再コンパイルできます。
ildasm / dll recomp_srp.msil
そして、私の終わりにボクシングなしで正常に実行されます:
<*>...もちろん、保護から公開に変更しました。変更を元に戻し、残りの実装を提供する必要があります。
これは設計によるものです。 Tを特定のクラスに制限していないので、ほとんどの場合、オブジェクトにキャストします。したがって、ILにボクシングが含まれているのはなぜですか。
T:ActualClassでこのコードを試します
いくつかの点をフォローアップします。まず、このバグは、制約 where T:class
を持つ generic class の両方のメソッドと、同じ制約を持つ genericメソッドで発生します(ジェネリッククラスまたは非ジェネリッククラス)。 T
:
Object
を使用する(他の点では同一の)非汎用メソッドでは発生しません。
// static T XchgNullCur<T>(ref T addr, T value) where T : class =>
// Interlocked.CompareExchange(ref addr, val, null) ?? value;
.locals init (!T tmp)
ldarg addr
ldarg val
ldloca tmp
initobj !T
ldloc tmp
call !!0 Interlocked::CompareExchange<!T>(!!0&, !!0, !!0)
dup
box !T
brtrue L_001a
pop
ldarg val
L_001a:
ret
// static Object XchgNullCur(ref Object addr, Object val) =>
// Interlocked.CompareExchange(ref addr, val, null) ?? value;
ldarg addr
ldarg val
ldnull
call object Interlocked::CompareExchange(object&, object, object)
dup
brtrue L_000d
pop
ldarg val
L_000d:
ret
最初の例でいくつかの追加の問題に注意してください。単に ldnull
する代わりに、余分なローカル変数 tmp
を無意味にターゲットとする無関係な initobj
呼び出しがあります。
ただし、こちらで示唆されているように、これは重要ではありません。上記の2つの例で生成されたILコードには違いがありますが、 x64 JIT はほぼ同じコードを生成します。次の結果は、.NET Framework 4.7.2 リリースモードで、最適化&quot; not-suppressed&quot;を使用した場合です。