Overcoming problems with MethodInfo.Invoke of methods with by-reference value type arguments

Published on Tuesday, April 29, 2008

Source

Overcoming problems with MethodInfo.Invoke of methods with by-reference value type arguments

I ran into an interesting problem on the Forums recently. Basically, whenyou use MethodInfo.Invoke to invoke a method with by-reference value type arguments you can't have the invoked method update a variable/argument. The problem is, when you invoke the method the parameter is passed to the MethodInfo.Invoke via an object array. Since we're dealing with a value type, the originalvalue typeis boxed and the invoked method actually updates the array element, not the original object (as it would with reference types). For example:

using System;

using System.Reflection.Emit;

using System.Reflection;

using System.Diagnostics;

namespace InvokeTesting

{

class Program

{

private const int testValue = 10;

public static void TestMethod(ref int i)

{

i = testValue;

}

static void Main(string[] args)

{

MethodInfo methodInfo = typeof (Program).GetMethod("TestMethod", BindingFlags.Static | BindingFlags.Public);

int i = 0;

object[] parameters = new object[] ;

methodInfo.Invoke(null, parameters);

// original variable isn't updated

Debug.Assert(i == 0);

// array element is updated:

Debug.Assert((int)parameters[0] == testValue);

// copy updated value to original variable

i = (int)parameters[0];

Debug.Assert(i == testValue);

}

}

}

Of course, the only "workaround" is to get the new value out of the array.

Since essentially we're implementing a run-time method invocation, a "better" way would be to create a dynamic method to make the call. A dynamic method is a method created at run-time (whose body is generated through ILGenerator.Emit et al) and invoked via a delegate. For example:

using System;

using System.Reflection.Emit;

using System.Reflection;

using System.Diagnostics;

namespace EmitTesting

{

// delegate for void method that takes one reference parameter

public delegate void OneRefParameterCallback(ref T value);

class Program

{

private const int testValue = 10;

public static void TestMethod(ref int i)

{

i = testValue;

}

static void Main(string[] args)

{

// create a dynamic method object with arbitrary name "Caller", void return (null), and one parameter "ref int"

DynamicMethod caller = new DynamicMethod("Caller", null, new Type[] { typeof(int).MakeByRefType() }, typeof(Program).Module);

ILGenerator ilGenerator = caller.GetILGenerator();

// emit ldarg.0

ilGenerator.Emit(OpCodes.Ldarg_0);

// emit call void EmitTesting.Program::TestMethod(int32&)

MethodInfo mi = typeof(Program).GetMethod("TestMethod", BindingFlags.Static | BindingFlags.Public, null,

         new Type[] { typeof(int).MakeByRefType() }, null);

ilGenerator.Emit(OpCodes.Call, mi);

// emit ret

ilGenerator.Emit(OpCodes.Ret);

OneRefParameterCallback callback =

(OneRefParameterCallback<int>)caller.CreateDelegate(typeof(OneRefParameterCallback<int>));

// call our emitted code with a reference parameter

int i = 0;

callback(ref i);

Debug.Assert(i == testValue);

}

}

}

In thisexample,the method invocation directly updates a value type variable. This could be made more re-usable to refactoring the dynamic method creation code into it's own method.

kick it on DotNetKicks.com

comments powered by Disqus