2008-09-16 13 views
7

Microsoft ILでは、値型のメソッドを呼び出すには間接参照が必要です。私たちは「IL」という名前のILGeneratorを持っており、それは現在、我々はそれが価値があるかどうかを確認したい場合は、我々は次のように放出することができ、スタックの一番上のNullableを考えてみましょう:間接的にスタックに値型をロードすることは可能ですか?

var local = il.DeclareLocal(typeof(Nullable<int>)); 
il.Emit(OpCodes.Stloc, local); 
il.Emit(OpCodes.Ldloca, local); 
var method = typeof(Nullable<int>).GetMethod("get_HasValue"); 
il.EmitCall(OpCodes.Call, method, null); 

しかし、それは希望ローカル変数として保存スキップして、単純に、何かのようにすでにスタック上の変数のアドレスにメソッドを呼び出すために良いこと:(特にldind_ref)

il.Emit(/* not sure */); 
var method = typeof(Nullable<int>).GetMethod("get_HasValue"); 
il.EmitCall(OpCodes.Call, method, null); 

命令のldindファミリーが有望に見えるけどこれが値のボクシングを引き起こすかどうかを知るための十分な文書を見つけることができません。

私はC#コンパイラの出力を見てきましたが、これを実現するためにローカル変数を使用しています。これにより、最初の方法が唯一の方法である可能性があります。誰もが良いアイデアを持っていますか?

****編集:追加の注意事項****コメントアウト行で、以下のプログラムのように、直接メソッドを呼び出そうと

は、動作しません(エラーは、操作ができた「となりますランタイムを不安定にする)。行のコメントを外し、期待どおりに動作し、 "True"を返すことがわかります。それは値型なので(あなたはできるものの、それが参照型である場合)

var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes); 
var il = m.GetILGenerator(); 
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) }); 
il.Emit(OpCodes.Ldc_I4_6); 
il.Emit(OpCodes.Newobj, ctor); 
//var local = il.DeclareLocal(typeof(Nullable<int>)); 
//il.Emit(OpCodes.Stloc, local); 
//il.Emit(OpCodes.Ldloca, local); 
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue"); 
il.Emit(OpCodes.Call, getValue); 
il.Emit(OpCodes.Ret); 
Console.WriteLine(m.Invoke(null, null)); 

だから、単純にスタック上の値でメソッドを呼び出すことはできません。

私が達成したいのは(あるいは可能かどうかを知るために)、コメントアウトされた3行を置き換えて一時的なローカルを使わずにプログラムを動作させ続けることです。

答えて

2

変数がすでにスタックにある場合は、先に進んでメソッド呼び出しを発行できます。

コンストラクタは、型指定された形式でスタック上の変数をプッシュしないようです。 ILを少し掘り下げた後、変数を構築した後に変数を使用するには2つの方法があるようです。

あなたはコンストラクタを呼び出す前に、評価スタックに参照を格納して、そのようにコンストラクタを呼び出した後に、再びその変数をロードする変数をロードすることができます:他のオプションがそれをやっている

DynamicMethod method = new DynamicMethod("M", typeof(bool), Type.EmptyTypes); 
ILGenerator il = method.GetILGenerator(); 
Type nullable = typeof(Nullable<int>); 
ConstructorInfo ctor = nullable.GetConstructor(new Type[] { typeof(int) }); 
MethodInfo getValue = nullable.GetProperty("HasValue").GetGetMethod(); 
LocalBuilder value = il.DeclareLocal(nullable);   

// load the variable to assign the value from the ctor to 
il.Emit(OpCodes.Ldloca_S, value); 
// load constructor args 
il.Emit(OpCodes.Ldc_I4_6); 
il.Emit(OpCodes.Call, ctor); 
il.Emit(OpCodes.Ldloca_S, value); 

il.Emit(OpCodes.Call, getValue); 
il.Emit(OpCodes.Ret); 
Console.WriteLine(method.Invoke(null, null)); 

