Tuesday 1 November 2011

C# Params and MethodInfo.Invoke

I guess everyone will agree that the params keyword in C# is pretty useful, but probably many of us will have never thought too much about how it works.
As I had a issue with it recently, I had to give it some extra thinking...

I was trying to call via MethodInfo.Invoke a method that was expecting a params object[].
MethodInfo.Invoke also expects a params object[].
And I was getting a nasty:
System.Reflection.TargetParameterCountException: Parameter count mismatch.

public static void WriteParams(params Object[] myParams)
 {
  foreach(Object param in myParams)
  {
   Console.WriteLine(param.ToString());
  }
 }

object[] myValues = new object[]{"hi", "man"};
  MethodInfo method = typeof(App).GetMethod("WriteParams");
  //runtime error: Parameter Count Mismatch
  try
  {
   method.Invoke(null, myValues);
  }
  catch (Exception ex)
  {
  }

If we have a look at the bytecodes generated for a call to a method expecting params where we're passing several arguments to the call, we'll see that there's some compiler help involved. The compiler takes care of wrapping those parameters into an array, that is what will be passed to the method. If we were already calling the method with an array, the compiler does not do any extra wrapping. This is rather well explained here

The params parameter modifer gives callers a shortcut syntax for passing multiple arguments to a method. There are two ways to call a method with a params parameter: 1) Calling with an array of the parameter type, in which case the params keyword has no effect and the array is passed directly to the method:
object[] array = new[] { "1", "2" };
// Foo receives the 'array' argument directly.
Foo( array );
2) Or, calling with an extended list of arguments, in which case the compiler will automatically wrap the list of arguments in a temporary array and pass that to the method:

In my case, as I was already passing an Object[] to MethodInfo.Invoke, this was not getting an extra wrap, and then, the Invoke method was passing the items in that array as individual parameters to a method that in this case also expected an array of parameters... (I guess that the Invoke method does not do any checking to see if the target method expects a params object[], remember that it's something done by the compiler).
so the Solution is just doing the extra wrapping myself when calling Invoke

//we  have to wrap the array into another array
  method.Invoke(null, new object[]{myValues});

We find the same problem with other dynamic invocation scenarios, like calling Delegate.DynamicInvoke.

Delegate deleg = new Action (WriteParams);
  try
  {
   deleg.DynamicInvoke(myValues);
  }
  catch (Exception ex)
  {
  }
  deleg.DynamicInvoke(new object[]{myValues});

All this brings to my mind another issue with params with a similar response. What happens if we have a method that expects a params object[], and we want to pass to it an only parameter that happens to be an object[] ?
By defaul, the method will be treating that object[] that we're passing as a params object[], so it would be as if we were passing n parameters to it, instead of an only parameter.
Again the solution is to wrap our Array in another Array. We can do it ourselves, or cast our Array to Object, so that the compiler itself does the extra wrapping for us.

You can check the source here

2 comments: