Export (0) Print
Expand All

How to: Define a Generic Method with Reflection Emit

The first procedure shows how to create a simple generic method with two type parameters, and how to apply class constraints, interface constraints, and special constraints to the type parameters.

The second procedure shows how to emit the method body, and how to use the type parameters of the generic method to create instances of generic types and to call their methods.

The third procedure shows how to invoke the generic method.

Important note Important Note:

A method is not generic just because it belongs to a generic type and uses the type parameters of that type. A method is generic only if it has its own type parameter list. A generic method can appear on a nongeneric type, as in this example. For an example of a nongeneric method on a generic type, see How to: Define a Generic Type with Reflection Emit.

To define a generic method

  1. Before beginning, it is useful to look at how the generic method appears when it is written using a high-level language. The following code is included in the example code for this topic, along with code to call the generic method. The method has two type parameters, TInput and TOutput, the second of which must be a reference type (class), must have a parameterless constructor (new), and must implement ICollection(Of TInput) (ICollection<TInput> in C#). This interface constraint ensures that the ICollection<T>.Add method can be used to add elements to the TOutput collection that the method creates. The method has one formal parameter, input, which is an array of TInput. The method creates a collection of type TOutput and copies the elements of input to the collection.

    
    public static TOutput Factory<TInput, TOutput>(TInput[] tarray)
        where TOutput : class, ICollection<TInput>, new()
    {
       TOutput ret = new TOutput();
       ICollection<TInput> ic = ret;
    
       foreach (TInput t in tarray)
       {
          ic.Add(t);
       }
       return ret;
    }
    
    
    
  2. Define a dynamic assembly and a dynamic module to contain the type the generic method belongs to. In Silverlight, a dynamic assembly has only one module and is always created with AssemblyBuilderAccess.Run

    
    AssemblyName asmName = new AssemblyName("DemoMethodBuilder1");
    AppDomain domain = AppDomain.CurrentDomain;
    AssemblyBuilder demoAssembly =
        domain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
    
    // Define the module that contains the code. For an 
    // assembly with one module, the module name is the 
    // assembly name plus a file extension.
    ModuleBuilder demoModule = demoAssembly.DefineDynamicModule(asmName.Name);
    
    
    
  3. Define the type the generic method belongs to. The type does not have to be generic. A generic method can belong to either a generic or nongeneric type. In this example, the type is a class, is not generic, and is named DemoType.

    
    TypeBuilder demoType =
        demoModule.DefineType("DemoType", TypeAttributes.Public);
    
    
    
  4. Define the generic method. If the types of a generic method's formal parameters are specified by generic type parameters of the generic method, use the DefineMethod(String, MethodAttributes) method overload to define the method. The generic type parameters of the method are not yet defined, so you cannot specify the types of the method's formal parameters in the call to DefineMethod. In this example, the method is named Factory. The method is public and static (Shared in Visual Basic).

    
    MethodBuilder factory =
        demoType.DefineMethod("Factory",
            MethodAttributes.Public | MethodAttributes.Static);
    
    
    
  5. Define the generic type parameters of DemoMethod by passing an array of strings that contains the parameter names to the MethodBuilder.DefineGenericParameters method. This makes the method a generic method. The following code makes Factory a generic method with type parameters TInput and TOutput. To make the code easier to read, variables with these names are created to hold the GenericTypeParameterBuilder objects representing the two type parameters.

    
    string[] typeParameterNames = { "TInput", "TOutput" };
    GenericTypeParameterBuilder[] typeParameters =
        factory.DefineGenericParameters(typeParameterNames);
    
    GenericTypeParameterBuilder TInput = typeParameters[0];
    GenericTypeParameterBuilder TOutput = typeParameters[1];
    
    
    
  6. Optionally add special constraints to the type parameters. Special constraints are added by using the SetGenericParameterAttributes method. In this example, TOutput is constrained to be a reference type and to have a parameterless constructor.

    
    TOutput.SetGenericParameterAttributes(
        GenericParameterAttributes.ReferenceTypeConstraint |
        GenericParameterAttributes.DefaultConstructorConstraint);
    
    
    
  7. Optionally add class and interface constraints to the type parameters. In this example, type parameter TOutput is constrained to types that implement the ICollection(Of TInput) (ICollection<TInput> in C#) interface. This ensures that the Add method can be used to add elements.

    
    Type icoll = typeof(ICollection<>);
    Type icollOfTInput = icoll.MakeGenericType(TInput);
    Type[] constraints = { icollOfTInput };
    TOutput.SetInterfaceConstraints(constraints);
    
    
    
  8. Define the formal parameters of the method, using the SetParameters method. In this example, the Factory method has one parameter, an array of TInput. This type is created by calling the MakeArrayType method on the GenericTypeParameterBuilder that represents TInput. The argument of SetParameters is an array of Type objects.

    
    Type[] parms = { TInput.MakeArrayType() };
    factory.SetParameters(parms);
    
    
    
  9. Define the return type for the method, using the SetReturnType method. In this example, an instance of TOutput is returned.

    
    factory.SetReturnType(TOutput);
    
    
    
  10. Emit the method body, using ILGenerator. For details, see the accompanying procedure To Emit the Method Body.

    Important note Important Note:

    When you emit calls to methods of generic types, and the type arguments of those types are type parameters of the generic method, you must use the static GetConstructor(Type, ConstructorInfo), GetMethod(Type, MethodInfo), and GetField(Type, FieldInfo) method overloads of the TypeBuilder class to obtain constructed forms of the methods. The accompanying procedure for emitting the method body demonstrates this.

  11. Complete the type that contains the method. The accompanying procedure To Invoke the Generic Method shows two ways to invoke the completed method.

    
    // Complete the type.
    Type dt = demoType.CreateType();
    
    
    

To emit the method body

  1. Get a code generator and declare local variables and labels. The DeclareLocal method is used to declare local variables. The Factory method has four local variables: retVal to hold the new TOutput that is returned by the method, ic to hold the TOutput when it is cast to ICollection(Of TInput) (ICollection<TInput> in C#), input to hold the input array of TInput objects, and index to iterate through the array. The method also has two labels, one to enter the loop (enterLoop) and one for the top of the loop (loopAgain). These labels are defined by using the DefineLabel method.

    First, the method loads its argument by using the Ldarg_0 opcode and stores it in the local variable input by using the Stloc_S opcode.

    
    ILGenerator ilgen = factory.GetILGenerator();
    
    LocalBuilder retVal = ilgen.DeclareLocal(TOutput);
    LocalBuilder ic = ilgen.DeclareLocal(icollOfTInput);
    LocalBuilder input = ilgen.DeclareLocal(TInput.MakeArrayType());
    LocalBuilder index = ilgen.DeclareLocal(typeof(int));
    
    Label enterLoop = ilgen.DefineLabel();
    Label loopAgain = ilgen.DefineLabel();
    
    ilgen.Emit(OpCodes.Ldarg_0);
    ilgen.Emit(OpCodes.Stloc_S, input);
    
    
    
  2. Emit code to create an instance of TOutput, using the generic method overload of the Activator.CreateInstance method. Using this overload requires the specified type to have a parameterless constructor, which is the reason for adding that constraint to TOutput. Create the constructed generic method by passing TOutput to MakeGenericMethod. After you emit code to call the method, emit code to store it in the local variable retVal by using Stloc_S

    
    MethodInfo createInst =
        typeof(Activator).GetMethod("CreateInstance", Type.EmptyTypes);
    MethodInfo createInstOfTOutput =
        createInst.MakeGenericMethod(TOutput);
    
    ilgen.Emit(OpCodes.Call, createInstOfTOutput);
    ilgen.Emit(OpCodes.Stloc_S, retVal);
    
    
    
  3. Emit code to cast the new TOutput object to ICollection(Of TInput) and store it in the local variable ic.

    
    ilgen.Emit(OpCodes.Ldloc_S, retVal);
    ilgen.Emit(OpCodes.Box, TOutput);
    ilgen.Emit(OpCodes.Castclass, icollOfTInput);
    ilgen.Emit(OpCodes.Stloc_S, ic);
    
    
    
  4. Get a MethodInfo that represents the ICollection<T>.Add method. The method is acting on an ICollection(Of TInput) (ICollection<TInput> in C#), so you must get the Add method that is specific to that constructed type. You cannot use the GetMethod method to get this MethodInfo directly from icollOfTInput, because GetMethod is not supported on a type that has been constructed with a GenericTypeParameterBuilder. Instead, call GetMethod on icoll, which contains the generic type definition for the ICollection<T> generic interface. Then use the GetMethod(Type, MethodInfo) static method to produce the MethodInfo for the constructed type. The following code demonstrates this.

    
    MethodInfo mAddPrep = icoll.GetMethod("Add");
    MethodInfo mAdd = TypeBuilder.GetMethod(icollOfTInput, mAddPrep);
    
    
    
  5. Emit code to initialize the index variable, by loading a 32-bit integer 0 and storing it in the variable. Emit code to branch to the label enterLoop. This label has not yet been marked, because it is inside the loop. Code for the loop is emitted in the next step.

    
    // Initialize the count and enter the loop.
    ilgen.Emit(OpCodes.Ldc_I4_0);
    ilgen.Emit(OpCodes.Stloc_S, index);
    ilgen.Emit(OpCodes.Br_S, enterLoop);
    
    
    
  6. Emit code for the loop. The first step is to mark the top of the loop, by calling the MarkLabel method with the loopAgain label. Branch statements that use the label will now branch to this point in the code. The next step is to push the TOutput object, which is cast to ICollection(Of TInput), onto the stack. It is not needed immediately but needs to be in position for calling the Add method. Next, the input array is pushed onto the stack, and then the index variable that contains the current index into the array. The Ldelem opcode pops the index and the array off the stack and pushes the indexed array element onto the stack. The stack is now ready for the call to the ICollection<T>.Add method, which pops the collection and the new element off the stack and adds the element to the collection.

    The rest of the code in the loop increments the index and tests to see whether the loop is finished: The index and a 32-bit integer 1 are pushed onto the stack and added, leaving the sum on the stack; the sum is stored in index. MarkLabel is called to set this point as the entry point for the loop. The index is loaded again. The input array is pushed on the stack, and Ldlen is emitted to get its length. The index and the length are now on the stack, and Clt is emitted to compare them. If the index is less than the length, Brtrue_S branches back to the beginning of the loop.

    
    ilgen.MarkLabel(loopAgain);
    
    ilgen.Emit(OpCodes.Ldloc_S, ic);
    ilgen.Emit(OpCodes.Ldloc_S, input);
    ilgen.Emit(OpCodes.Ldloc_S, index);
    ilgen.Emit(OpCodes.Ldelem, TInput);
    ilgen.Emit(OpCodes.Callvirt, mAdd);
    
    ilgen.Emit(OpCodes.Ldloc_S, index);
    ilgen.Emit(OpCodes.Ldc_I4_1);
    ilgen.Emit(OpCodes.Add);
    ilgen.Emit(OpCodes.Stloc_S, index);
    
    ilgen.MarkLabel(enterLoop);
    ilgen.Emit(OpCodes.Ldloc_S, index);
    ilgen.Emit(OpCodes.Ldloc_S, input);
    ilgen.Emit(OpCodes.Ldlen);
    ilgen.Emit(OpCodes.Conv_I4);
    ilgen.Emit(OpCodes.Clt);
    ilgen.Emit(OpCodes.Brtrue_S, loopAgain);
    
    
    
  7. Emit code to push the TOutput object onto the stack and return from the method. The local variables retVal and ic both contain references to the new TOutput; ic is used only to access the ICollection<T>.Add method.

    
    ilgen.Emit(OpCodes.Ldloc_S, retVal);
    ilgen.Emit(OpCodes.Ret);
    
    
    

To invoke the generic method

  1. Factory is a generic method definition. To invoke it, you must assign types to its generic type parameters. Use the MakeGenericMethod method to do this. The following code creates a constructed generic method, specifying String for TInput and List(Of String) (List<string> in C#) for TOutput, and displays a string representation of the method.

    
    MethodInfo m = dt.GetMethod("Factory");
    MethodInfo bound =
        m.MakeGenericMethod(typeof(string), typeof(List<string>));
    
    // Display a string representing the bound method.
    outputBlock.Text += bound + "\n";
    
    
    
  2. To invoke the method late-bound, use the Invoke method. The following code creates an array of Object, which contains as its only element an array of strings, and passes it as the argument list for the generic method. The first parameter of Invoke is a null reference because the method is static. The return value is cast to List(Of String), and its first element is displayed.

    
    object o = bound.Invoke(null, new object[] { arr });
    List<string> list2 = (List<string>)o;
    
    outputBlock.Text += String.Format("The first element is: {0}", list2[0]) + "\n";
    
    
    
  3. To invoke the method by using a delegate, you must have a delegate that matches the signature of the constructed generic method. An easy way to do this is to create a generic delegate. The following code creates an instance of the generic delegate D defined in the example code, using the Delegate.CreateDelegate(Type, MethodInfo) method overload, and invokes the delegate. Delegates perform better than late-bound calls.

    
    Type dType = typeof(D<string, List<string>>);
    D<string, List<string>> test;
    test = (D<string, List<string>>)
        Delegate.CreateDelegate(dType, bound);
    
    List<string> list3 = test(arr);
    outputBlock.Text += String.Format("The first element is: {0}", list3[0]) + "\n";
    
    
    

The following example creates a nongeneric type, DemoType, with a generic method, Factory. This method has two generic type parameters, TInput to specify an input type and TOutput to specify an output type. The TOutput type parameter is constrained to implement ICollection<TInput> (ICollection(Of TInput) in Visual Basic), to be a reference type, and to have a parameterless constructor.

The method has one formal parameter, which is an array of TInput. The method returns an instance of TOutput that contains all the elements of the input array. TOutput can be any generic collection type that implements the ICollection<T> generic interface.

Note Note:

A good way to learn how to emit code is to write a Visual Basic, C#, or Visual C++ program that performs the task you are trying to emit, and use the disassembler to examine the Microsoft intermediate language (MSIL) that is produced by the compiler.

The code example includes source code that is equivalent to the emitted method. The emitted method is invoked late-bound and also by using a generic delegate that is declared in the code example.


using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

// Declare a generic delegate that can be used to execute the 
// finished method.
//
public delegate TOut D<TIn, TOut>(TIn[] input);

class Example
{
   // This method shows how to declare, in C#, the generic
   // method this program emits. The method has two type parameters,
   // TInput and TOutput, the second of which must be a reference type
   // (class), must have a parameterless constructor (new()), and must
   // implement ICollection<TInput>. This interface constraint
   // ensures that ICollection<TInput>.Add can be used to add
   // elements to the TOutput object the method creates. The method 
   // has one formal parameter, input, which is an array of TInput. 
   // The elements of this array are copied to the new TOutput.
   //
   public static TOutput Factory<TInput, TOutput>(TInput[] tarray)
       where TOutput : class, ICollection<TInput>, new()
   {
      TOutput ret = new TOutput();
      ICollection<TInput> ic = ret;

      foreach (TInput t in tarray)
      {
         ic.Add(t);
      }
      return ret;
   }

   public static void Demo(System.Windows.Controls.TextBlock outputBlock)
   {
      // The following shows the usage syntax of the C#
      // version of the generic method emitted by this program.
      // Note that the generic parameters must be specified 
      // explicitly, because the compiler does not have enough 
      // context to infer the type of TOutput. In this case, TOutput
      // is a generic List containing strings.
      // 
      string[] arr = { "a", "b", "c", "d", "e" };
      List<string> list1 =
          Example.Factory<string, List<string>>(arr);
      outputBlock.Text += String.Format("The first element is: {0}\n", list1[0]);


      // Creating a dynamic assembly requires an AssemblyName
      // object, and the current application domain.
      //
      AssemblyName asmName = new AssemblyName("DemoMethodBuilder1");
      AppDomain domain = AppDomain.CurrentDomain;
      AssemblyBuilder demoAssembly =
          domain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);

      // Define the module that contains the code. For an 
      // assembly with one module, the module name is the 
      // assembly name plus a file extension.
      ModuleBuilder demoModule = demoAssembly.DefineDynamicModule(asmName.Name);

      // Define a type to contain the method.
      TypeBuilder demoType =
          demoModule.DefineType("DemoType", TypeAttributes.Public);

      // Define a public static method with standard calling
      // conventions. Do not specify the parameter types or the
      // return type, because type parameters will be used for 
      // those types, and the type parameters have not been
      // defined yet.
      //
      MethodBuilder factory =
          demoType.DefineMethod("Factory",
              MethodAttributes.Public | MethodAttributes.Static);

      // Defining generic type parameters for the method makes it a
      // generic method. To make the code easier to read, each
      // type parameter is copied to a variable of the same name.
      //
      string[] typeParameterNames = { "TInput", "TOutput" };
      GenericTypeParameterBuilder[] typeParameters =
          factory.DefineGenericParameters(typeParameterNames);

      GenericTypeParameterBuilder TInput = typeParameters[0];
      GenericTypeParameterBuilder TOutput = typeParameters[1];

      // Add special constraints.
      // The type parameter TOutput is constrained to be a reference
      // type, and to have a parameterless constructor. This ensures
      // that the Factory method can create the collection type.
      // 
      TOutput.SetGenericParameterAttributes(
          GenericParameterAttributes.ReferenceTypeConstraint |
          GenericParameterAttributes.DefaultConstructorConstraint);

      // Add interface and base type constraints.
      // The type parameter TOutput is constrained to types that
      // implement the ICollection<T> interface, to ensure that
      // they have an Add method that can be used to add elements.
      //
      // To create the constraint, first use MakeGenericType to bind 
      // the type parameter TInput to the ICollection<T> interface,
      // returning the type ICollection<TInput>, then pass
      // the newly created type to the SetInterfaceConstraints
      // method. The constraints must be passed as an array, even if
      // there is only one interface.
      //
      Type icoll = typeof(ICollection<>);
      Type icollOfTInput = icoll.MakeGenericType(TInput);
      Type[] constraints = { icollOfTInput };
      TOutput.SetInterfaceConstraints(constraints);

      // Set parameter types for the method. The method takes
      // one parameter, an array of type TInput.
      Type[] parms = { TInput.MakeArrayType() };
      factory.SetParameters(parms);

      // Set the return type for the method. The return type is
      // the generic type parameter TOutput.
      factory.SetReturnType(TOutput);

      // Generate a code body for the method. 
      // -----------------------------------
      // Get a code generator and declare local variables and
      // labels. Save the input array to a local variable.
      //
      ILGenerator ilgen = factory.GetILGenerator();

      LocalBuilder retVal = ilgen.DeclareLocal(TOutput);
      LocalBuilder ic = ilgen.DeclareLocal(icollOfTInput);
      LocalBuilder input = ilgen.DeclareLocal(TInput.MakeArrayType());
      LocalBuilder index = ilgen.DeclareLocal(typeof(int));

      Label enterLoop = ilgen.DefineLabel();
      Label loopAgain = ilgen.DefineLabel();

      ilgen.Emit(OpCodes.Ldarg_0);
      ilgen.Emit(OpCodes.Stloc_S, input);

      // Create an instance of TOutput, using the generic method 
      // overload of the Activator.CreateInstance method. 
      // Using this overload requires the specified type to have
      // a parameterless constructor, which is the reason for adding 
      // that constraint to TOutput. Create the constructed generic
      // method by passing TOutput to MakeGenericMethod. After
      // emitting code to call the method, emit code to store the
      // new TOutput in a local variable. 
      //
      MethodInfo createInst =
          typeof(Activator).GetMethod("CreateInstance", Type.EmptyTypes);
      MethodInfo createInstOfTOutput =
          createInst.MakeGenericMethod(TOutput);

      ilgen.Emit(OpCodes.Call, createInstOfTOutput);
      ilgen.Emit(OpCodes.Stloc_S, retVal);

      // Load the reference to the TOutput object, cast it to
      // ICollection<TInput>, and save it.
      //
      ilgen.Emit(OpCodes.Ldloc_S, retVal);
      ilgen.Emit(OpCodes.Box, TOutput);
      ilgen.Emit(OpCodes.Castclass, icollOfTInput);
      ilgen.Emit(OpCodes.Stloc_S, ic);

      // Loop through the array, adding each element to the new
      // instance of TOutput. Note that in order to get a MethodInfo
      // for ICollection<TInput>.Add, it is necessary to first 
      // get the Add method for the generic type defintion,
      // ICollection<T>.Add. This is because it is not possible
      // to call GetMethod on icollOfTInput. The static overload of
      // TypeBuilder.GetMethod produces the correct MethodInfo for
      // the constructed type.
      //
      MethodInfo mAddPrep = icoll.GetMethod("Add");
      MethodInfo mAdd = TypeBuilder.GetMethod(icollOfTInput, mAddPrep);

      // Initialize the count and enter the loop.
      ilgen.Emit(OpCodes.Ldc_I4_0);
      ilgen.Emit(OpCodes.Stloc_S, index);
      ilgen.Emit(OpCodes.Br_S, enterLoop);

      // Mark the beginning of the loop. Push the ICollection
      // reference on the stack, so it will be in position for the
      // call to Add. Then push the array and the index on the 
      // stack, get the array element, and call Add (represented
      // by the MethodInfo mAdd) to add it to the collection.
      //
      // The other ten instructions just increment the index
      // and test for the end of the loop. Note the MarkLabel
      // method, which sets the point in the code where the 
      // loop is entered. (See the earlier Br_S to enterLoop.)
      //
      ilgen.MarkLabel(loopAgain);

      ilgen.Emit(OpCodes.Ldloc_S, ic);
      ilgen.Emit(OpCodes.Ldloc_S, input);
      ilgen.Emit(OpCodes.Ldloc_S, index);
      ilgen.Emit(OpCodes.Ldelem, TInput);
      ilgen.Emit(OpCodes.Callvirt, mAdd);

      ilgen.Emit(OpCodes.Ldloc_S, index);
      ilgen.Emit(OpCodes.Ldc_I4_1);
      ilgen.Emit(OpCodes.Add);
      ilgen.Emit(OpCodes.Stloc_S, index);

      ilgen.MarkLabel(enterLoop);
      ilgen.Emit(OpCodes.Ldloc_S, index);
      ilgen.Emit(OpCodes.Ldloc_S, input);
      ilgen.Emit(OpCodes.Ldlen);
      ilgen.Emit(OpCodes.Conv_I4);
      ilgen.Emit(OpCodes.Clt);
      ilgen.Emit(OpCodes.Brtrue_S, loopAgain);

      ilgen.Emit(OpCodes.Ldloc_S, retVal);
      ilgen.Emit(OpCodes.Ret);

      // Complete the type.
      Type dt = demoType.CreateType();

      // To create a constructed generic method that can be
      // executed, first call the GetMethod method on the completed 
      // type to get the generic method definition. Call MakeGenericType
      // on the generic method definition to obtain the constructed
      // method, passing in the type arguments. In this case, the
      // constructed method has string for TInput and List<string>
      // for TOutput. 
      //
      MethodInfo m = dt.GetMethod("Factory");
      MethodInfo bound =
          m.MakeGenericMethod(typeof(string), typeof(List<string>));

      // Display a string representing the bound method.
      outputBlock.Text += bound + "\n";


      // Once the generic method is constructed, 
      // you can invoke it and pass in an array of objects 
      // representing the arguments. In this case, there is only
      // one element in that array, the argument 'arr'.
      //
      object o = bound.Invoke(null, new object[] { arr });
      List<string> list2 = (List<string>)o;

      outputBlock.Text += String.Format("The first element is: {0}", list2[0]) + "\n";


      // You can get better performance from multiple calls if
      // you bind the constructed method to a delegate. The 
      // following code uses the generic delegate D defined 
      // earlier.
      //
      Type dType = typeof(D<string, List<string>>);
      D<string, List<string>> test;
      test = (D<string, List<string>>)
          Delegate.CreateDelegate(dType, bound);

      List<string> list3 = test(arr);
      outputBlock.Text += String.Format("The first element is: {0}", list3[0]) + "\n";
   }
}

/* This code example produces the following output:

The first element is: a
System.Collections.Generic.List`1[System.String] Factory[String,List`1](System.String[])
The first element is: a
The first element is: a
 */


  • The code contains the C# using statements (Imports in Visual Basic) necessary for compilation.

  • No additional assembly references are required.

  • The code contains a static (Shared in Visual Basic) Demo method that has one parameter, a TextBlock that is used to display the output. For instructions on building the code as part of a simple Silverlight-based application, see Building Examples That Use a Demo Method and a TextBlock Control. You can put all the code in this sample into one source file.

Community Additions

ADD
Show:
© 2014 Microsoft