をあなたが示した通りです。私が見ることができる唯一の理由は、ctorメソッドがvoidを返すため、他のメソッドのようにスタックに値を入れないことです。新しいオブジェクトがスタックにない場合、Setlocを呼び出すことができるのは奇妙なことです。

+0

これも完全に動作しますが、引き続きローカルが必要です。私はこれが、地元の人がいなくてもこれをすることはできないという私の最初の疑惑を確認し始めていると思います。 Stlocは、ローカルに格納されているタイプの追加のメタデータがあるために動作すると思いますが、もう一度呼び出しが行われます。 Odd。 –

+0

ええ、私はそれが期待したようにうまくいかないことが非常に奇妙であることを発見しました。あなたの動的メソッドがNullable を引数として取る場合は、ldarg_0を呼び出すだけで済みますが、メソッドで値の型を実際に作成する必要があると仮定しています。 –

+0

実際、メソッド内で値を作成する必要があります。私が持っている問題は、かなりの数の地方自治体が一時的な操作のために純粋に使用されていることを意味する(異なるタイプの)方法ごとにかなりの数があることです。それは大したことではなく、すべて正常に動作しますが、ちょっと面倒です。 –

1

オプションをもう少し詳しく検討した後、あなたはそれができないと仮定して正しいと思います。 MSIL命令のスタック動作を調べると、オペレーションがスタックにそのオペランドを残さないことがわかります。これは 'スタックエントリのアドレスを取得する'ための要件であるため、私はかなり存在しないと確信しています。

これにより、dup + boxまたはstloc + ldlocaのいずれかが表示されます。あなたが指摘したように、後者はおそらくより効率的です。

@グレッグ:多くの命令は、スタック上でその結果を残したが、何の命令が「スタックの要素のアドレスを取得」命令に必要とされるスタック上にそのオペランド、のいずれかを残していません。

+0

newobj命令はその結果をスタックに残します。これが参照型の場合は、すぐにメソッドを呼び出すことができます。基本的に私はあなたが正しいと思うスタックの上に構造体の値がありますが、私はそれのメソッドを呼び出すためにその値のアドレスが必要です。 –

+0

実際、C#チームは、オプションで 'ref'戻り値を許可するという考えを簡単に検討しました。これは、このディスカッションが示唆していることです:http://blogs.msdn.com/b/ericlippert/archive/2011/06/ 23/ref-returns-and-ref-locals.aspx –

0

ちょうどOPが求めているものを行い、クラスを書いた...ここでC#コンパイラが生成するILコードです:

IL_0008: ldarg.0 
    IL_0009: ldarg.1 
    IL_000a: newobj  instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0) 
    IL_000f: stfld  valuetype [mscorlib]System.Nullable`1<int32> ConsoleApplication3.Temptress::_X 
    IL_0014: nop 
    IL_0015: ret 
1

私はそれを考え出しました!幸運にも、私はunboxオペコードについて読んでいて、アドレスの値をプッシュすることに気づいた。 unbox.anyは実際の値をプッシュします。そのため、ローカル変数に値を格納せずに値の型でメソッドを呼び出し、そのアドレスをロードするには、単にboxの後にunboxを続けるだけです。

var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes); 
var il = m.GetILGenerator(); 
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) }); 
il.Emit(OpCodes.Ldc_I4_6); 
il.Emit(OpCodes.Newobj, ctor); 
il.Emit(OpCodes.Box, typeof(Nullable<int>)); // box followed by unbox 
il.Emit(OpCodes.Unbox, typeof(Nullable<int>)); 
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue"); 
il.Emit(OpCodes.Call, getValue); 
il.Emit(OpCodes.Ret); 
Console.WriteLine(m.Invoke(null, null)); 

この方法の欠点は、ボクシングは箱入りのオブジェクトのメモリ割り当てを引き起こすことがあるので、それは(すでに割り当てられる)ローカル変数を使用するよりも少し遅い:あなたの最後の例を使用。しかし、必要なすべてのローカル変数を決定、宣言、参照する必要がなくなります。

関連する問題