C# コンパイラ + ボックス化された汎用コード + 制約
質問
次の汎用メソッドに対して生成された MSIL コードを調べてみましょう。
public static U BoxValue<T, U>(T value)
where T : struct, U
where U : class
{
return value;
}
見て:
.method public hidebysig static !!U BoxValue<valuetype .ctor
([mscorlib]System.ValueType, !!U) T,class U>(!!T 'value') cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: box !!T
IL_0006: unbox.any !!U
IL_000b: ret
}
ただし、上記の汎用コードの場合、より効率的な IL 表現は次のようになります。
IL_0000: ldarg.0
IL_0001: box !!T
IL_0006: ret
制約から、値がボックス化されていることがわかります。 参照型. Unbox.any
オペコードは完全に冗長です。 box
opcode の IL スタックの値はすでに有効な参照になります。 !!U
, 、開封せずに使用できます。
C# 3.0 コンパイラは、より効率的な汎用コードを出力するために制約メタデータを使用しないのはなぜですか?Unbox.any はオーバーヘッドがわずかですが (4 倍から 5 倍遅いだけです)、このシナリオではより良いコードを出力してみませんか?
解決
ベリファイアにいくつかの問題があるため、コンパイラがこれを行っているようです。
コンパイラーに生成させたい IL は検証可能ではないため、C# コンパイラーはそれを生成できません (「安全でない」コンテキストの外側にあるすべての C# コードは検証可能である必要があります)。
「検証タイプの互換性」のルールは、Ecma 仕様のセクション 1.8.1.2.3、パート III に記載されています。
彼らは、型 'S' は、次の規則を使用して型 'T' または (S := T) と検証互換性があると言います。
- [:= は再帰的] すべての検証タイプ S、S := S
- [:= は推移的です] すべての検証タイプ S、T、U について、S := T および T := U の場合、S := U になります。
- S := T S が T の基本クラスまたは T によって実装されたインターフェイスであり、T が値型ではない場合。
- object := T (T がインターフェイス型の場合)。
- s:= tの場合、sとtの両方がインターフェイスであり、tの実装にはsの実装が必要です
- S := S がオブジェクト型またはインターフェイスの場合は null
- s []:= t [] s:= tの場合、アレイは両方のベクトル(ゼロベース、ランク1)のいずれかであるか、どちらもベクトルであり、両方が同じランクを持っています。(このルールは配列の共分散を扱います。)
- sとtがメソッドポインターである場合、s:= tの署名(戻り型、パラメータータイプ、呼び出し慣習)が同じ場合。
これらのルールのうち、この場合に適用できる唯一のルールは #3 です。
ただし、#3 はコードには適用されません。「U」は「T」の基本クラスではなく、「T」の基本インターフェイスでもないため、「or」チェックは false を返します。
これは、ベリファイアを通過できる方法でボックス化された T を U に変換するには、何らかの命令を実行する必要があることを意味します。
必要なコードの生成が実際に検証可能となるように、検証ルールを変更する必要があるという意見には私も同意します。
ただし、技術的には、コンパイラーは ECMA 仕様に基づいて「正しい」ことを行っています。
Microsoft の誰かにバグを報告する必要があります。
他のヒント
これらの制約は奇妙に見えるます:
where T : struct, U
where U : class
Tは値型であるが、同じ時間での参照タイプであるUから継承しなければなりません。私は、上記の制約を満たし、私たちは、このメソッドを呼び出す可能性がありますどのような種類のだろうか。