How to: Define Generic Methods with Reflection Emission
The second procedure demonstrates how to emit a method body and how to create an instance of a generic type using the type parameters of the generic method and call its method. The third procedure demonstrates how to call a generic method. Important: Just because a method belongs to a generic type and uses its type parameters, it cannot be called a generic method. If a method has its own type parameter list, it can only be called a generic method. Generic methods can appear on non-generic types, which is the case. For examples of non-generic methods on generic types, see How to: Define Generic Types with Reflection Emitting. Defining generic methods First, if you are writing in a high-level language, it is useful to see how generic methods are displayed. The following code is included in the code example in this topic, as is the code used to call generic methods. This method has two type parameters, TInput and TOutput, the latter must be a reference type (class), must have a parameterless constructor (new), and must implement ICollection(TInput) (I collection in C#). This interface constraint ensures that the ICollectionTAdd method can be used to add elements to the TOutput collection created by this method. This method has a formal parameter, TInput, which is an array of t inputs. This method creates a collection of type TOutput and copies the input elements into the collection. Common shared function Factory(Of TInput, _ toutput as {i collection (of t input), Class, New })_(ByVal input()As TInput)As TOutput Dim retval As New TOutput()Dim IC As I collection(Of TInput)= retval For Each t As TInput In input IC。 Add (t) nextprewalend function public static tout factory (tin put [] tarray) where TOutput: class, ICollection, new () {tout put ret = new tout put (); ICollection ic = retforeach (TInput t in tarray) { ic。 Add (t); Ret returns; } Define a dynamic assembly and a dynamic module to contain the type to which the generic method belongs. In this example, the assembly has only one module (named DemoMethodBuilder 1), and the module name is the assembly name plus an extension. In this example, the assembly is saved to disk and executed, so AssemblyBuilderAccessRunandSave is specified. You can use Ildasm.exe(MSIL disassembler) to check DemoMethodBuilder 1.dll and compare it with Microsoft Intermediate Language (MSIL) of the method shown in step 1. Dim asmName as the new assembly name ("demomethodbuilder1") and dimdomain as AppDomain = AppDomain. CurrentDomain Dim demo assembly as AssemblyBuilder = _ domain. defined dynamic assembly(ASM name,_ AssemblyBuilderAccess。 Defines a module that contains code. For an Assembly with a module, the module name is the Assembly Name plus a file extension. dim demo module As module builder = _ demo assembly。 DefineDynamicModule( _ asmName。 Name, _ asmName. Name and name. ".dll ")assembly name ASM name = new assembly name(" demomethodbuilder 1 "); AppDomain domain = AppDomain. CurrentDomainassembly builder demo assembly = domain。 defined dynamic assembly(ASM name,AssemblyBuilderAccess。 RunAndSave); //Define the module containing the code. For a//assembly with a module, the module name is the//assembly name plus a file extension. ModuleBuilder demo module = demo assembly。 DefineDynamicModule(asmName。 Name, asmName. Name+". dll”); Defines the type to which this generic method belongs. The type is not necessarily a generic type. Generic methods can be generic or non-generic types. In this example, the type is a class, it is not a generic type, and its name is DemoType. dim demo type As type builder = demo module。 DefineType( _ "DemoType ",_ TypeAttributes。 public)type builder demo type = demo module。 DefineType("DemoType ",TypeAttributes。 Male); Define generic methods. If the parameter type of a generic method is specified by the generic type parameter of the generic method, use the define method (string, method attributes) method overload to define the method. The generic type parameter of this method has not been defined, so the parameter type of this method cannot be specified in the call to DefineMethod. In this example, the method is named Factory. This method is public and static (Shared in Visual Basic). dim factory As method builder = _ demo type。 DefineMethod("Factory ",_ MethodAttributes。 Public or method properties. static)method builder factory = demo type。 DefineMethod("Factory ",MethodAttributes。 Public | Method Properties. Static); Define generic type parameters of DemoMethod by passing a string array containing parameter names to the methodbuilderdefinegenericparameters method. This makes this method generic. The following code makes Factory a generic method with type parameters TInput and TOutput. To make your code easier to read, you can create variables with these names to hold GenericTypeParameterBuilder objects that represent these two types of parameters. Dim type parameter names()As String = { " tin put "," tout put " } Dim type parameters()As GenericTypeParameterBuilder = _ factory。 DefineGenericParameters(typeParameterNames)Dim tin put As GenericTypeParameterBuilder = type parameters(0) Dim TOutput As GenericTypeParameterBuilder = type parameters( 1)string[]type parameternames = { " tin put "," TOutput " }; GenericTypeParameterBuilder[]type parameters = factory。 DefineGenericParameters(type parameter names); GenericTypeParameterBuilder TInput = type parameters[0]; GenericTypeParameterBuilder TOutput = type parameters[ 1]; You can choose to add special constraints for type parameters. Use the SetGenericParameterAttributes method to add special constraints. In this example, TOutput is constrained to a reference type and has a parameterless constructor. Output. setgenericparametertattributes(_ genericparametertattributes。 ReferenceTypeConstraint or _ GenericParameterAttributes. DefaultConstructorConstrai nt) output. SetGenericParameterAttributes(GenericParameterAttributes。 ReferenceTypeConstraint | genericparameteratattributes。 DefaultConstructorConstraint); You can choose to add class constraints and interface constraints for type parameters. In this example, the type parameter TOutput is constrained to the type that implements the ICollection (belonging to TInput) interface. This ensures that the Add method can be used to add elements. Dim icoll As Type = GetType(I collection(Of))Dim icollOfTInput As Type = icoll。 MakeGenericType(TInput)Dim constraints()As Type = { icollOfTInput } TOutput。 set interface constraints(constraints)Type icoll = Type of(I collection); Type icollOfTInput = icoll. MakeGenericType(TInput); type[]constraints = { icollOfTInput }; Output. SetInterfaceConstraints (constraints); Use the SetParameters method to define the parameters of this method. In this example, the factory method has one parameter, which is an array of t inputs. This type is created by calling the MakeArrayType method on the GenericTypeParameterBuilder that represents TInput. The parameter of SetParameters is an array of Type objects. Dim params() As Type = { TInput。 MakeArrayType()} factory. set parameters(params)Type[]parms = { t input。 make array type()}; Factory. SetParameters; Use the SetReturnType method to define the return type of this method. In this example, an instance of TOutput is returned. Factory. SetReturnType(TOutput) factory. SetReturnType(TOutput); Emit the method body using ILGenerator. For more information, see the attached procedure emitting method body. Important When calling methods of generic types, the type variables of these types are the type parameters of generic methods, and the static getconstructor (type, constructor info), GetMethod(Type, MethodInfo) and GetField(Type, FieldInfo) method overloads of TypeBuilder class must be used to obtain the structural forms of these methods. The accompanying process of emitting the method body demonstrates this point. Complete the type containing the method and save the assembly. The attached procedure calls generic methods demonstrate two ways to call complete methods. Complete the type. Dim dt As Type = demoType。 CreateType () "saves the assembly so that it can be checked with Ildasm.exe. Presentation assembly. Save (asmName. Name and name. ".dll") // completion type. Type dt = demoType. create type(); //Save the assembly so that you can check it with Ildasm.exe. Presentation assembly. Save (asmName. Name+". dll”); Emit the method body to get the 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 is used to save the new TOutput returned by the method, ic is used to save the state when TOutput is converted into I collection (of Tinput) (iCollection in C #), Input is used to save the input array of T input object, and index is used to iterate the array. This method also has two labels defined by the DefineLabel method, one for entering the loop and the other for looping again. This method first loads its parameters with Ldarg_0 operation code, and then stores the parameters in the input local variables with Stloc_S operation code. Dim ilgen As ILGenerator = factory。 GetILGenerator()Dim retVal As local builder = ilgen。 Declare Local(TOutput) Dim ic as LocalBuilder = ilgen. Declare the Local(icollOfTInput) Dim input as LocalBuilder = _ ilgen. DeclareLocal(TInput。 Make MakeArrayType()) Dim index is LocalBuilder = _ ilgen. declare local(GetType(Integer))Dim enter loop As Label = ilgen。 Redefine Label()Dim loop as Label = ilgen. DefineLabel() ilgen。 Issue (operation code. Ldarg_0) ilgen。 Issue (operation code. Stloc_S,input) ILGenerator ilgen = factory。 GetILGenerator(); LocalBuilder retVal = ilgen。 declare local(TOutput); LocalBuilder ic = ilgen。 declare local(icollOfTInput); LocalBuilder input = ilgen. DeclareLocal(TInput。 MakeArrayType()); LocalBuilder index = ilgen。 declare local(type of(int)); Label enterLoop = ilgen. define label(); Label loopAgain = ilgen. define label(); Ilgen. Issue (operation code. ldarg _ 0); Ilgen. Issue (operation code. Stloc_S, input); Use the generic method overload of the ActivatorCreateInstance method to issue code to create an instance of TOutput. Using this overload requires the specified type to have a parameterless constructor, which is why constraints are added to TOutput. Create a constructed generic method by passing TOutput to MakeGenericMethod. After the code calling the method is issued, the code stored in the local variable retVal using Stloc_S and dim create inst as the method info = _ gettype (activator) is issued. Getmethod ("create instance ",type. empty types)Dim createinstofoutput As method info = _ create inst。 makegeneric method(TOutput)ilgen。 Issue (operation code. Call,createinstofoutput)ilgen。 Issue (operation code. Stloc_S,retVal)method info create inst = type of(Activator)。 GetMethod("CreateInstance ",type. empty types); method info createinstofoutput = create inst。 MakeGenericMethod(TOutput); Ilgen. Issue (operation code. Call, createinstofoutput); Ilgen. Issue (operation code. Stloc_S,retVal); Publish the code to forcibly convert the new TOutput object into icollection (belonging to TInput) and store it in the local variable IC. Ilgen. Issue (operation code. Ldloc_S,retVal) ilgen。 Issue (operation code. Box,TOutput) ilgen。 Issue (operation code. Castclass, icollOfTInput) Ilgen. Issue (operation code. Stloc_S,ic) ilgen。 Issue (operation code. Ldloc_S,retVal); Ilgen. Issue (operation code. Box,TOutput); Ilgen. Issue (operation code. Castclass,icollOfTInput); Ilgen. Issue (operation code. Stloc_S,IC); Gets the MethodInfo that represents the ICollectionTAdd method. This method operates on I collection (TInput) (iCollection in C #), so you need to get the Add method specific to this construction type. You cannot use the GetMethod method to get this MethodInfo directly from icollOfTInput because the types constructed with GenericTypeParameterBuilder do not support GetMethod. Instead, you should call the GetMethod on an icoll that contains the generic type definition of the ICollectionT generic interface. Then use the static method of getmethod (type, MethodInfo) to generate the methodinfo of the constructed type. The following code demonstrates this. Dim mAddPrep As MethodInfo = icoll。 get method(" Add ")Dim mAdd As method info = _ type builder。 GetMethod(icollOfTInput,madd prep)method info madd prep = icoll。 get method(" Add "); MethodInfo mAdd = TypeBuilder。 GetMethod(icollOfTInput,madd prep); Issue the code to initialize the index variable (by loading the 32-bit integer 0 and storing it in the variable). Issue code to branch to the tag enterLoop. Because this label is in a loop, it is not marked. The code for this loop will be released in the next step. Initializes the count and enters the loop. Ilgen. Issue (operation code. Ldc_I4_0) ilgen。 Issue (operation code. Stloc_S,index) ilgen。 Issue (operation code. Br_S, enterLoop) // Initialize the count and enter the loop. Ilgen. Issue (operation code. LDC _ I4 _ 0); Ilgen. Issue (operation code. Stloc_S,index); Ilgen. Issue (operation code. Br_S,enter loop); Code that emits a loop. The first step is to mark the top of the loop by calling MarkLabel with the loopAgain tag. Branch statements that use this tag will now branch to this point in the code. The next step is to push the TOutput object converted to ICollection (belonging to TInput) onto the stack. This does not need to be done immediately, but it needs to be done before calling the Add method. Next, the input array is pushed onto the stack, followed by the index variable containing the current index of the array. The Ldelem opcode pops the index and array from the stack, and then pushes the index array elements onto the stack. The stack is now ready to call the ICollectionTAdd method, which pops the collection and new elements from the stack and adds the elements to the collection. Other codes in the loop will increment the index and check whether the loop is completed by testing: push the index and the 32-bit integer 1 onto the stack and add them, leaving the sum on the stack; The sum is stored in the index. Then call MarkLabel to set this point as the entry point of the loop. Load the index again. Push the input array onto the stack, and then issue Ldlen to get its length. The index and length are now on the stack, and Clt is issued to compare them. If the index is less than the length, Brtrue_S returns the start of the loop. Ilgen. MarkLabel (cycle again) ilgen. Issue (operation code. Ldloc_S,ic) ilgen。 Issue (operation code. Ldloc_S,input) ilgen。 Issue (operation code. Ldloc_S,index) ilgen。 Issue (operation code. Ilgen. Issue (operation code. Calvit, Madre) Ilgen. Issue (operation code. Ldloc_S,index) ilgen。 Issue (operation code. Ldc_I4_ 1) Ilgen. Issue (operation code. Add) Ilgen. Issue (operation code. Stloc_S,index) ilgen。 MarkLabel(enterLoop) ilgen。 Issue (operation code. Ldloc_S,index) ilgen。 Issue (operation code. Ldloc_S,input) ilgen。 Issue (operation code. Ilgen. Issue (operation code. Conv_I4) Ilgen. Issue (operation code. Clt) Ilgen. Issue (operation code. Brtrue_S, loopAgain) Ilgen. MarkLabel (loop again); Ilgen. Issue (operation code. Ldloc_S,IC); Ilgen. Issue (operation code. Ldloc_S, input); Ilgen. Issue (operation code. Ldloc_S,index); Ilgen. Issue (operation code. Ldelem,TInput); Ilgen. Issue (operation code. Callvirt,mAdd); Ilgen. Issue (operation code. Ldloc_S,index); Ilgen. Issue (operation code. LDC _ I4 _ 1); Ilgen. Issue (operation code. Add); Ilgen. Issue (operation code. Stloc_S,index); Ilgen. mark label(enter loop); Ilgen. Issue (operation code. Ldloc_S,index); Ilgen. Issue (operation code. Ldloc_S, input); Ilgen. Issue (operation code. ldlen); Ilgen. Issue (operation code. conv _ I4); Ilgen. Issue (operation code. CLT); Ilgen. Issue (operation code. Brtrue_S,loop again); Issue code, push the TOutput object onto the stack, and return from this method. Both local variables retVal and ic contain references to the new output; Ic is only used to access the ICollectionTAdd method. Ilgen. Issue (operation code. Ldloc_S,retVal) ilgen。 Issue (operation code. Ret) Ilgen. Issue (operation code. Ldloc_S,retVal); Ilgen. Issue (operation code. ret); Call the generic method factory to define generic methods. To call a generic method, you must assign a type to the generic type parameter of the generic method. You can do this by using the MakeGenericMethod method. The following code creates a constructed generic method by specifying String for TInput and List for TOutput (String in C#), and displays the string representation of the method. Dim m As MethodInfo = dt。 Get method ("factory") dimbound as method info = m.make generic method (_ gettype (string), GetType(List(Of String))' displays the string representing the binding method. Console. WriteLine method m = dt. get method(" Factory "); method info bound = m . makegeneric method(type of(string),type of(List)); //Displays a string representing the binding method. Console. WriteLine (binding); To call this method as a late binding, use the Invoke method. The following code creates an Object array whose only element is a string array and passes it as a parameter list of generic methods. 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. Dim o As Object = bound。 Invoke(Nothing, newobject () {arr}) dimlist2asList(Of String) = ctype (o, list (of string)) console. WriteLine ("the first element is: {0}", list2(0)) object o = bound. Invoke(null,new object[]{ arr }); List List 2 =(List)o; Console. WriteLine ("The first element is: {0}", list2 [0]); To call this method using a delegate, there must be a delegate that matches the signature of the constructed generic method. A simple way to achieve the above goal is to create a generic delegate. The following code uses the delegate createdelegate (type, methodinfo) method overload to create an instance of the generic delegate d defined in the code example, and then calls the delegate. Delegates perform better than late-bound calls. Dim dType As Type = GetType(D(Of String,List(Of String)))Dim test As D(Of String,List(Of String))test = CType(_[Delegate]。 CreateDelegate(dType,bound),_ D(Of String,List(Of String)))Dim List 3 As List(Of String)= test(arr)Console。 WriteLine ("the first element is: {0}", list3 (0)) typedtype = typeof (d); D test; Test = (D) delegation. CreateDelegate(dType,bound); list list 3 = test(arr); Console. WriteLine ("The first element is: {0}", list 3 [0]); You can also call the emitted method from a program that references a saved assembly. The following code example uses a generic method factory to create a non-generic type DemoType. This method has two generic type parameters: TInput is used to specify the input type, and TOutput is used to specify the output type. TOutput type parameters are limited to implementing ICollection (ICollection(Of TInput) in Visual Basic), reference types and constructors with no parameters. This method has one parameter, which is an array of t inputs. This method returns an instance of TOutput that contains all the elements in the input array. TOutput can be any generic collection type that implements the ICollectionT generic interface. When the code is executed, the dynamic assembly is saved as a demogenic method 1. dll, which can be checked by Ildasm.exe(MSIL disassembler). A good way to explain how to issue code is to write a Visual Basic, C# or Visual C++ program that performs the task you want to issue, and then use the disassembler to check the MSIL generated by the compiler. The code example contains the source code equivalent to the emitted method. The published method is called in the form of binding later, and the generic delegate declared in the code example is also used. Import system Import system. Assemble. Universal import system. Reflex import system. "Reflection.Emit" declares a generic delegate' delegate function D(Of TIn, TOut)(ByVal input() As TIn) As TOut class GenericMethodBuilder' that can be used to execute "completed method". This method demonstrates how to declare a generic method issued by this program in Visual Basic. This method has two type parameters, "TInput" and "TOutput", in which the second parameter must be a reference type (class), must have a parameterless constructor (New), and must "implement ICollection (TInput)". "this interface constraint" ensures ICollection (belonging to TInput). Add can be used to add elements to the TOutput object created by this method. This method has a formal parameter TInput, which is an array of t inputs. The elements of this array are copied to the new output. Common shared function Factory(Of TInput, _ toutput as {i collection (of t input), Class, New })_(ByVal input()As TInput)As TOutput Dim retval As New TOutput()Dim IC As I collection(Of TInput)= retval For Each t As TInput In input IC。 Add (t) Next Retval End Function Public Shared Sub-main ()' The usage syntax of Visual Basic' version of generic method issued by this program is shown below. Note that generic parameters must be explicitly specified because the compiler does not have enough context to infer the type of output. In this case,' toutput' is a general list containing strings. Dim arr() As String = {"a "," b "," c "," d "," e " } Dim List 1 As List(Of String)= _ GenericMethodBuilder。 Factory (string), list (string)) (array) console. WriteLine ("The first element is: {0}", list 1(0))' AssemblyName is required to create a dynamic assembly' object and the current application domain. Dim asmName as the new assembly name ("demomethodbuilder1") and dimdomain as AppDomain = AppDomain. CurrentDomain Dim demo assembly as AssemblyBuilder = _ domain. defined dynamic assembly(ASM name,_ AssemblyBuilderAccess。 Defines a module that contains code. For an Assembly with a module, the module name is the Assembly Name plus a file extension. dim demo module As module builder = _ demo assembly。 DefineDynamicModule( _ asmName。 Name, _ asmName. Name and name. ".dll")' defines a type that contains this method. dim demo type As type builder = demo module。 DefineType( _ "DemoType ",_ TypeAttributes。 Public)' Define a shared public method with standard calling convention. Do not specify parameter types or "return types" because the type parameters will be used for these types, and the type parameters have not been "defined" for dimfactory as method builder = _ demotype. DefineMethod("Factory ",_ MethodAttributes。 Public or method properties. Define generic type parameters for this method to make it a "generic method". To make the code easier to read, each type of parameter is copied into a variable with the same name. Dim type parameter names()As String = { " tin put "," tout put " } Dim type parameters()As GenericTypeParameterBuilder = _ factory。 Define genericparameters (typeparameters) dimtin put as generictypeparameterbuilder = typeparameters (0) dimtout put as generictypeparameterbuilder = typeparameters (1) "Add special constraints." The type parameter TOutput is constrained to a reference type and has a parameterless constructor. This ensures that factory methods can create collection types. Output. SetGenericParameterAttribute s(_ GenericParameterAttributes。 ReferenceTypeConstraint or _ GenericParameterAttributes. Add interface and base type constraints. The type parameter TOutput is constrained to "the type that implements the ICollection(Of T) interface to ensure that" they have an Add method that can be used to add elements. " To create a constraint, first bind the "type parameter TInput to the ICollection(Of T) interface" with MakeGenericType, return the iCollection (of t) type, and then pass the newly created type to the "SetInterfaceConstraints" method. Constraints must be passed as arrays, even if there is only one interface. Dim icoll As Type = GetType(I collection(Of))Dim icollOfTInput As Type = icoll。 MakeGenericType(TInput)Dim constraints()As Type = { icollOfTInput } TOutput。 SetInterfaceConstraints (constraints)' sets the parameter type of the method. This method takes one parameter, that is, an array of type TInput. Dim parameter ()