Cook Computing

ParamArrayAttribute Codegen

November 9, 2005 Written by Charles Cook

I mentioned a while ago that I'm adding support for XML-RPC methods with a variable number of parameters in XML-RPC.NET. I recently enhanced the XmlRpcProxyGen class to add the ParamArrayAttribute where required. I wrote the following sample program to explore how to do this (just a few lines of new code) and to get some practice using Reflection.Emit (the implementation of the Foo method).


using System;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;

public interface IFoo
{
  int Foo(params int[] args);
}

class Program
{
  static void Main(string[] args)
  {
    AppDomain ad = AppDomain.CurrentDomain;
    Type itf = typeof(IFoo);
    AssemblyName asmName = new AssemblyName();
    asmName.Name = "FooAssembly";
    AssemblyBuilder asmBldr = ad.DefineDynamicAssembly(
      asmName, AssemblyBuilderAccess.Run);
    ModuleBuilder modBldr = asmBldr.DefineDynamicModule(
      asmName.Name);
    TypeBuilder typeBldr = modBldr.DefineType(
      "FooClass", TypeAttributes.Class | TypeAttributes.Public,
      null, new Type[] { itf });
    MethodBuilder mthdBldr = typeBldr.DefineMethod("Foo",
      MethodAttributes.Public | MethodAttributes.Virtual,
      typeof(int), new Type[] { typeof(int[]) } );

    // add ParamArrayAttribute to Foo parameter
    ParameterBuilder paramBldr = mthdBldr.DefineParameter(1,
      ParameterAttributes.In, "args");
    ConstructorInfo ctorInfo 
      = typeof(ParamArrayAttribute).GetConstructor(new Type[0]);
    CustomAttributeBuilder attrBldr =
        new CustomAttributeBuilder(ctorInfo, new object[0]);
    paramBldr.SetCustomAttribute(attrBldr);

    // generate the IL
    ILGenerator ilgen = mthdBldr.GetILGenerator();
    Label Next = ilgen.DefineLabel();
    Label Done = ilgen.DefineLabel();
    LocalBuilder total = ilgen.DeclareLocal (typeof (int));
    LocalBuilder idx = ilgen.DeclareLocal (typeof (int));
    
    // get count of arguments in array
    ilgen.Emit(OpCodes.Ldarg, 1);
    ilgen.Emit(OpCodes.Ldlen);
    ilgen.Emit(OpCodes.Stloc, idx);
    
    // return here for subsequent arguments
    ilgen.MarkLabel (Next);
    
    // decrement index - if result less than zero we're done
    ilgen.Emit(OpCodes.Ldloc, idx);
    ilgen.Emit(OpCodes.Ldc_I4_1);
    ilgen.Emit(OpCodes.Sub);
    ilgen.Emit(OpCodes.Dup);
    ilgen.Emit(OpCodes.Stloc, idx);
    ilgen.Emit(OpCodes.Ldc_I4_0);    
    ilgen.Emit(OpCodes.Blt, Done);
    
    // use index to get argument and add to total
    ilgen.Emit(OpCodes.Ldarg, 1);
    ilgen.Emit(OpCodes.Ldloc, idx);
    ilgen.Emit(OpCodes.Ldelem_I4);
    ilgen.Emit(OpCodes.Ldloc, total);
    ilgen.Emit(OpCodes.Add);
    ilgen.Emit(OpCodes.Stloc, total);
    
    // go back for next argument
    ilgen.Emit(OpCodes.Br_S, Next);

    // all arguments added - put total on stack and return
    ilgen.MarkLabel (Done);
    ilgen.Emit(OpCodes.Ldloc, total);
    ilgen.Emit(OpCodes.Ret);
    
    // build the type
    Type fooType = typeBldr.CreateType();

    // check IFoo.Foo has ParamArrayAttribute
    MethodInfo mi = fooType.GetMethod("Foo");
    ParameterInfo pi = mi.GetParameters()[0];
    Debug.Assert(Attribute.IsDefined(pi, 
      typeof(ParamArrayAttribute)));
    
    // create instance of type and test it
    IFoo ifoo = (IFoo)Activator.CreateInstance(fooType);
    Debug.Assert(ifoo.Foo() == 0);
    Debug.Assert(ifoo.Foo(1) == 1);
    Debug.Assert(ifoo.Foo(1,2) == 3);
    Debug.Assert(ifoo.Foo(1,2,3) == 6);
  }
}