为什么存储局部变量并读回它会触发 TargetInvocationException?

Why does storing a local variable and reading it back trigger a TargetInvocationException?

假设我有这个方法:

MethodBuilder doubleMethod = typeBuilder.DefineMethod("Double", 
                                                     MethodAttributes.Public | MethodAttributes.Static,
                                                     typeof(int), new [] { typeof(int) });
ILGenerator il = countMethod.GetILGenerator();

il.Emit(OpCodes.Ldarg_0);  // We load the input argument (an int) into the evaluation stack
il.Emit(OpCodes.Ldc_I4_2); // We load the integer 2 into the evaluation stack
il.Emit(OpCodes.Mul);      // We multiply both numbers (n * 2)
il.Emit(OpCodes.Ret);      // We return what is left on the evaluation stack, i.e. the result of the multiplication

我可以成功调用这个方法:

Type type = typeBuilder.CreateType();
MethodInfo method = type.GetMethod("Double");
object result = method.Invoke(null, new object[] { 4 }); // result = 8

但是,如果我将我的 IL 代码更改为:

il.Emit(OpCodes.Ldarg_0);  // We load the input argument (an int) into the evaluation stack
il.Emit(OpCodes.Ldc_I4_2); // We load the integer 2 into the evaluation stack
il.Emit(OpCodes.Mul);      // We multiply both numbers (n * 2)

/* I added these two instructions */
il.Emit(OpCodes.Stloc_0);  // We pop the value from the evaluation stack and store into a local variable
il.Emit(OpCodes.Ldloc_0);  // We read that local variable and push it back into the evaluation stack

il.Emit(OpCodes.Ret);      // We return what is left on the evaluation stack, i.e. the result of the multiplication 

尝试调用生成的方法时,抛出以下异常:

TargetInvocationException was unhandled - Exception has been thrown by the target of an invocation.

这是为什么?我的意思是,从评估堆栈中弹出值然后再次压入相同的值应该绝对没有任何作用。当它到达 OpCodes.Ret 时,正确的值应该在计算堆栈上。

要在 IL 中使用局部变量,您首先需要声明它,以便运行时知道它的类型。为此,请使用 ILGenerator.DeclareLocal().

在发出使用该变量的指令时,您也可以考虑 using the LocalBuilder returned from DeclareLocal()。这样,您就不需要记住所有局部变量的索引:

var local = il.DeclareLocal(typeof(int));

…

il.Emit(OpCodes.Stloc, local);
il.Emit(OpCodes.Ldloc, local);