Partager via


ILGenerator.EmitCall Mainly For vararg Methods

Vararg (variable arguments) methods accept argument lists of unknown length and type. CLR supports this by the IL instruction (arglist) and other BCL types, such as System.ArgIterator. C# compiler has undocumented keyword "__arglist" to support defining vararg methods, accessing variable arguments and calling them, as shown below.

static void VarargMethod(string headline, __arglist) {
  ArgIterator ai = new ArgIterator(__arglist);
 
  Console.Write(headline);
  while (ai.GetRemainingCount() > 0)
    Console.Write(TypedReference.ToObject(ai.GetNextArg()));
    Console.WriteLine();
}
 
static void CallVarargMethod() {
  VarargMethod("Hello world from ", __arglist(Assembly.GetExecutingAssembly()));
  VarargMethod("Current time: ", __arglist(DateTime.Now, " (UTC ", DateTime.UtcNow, ")"));
}

Two MemberRef tokens are needed to specify each vararg method call site. The blob each MemberRef points to specifies the methodref signature: types of both fixed and variable arguments.

MethodName: VarargMethod (06000001)
MemberRef #1 (0a00000c)
    2 Arguments
        Argument #1: String
        Argument #2: <ELEMENT_TYPE_SENTINEL> Class System.Reflection.Assembly
MemberRef #2 (0a00000f)
    5 Arguments
        Argument #1: String
        Argument #2: <ELEMENT_TYPE_SENTINEL> ValueClass System.DateTime
        Argument #3: String
        Argument #4: ValueClass System.DateTime
        Argument #5: String

ILGenerator.EmitCall(OpCode opcode, MethodInfo methodInfo, Type[] optionalParameterTypes) is designed for emitting call to such vararg method (for both Reflection.Emit and dynamic method scenarios). The optionalParameterTypes argument is passed in to create different MethodRef tokens.

Assume the MethodInfo of "VarargMethod" is miVarargMethod, the following code creates a DynamicMethod to perform the same functionality as previous "CallVarargMethod" does:

DynamicMethod dm = new DynamicMethod("CallVarargMethod", typeof(void), Type.EmptyTypes, typeof(object));
ILGenerator il = dm.GetILGenerator();
 
il.Emit(OpCodes.Ldstr, "Hello world from ");
il.Emit(OpCodes.Call, typeof(Assembly).GetMethod("GetExecutingAssembly"));
il.EmitCall(OpCodes.Call, miVarargMethod, new Type[] { typeof(Assembly) });
 
il.Emit(OpCodes.Ldstr, "Current time: ");
il.Emit(OpCodes.Call, typeof(DateTime).GetMethod("get_Now"));
il.Emit(OpCodes.Ldstr, " (UTC ");
il.Emit(OpCodes.Call, typeof(DateTime).GetMethod("get_UtcNow"));
il.Emit(OpCodes.Ldstr, ")");
il.EmitCall(OpCodes.Call, miVarargMethod, new Type[] { typeof(DateTime), typeof(string), typeof(DateTime), typeof(string) });
 
il.Emit(OpCodes.Ret);

As you see in the attached C# code (which uses ILGenerator.EmitCall for both Reflection.Emit and DynamicMethod), Type.GetMethod (and other APIs) can return MethodInfo of vararg methods; however MethodInfo.Invoke currently does not support to invoke on them. It is easy to get around with this: just create a dynamic method, and use EmitCall, and invoke on the dynamic method.

Although EmitCall can be used to emit a call to non-vararg method, you are encouraged to use ILGenerator.Emit(OpCode, MethodInfo) for that scenario, and use EmitCall mainly for vararg methods. With that said, the naming of this API does sound somewhat misleading.

VarargMethod.cs