Search code examples
c#.netreflectioncilreflection.emit

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


Let's suppose I have this method:

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

I can call this method successfully:

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

However, If I change my IL code to this:

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 

When trying to call the generated method, the following exception is thrown:

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

Why is this? I mean, popping the value from the evaluation stack and then pushing the same value again should do absoultely nothing. By the time it reaches OpCodes.Ret, the correct value should be on the evaluation stack.


Solution

  • To use a local variable in IL, you first need to declare it, so that the runtime knows its type. To do that, use ILGenerator.DeclareLocal().

    You might also consider using the LocalBuilder returned from DeclareLocal() when emitting instructions that use the variable. This way, you don't need to remember the indexes of all your local variables:

    var local = il.DeclareLocal(typeof(int));
    
    …
    
    il.Emit(OpCodes.Stloc, local);
    il.Emit(OpCodes.Ldloc, local);