如何:定义具有反射发出的泛型方法

更新:2007 年 11 月

第一个过程演示如何使用两个类型参数创建一个简单的泛型方法,以及如何对类型参数应用类约束、接口约束和特殊约束。

第二个过程演示如何发出方法体,以及如何使用泛型方法的类型参数创建泛型类型的实例以及调用其方法。

第三个过程演示如何调用泛型方法。

重要说明:

不能仅因为某个方法属于泛型类型且使用泛型类型的类型参数,就称该方法是泛型方法。只有当方法具有自己的类型参数列表时,才能称其为泛型方法。泛型方法可以出现在非泛型类型上,在本例中就是如此。有关泛型类型上的非泛型方法的示例,请参见如何:定义具有反射发出的泛型类型

定义泛型方法

  1. 首先,如果使用高级语言编写,则查看泛型方法的显示方式会十分有用。下面的代码包含在此主题的代码示例中,用于调用泛型方法的代码同样也包含在其中。该方法具有两个类型参数 TInput 和 TOutput,后者必须为引用类型 (class),必须具有无参数构造函数 (new),并且必须实现 ICollection(Of TInput)(在 C# 中为 ICollection<TInput>)。此接口约束确保 ICollection<T>.Add 方法可用于将元素添加到该方法创建的 TOutput 集合中。该方法具有一个形参 input,这是一个 TInput 的数组。该方法创建类型 TOutput 的集合,并将 input 的元素复制到该集合中。

    Public Shared Function Factory(Of TInput, _
        TOutput As {ICollection(Of TInput), Class, New}) _
        (ByVal input() As TInput) As TOutput
    
        Dim retval As New TOutput()
        Dim ic As ICollection(Of TInput) = retval
    
        For Each t As TInput In input
            ic.Add(t)
        Next
    
        Return retval
    End Function 
    
    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. 定义一个动态程序集和一个动态模块,以包含该泛型方法所属的类型。在本例中,该程序集仅拥有一个模块(名为 DemoMethodBuilder1),模块名称即为程序集名称加上一个扩展名。在此示例中,将程序集保存到磁盘中并执行,因此指定了 AssemblyBuilderAccess.RunAndSave。您可以使用 MSIL 反汇编程序 (Ildasm.exe) 检查 DemoMethodBuilder1.dll,并将其与步骤 1 中显示的方法的 Microsoft 中间语言 (MSIL) 进行比较。

    Dim asmName As New AssemblyName("DemoMethodBuilder1")
    Dim domain As AppDomain = AppDomain.CurrentDomain
    Dim demoAssembly As AssemblyBuilder = _
        domain.DefineDynamicAssembly(asmName, _
            AssemblyBuilderAccess.RunAndSave)
    
    ' Define the module that contains the code. For an 
    ' assembly with one module, the module name is the 
    ' assembly name plus a file extension.
    Dim demoModule As ModuleBuilder = _
        demoAssembly.DefineDynamicModule( _
            asmName.Name, _
            asmName.Name & ".dll")
    
    AssemblyName asmName = new AssemblyName("DemoMethodBuilder1");
    AppDomain domain = AppDomain.CurrentDomain;
    AssemblyBuilder demoAssembly = 
        domain.DefineDynamicAssembly(asmName, 
            AssemblyBuilderAccess.RunAndSave);
    
    // 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, 
            asmName.Name+".dll");
    
  3. 定义该泛型方法所属的类型。该类型不一定为泛型类型。泛型方法可以属于泛型类型,也可以属于非泛型类型。在此示例中,该类型为一个类,它不是泛型类型,名称为 DemoType。

    Dim demoType As TypeBuilder = demoModule.DefineType( _
        "DemoType", _
        TypeAttributes.Public) 
    
    TypeBuilder demoType = 
        demoModule.DefineType("DemoType", TypeAttributes.Public);
    
  4. 定义泛型方法。如果泛型方法的形参的类型由该泛型方法的泛型类型参数指定,请使用 DefineMethod(String, MethodAttributes) 方法重载定义该方法。该方法的泛型类型参数尚未定义,因此不能在对 DefineMethod 的调用中指定该方法的形参的类型。在此示例中,该方法名为 Factory。该方法是公共方法,并且是 static(在 Visual Basic 中为 Shared)的。

    Dim factory As MethodBuilder = _
        demoType.DefineMethod("Factory", _
            MethodAttributes.Public Or MethodAttributes.Static)
    
    MethodBuilder factory = 
        demoType.DefineMethod("Factory", 
            MethodAttributes.Public | MethodAttributes.Static);
    
  5. 通过将包含参数名称的字符串数组传递给 MethodBuilder.DefineGenericParameters 方法来定义 DemoMethod 的泛型类型参数。这使得该方法成为泛型方法。下面的代码使得 Factory 成为具有类型参数 TInput 和 TOutput 的泛型方法。若要使代码更易于阅读,可创建具有这些名称的变量,以保存表示这两个类型参数的 GenericTypeParameterBuilder 对象。

    Dim typeParameterNames() As String = {"TInput", "TOutput"}
    Dim typeParameters() As GenericTypeParameterBuilder = _
        factory.DefineGenericParameters(typeParameterNames)
    
    Dim TInput As GenericTypeParameterBuilder = typeParameters(0)
    Dim TOutput As GenericTypeParameterBuilder = typeParameters(1)
    
    string[] typeParameterNames = {"TInput", "TOutput"};
    GenericTypeParameterBuilder[] typeParameters = 
        factory.DefineGenericParameters(typeParameterNames);
    
    GenericTypeParameterBuilder TInput = typeParameters[0];
    GenericTypeParameterBuilder TOutput = typeParameters[1];
    
  6. 可以选择为类型参数添加特殊约束。特殊约束是通过使用 SetGenericParameterAttributes 方法添加的。在此示例中,TOutput 被约束为引用类型,并且具有无参数构造函数。

    TOutput.SetGenericParameterAttributes( _
        GenericParameterAttributes.ReferenceTypeConstraint Or _
        GenericParameterAttributes.DefaultConstructorConstraint)
    
    TOutput.SetGenericParameterAttributes(
        GenericParameterAttributes.ReferenceTypeConstraint | 
        GenericParameterAttributes.DefaultConstructorConstraint);
    
  7. 可以选择为类型参数添加类约束和接口约束。在此示例中,类型参数 TOutput 被约束为实现 ICollection(Of TInput)(在 C# 中为 ICollection<TInput>)接口的类型。这确保 Add 方法可用于添加元素。

    Dim icoll As Type = GetType(ICollection(Of ))
    Dim icollOfTInput As Type = icoll.MakeGenericType(TInput)
    Dim constraints() As Type = { icollOfTInput }
    TOutput.SetInterfaceConstraints(constraints)
    
    Type icoll = typeof(ICollection<>);
    Type icollOfTInput = icoll.MakeGenericType(TInput);
    Type[] constraints = {icollOfTInput};
    TOutput.SetInterfaceConstraints(constraints);
    
  8. 使用 SetParameters 方法定义该方法的形参。在此示例中,Factory 方法具有一个参数,即 TInput 的数组。此类型是通过对表示 TInput 的 GenericTypeParameterBuilder 调用 MakeArrayType 方法创建的。SetParameters 的参数是 Type 对象的数组。

    Dim params() As Type = { TInput.MakeArrayType() }
    factory.SetParameters(params)
    
    Type[] parms = {TInput.MakeArrayType()};
    factory.SetParameters(parms);
    
  9. 使用 SetReturnType 方法定义该方法的返回类型。在此示例中,返回 TOutput 的一个实例。

    factory.SetReturnType(TOutput)
    
    factory.SetReturnType(TOutput);
    
  10. 使用 ILGenerator 发出该方法体。有关详细信息,请参见附带的过程发出方法体。

    重要说明:

    发出对泛型类型的方法的调用,而且这些类型的类型变量为该泛型方法的类型参数时,您必须使用 TypeBuilder 类的 staticGetConstructor(Type, ConstructorInfo)GetMethod(Type, MethodInfo)GetField(Type, FieldInfo) 方法重载,以获取这些方法的构造形式。发出方法体的附带过程对此进行了演示。

  11. 完成包含该方法的类型并保存程序集。附带的过程调用泛型方法演示了调用完整方法的两种方式。

    ' Complete the type.
    Dim dt As Type = demoType.CreateType()
    ' Save the assembly, so it can be examined with Ildasm.exe.
    demoAssembly.Save(asmName.Name & ".dll")
    
    // Complete the type.
    Type dt = demoType.CreateType();
    // Save the assembly, so it can be examined with Ildasm.exe.
    demoAssembly.Save(asmName.Name+".dll");
    

发出方法体

  1. 获取代码生成器并声明局部变量和标签。DeclareLocal 方法用于声明局部变量。Factory 方法具有四个局部变量:retVal 用于保存该方法返回的新 TOutput,ic 用于在 TOutput 转换为 ICollection(Of TInput)(在 C# 中为 ICollection<TInput>)时将其保存,input 用于保存 TInput 对象的输入数组,而 index 用于循环访问该数组。该方法还具有使用 DefineLabel 方法定义的两个标签,一个用于进入循环 (enterLoop),另一个用于循环的顶部 (loopAgain)。

    该方法首先使用 Ldarg_0 操作码加载其参数,然后使用 Stloc_S 操作码将参数存储在局部变量 input 中。

    Dim ilgen As ILGenerator = factory.GetILGenerator()
    
    Dim retVal As LocalBuilder = ilgen.DeclareLocal(TOutput)
    Dim ic As LocalBuilder = ilgen.DeclareLocal(icollOfTInput)
    Dim input As LocalBuilder = _
        ilgen.DeclareLocal(TInput.MakeArrayType())
    Dim index As LocalBuilder = _
        ilgen.DeclareLocal(GetType(Integer))
    
    Dim enterLoop As Label = ilgen.DefineLabel()
    Dim loopAgain As Label = ilgen.DefineLabel()
    
    ilgen.Emit(OpCodes.Ldarg_0)
    ilgen.Emit(OpCodes.Stloc_S, input)
    
    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. 使用 Activator.CreateInstance 方法的泛型方法重载发出代码以创建 TOutput 的实例。使用此重载需要指定类型具有无参数构造函数,这也是向 TOutput 添加该约束的原因。通过将 TOutput 传递到 MakeGenericMethod 创建构造的泛型方法。发出代码以调用该方法后,发出代码以使用 Stloc_S 将其存储在局部变量 retVal 中

    Dim createInst As MethodInfo = _
        GetType(Activator).GetMethod("CreateInstance", Type.EmptyTypes)
    Dim createInstOfTOutput As MethodInfo = _
        createInst.MakeGenericMethod(TOutput)
    
    ilgen.Emit(OpCodes.Call, createInstOfTOutput)
    ilgen.Emit(OpCodes.Stloc_S, retVal)
    
    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. 发出代码以将新的 TOutput 对象强制转换为 ICollection(Of TInput),并将其存储在局部变量 ic 中。

    ilgen.Emit(OpCodes.Ldloc_S, retVal)
    ilgen.Emit(OpCodes.Box, icollOfTInput)
    ilgen.Emit(OpCodes.Castclass, icollOfTInput)
    ilgen.Emit(OpCodes.Stloc_S, ic)
    
    ilgen.Emit(OpCodes.Ldloc_S, retVal);
    ilgen.Emit(OpCodes.Box, icollOfTInput);
    ilgen.Emit(OpCodes.Castclass, icollOfTInput);
    ilgen.Emit(OpCodes.Stloc_S, ic);
    
  4. 获取表示 ICollection<T>.Add 方法的 MethodInfo。此方法对 ICollection(Of TInput)(在 C# 中为 ICollection<TInput>)进行操作,因此有必要获取特定于该构造类型的 Add 方法。不能使用 GetMethod 方法直接从 icollOfTInput 获取此 MethodInfo,因为 GetMethod 在已使用 GenericTypeParameterBuilder 构造的类型上不受支持。而应该对包含 ICollection<T> 泛型接口的泛型类型定义的 icoll 调用 MethodInfo。然后使用 GetMethod(Type, MethodInfo)static 方法生成构造类型的 MethodInfo。下面的代码对此进行了演示。

    Dim mAddPrep As MethodInfo = icoll.GetMethod("Add")
    Dim mAdd As MethodInfo = _
        TypeBuilder.GetMethod(icollOfTInput, mAddPrep)
    
    MethodInfo mAddPrep = icoll.GetMethod("Add");
    MethodInfo mAdd = TypeBuilder.GetMethod(icollOfTInput, mAddPrep);
    
  5. 发出代码以初始化 index 变量(通过加载 32 位整数 0 并将其存储在变量中)。发出代码以分支到标签 enterLoop。因为此标签位于循环内,所以尚未进行标记。该循环的代码在下一步中发出。

    ' 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)
    
    // 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. 发出该循环的代码。第一步是标记循环的顶部,方法是用 loopAgain 标签调用 MarkLabel。使用该标签的分支语句现在将分支到代码中的这个点。下一步是将强制转换为 ICollection(Of TInput) 的 TOutput 对象推入堆栈。这不需要立即进行,但需要在调用 Add 方法之前完成。接下来,输入数组被推入堆栈,然后是包含该数组的当前索引的 index 变量。Ldelem 操作码从堆栈中弹出该索引和数组,然后将索引数组元素推入堆栈。堆栈现在准备好调用 ICollection<T>.Add 方法,该方法从堆栈中弹出集合和新的元素,并将该元素添加到该集合中。

    循环中的其他代码会使索引递增,并通过测试查看循环是否完成:将索引和 32 位整数 1 推入堆栈并相加,在堆栈上保留总和;总和存储在 index 中。然后调用 MarkLabel 将此点设置为循环的入口点。再次加载该索引。将输入数组推入堆栈,然后发出 Ldlen 以获取其长度。索引和长度现在位于堆栈中,并发出 Clt 对二者进行比较。如果索引小于长度,Brtrue_S 会重新返回到循环的起始点。

    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.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. 发出代码以将 TOutput 对象推入堆栈,并从该方法返回。局部变量 retVal 和 ic 均包含对新的 TOutput 的引用;ic 仅用于访问 ICollection<T>.Add 方法。

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

调用泛型方法

  1. Factory 为泛型方法定义。若要调用泛型方法,您必须为泛型方法的泛型类型参数分配类型。使用 MakeGenericMethod 方法可完成此操作。下面的代码通过为 TInput 指定 String 并为 TOutput 指定 List(Of String)(在 C# 中为 List<string>)创建一个构造的泛型方法,并显示该方法的字符串表示形式。

    Dim m As MethodInfo = dt.GetMethod("Factory")
    Dim bound As MethodInfo = m.MakeGenericMethod( _
        GetType(String), GetType(List(Of String)))
    
    ' Display a string representing the bound method.
    Console.WriteLine(bound)
    
    MethodInfo m = dt.GetMethod("Factory");
    MethodInfo bound = 
        m.MakeGenericMethod(typeof(string), typeof(List<string>));
    
    // Display a string representing the bound method.
    Console.WriteLine(bound);
    
  2. 若要以后期绑定的形式调用该方法,请使用 Invoke 方法。下面的代码创建 Object(包含的唯一元素为字符串数组)的数组,并将其作为泛型方法的参数列表传递。Invoke 的第一个参数为空引用,因为该方法为 static 的。返回值被强制转换为 List(Of String),并显示它的第一个元素。

    Dim o As Object = bound.Invoke(Nothing, New Object() { arr })
    Dim list2 As List(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<string> list2 = (List<string>) o;
    
    Console.WriteLine("The first element is: {0}", list2[0]);
    
  3. 若要使用委托调用该方法,您必须具有与该构造的泛型方法的签名匹配的委托。实现上述目标的一种简便方式是创建一个泛型委托。下面的代码使用 Delegate.CreateDelegate(Type, MethodInfo) 方法重载,创建在代码示例中定义的泛型委托 D 的一个实例,然后调用该委托。委托的性能比后期绑定调用好。

    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 list3 As List(Of String) = test(arr)
    Console.WriteLine("The first element is: {0}", list3(0))
    
    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);
    Console.WriteLine("The first element is: {0}", list3[0]);
    
  4. 发出的方法也可以从引用保存的程序集的程序调用。

示例

下面的代码示例使用泛型方法 Factory 创建一个非泛型类型 DemoType。此方法具有两个泛型类型参数:TInput 用于指定输入类型,TOutput 用于指定输出类型。TOutput 类型参数被限制为实现 ICollection<TInput>(在 Visual Basic 中为 ICollection(Of TInput)),限制为引用类型,并且限制为具有无参数构造函数。

该方法具有一个形参,这是一个 TInput 的数组。该方法返回包含输入数组的所有元素的 TOutput 的一个实例。TOutput 可以是实现 ICollection<T> 泛型接口的任何泛型集合类型。

代码执行时,该动态程序集会保存为 DemoGenericMethod1.dll,并可使用 MSIL 反汇编程序 (Ildasm.exe) 对其进行检查。

说明:

学习如何发出代码的一种好方法是编写执行您要发出的任务的 Visual Basic、C# 或 Visual C++ 程序,然后使用反汇编程序检查编译器生成的 MSIL。

该代码示例包含等效于发出的方法的源代码。发出的方法是以后期绑定的形式进行调用的,同时也使用了在该代码示例中声明的泛型委托。

Imports System
Imports System.Collections.Generic
Imports System.Reflection
Imports System.Reflection.Emit

' Declare a generic delegate that can be used to execute the 
' finished method.
'
Delegate Function D(Of TIn, TOut)(ByVal input() As TIn) As TOut

Class GenericMethodBuilder

    ' This method shows how to declare, in Visual Basic, 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(Of TInput). This interface constraint
    ' ensures that ICollection(Of 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 Shared Function Factory(Of TInput, _
        TOutput As {ICollection(Of TInput), Class, New}) _
        (ByVal input() As TInput) As TOutput

        Dim retval As New TOutput()
        Dim ic As ICollection(Of TInput) = retval

        For Each t As TInput In input
            ic.Add(t)
        Next

        Return retval
    End Function 


    Public Shared Sub Main()
        ' The following shows the usage syntax of the Visual Basic
        ' 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.
        ' 
        Dim arr() As String = {"a", "b", "c", "d", "e"}
        Dim list1 As List(Of String) = _
            GenericMethodBuilder.Factory(Of String, List(Of String))(arr)
        Console.WriteLine("The first element is: {0}", list1(0))


        ' Creating a dynamic assembly requires an AssemblyName
        ' object, and the current application domain.
        '
        Dim asmName As New AssemblyName("DemoMethodBuilder1")
        Dim domain As AppDomain = AppDomain.CurrentDomain
        Dim demoAssembly As AssemblyBuilder = _
            domain.DefineDynamicAssembly(asmName, _
                AssemblyBuilderAccess.RunAndSave)

        ' Define the module that contains the code. For an 
        ' assembly with one module, the module name is the 
        ' assembly name plus a file extension.
        Dim demoModule As ModuleBuilder = _
            demoAssembly.DefineDynamicModule( _
                asmName.Name, _
                asmName.Name & ".dll")

        ' Define a type to contain the method.
        Dim demoType As TypeBuilder = demoModule.DefineType( _
            "DemoType", _
            TypeAttributes.Public) 

        ' Define a Shared, Public 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.
        '
        Dim factory As MethodBuilder = _
            demoType.DefineMethod("Factory", _
                MethodAttributes.Public Or 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.
        '
        Dim typeParameterNames() As String = {"TInput", "TOutput"}
        Dim typeParameters() As GenericTypeParameterBuilder = _
            factory.DefineGenericParameters(typeParameterNames)

        Dim TInput As GenericTypeParameterBuilder = typeParameters(0)
        Dim TOutput As GenericTypeParameterBuilder = 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 Or _
            GenericParameterAttributes.DefaultConstructorConstraint)

        ' Add interface and base type constraints.
        ' The type parameter TOutput is constrained to types that
        ' implement the ICollection(Of 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(Of T) interface,
        ' returning the type ICollection(Of 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.
        '
        Dim icoll As Type = GetType(ICollection(Of ))
        Dim icollOfTInput As Type = icoll.MakeGenericType(TInput)
        Dim constraints() As Type = { icollOfTInput }
        TOutput.SetInterfaceConstraints(constraints)

        ' Set parameter types for the method. The method takes
        ' one parameter, an array of type TInput.
        Dim params() As Type = { TInput.MakeArrayType() }
        factory.SetParameters(params)

        ' 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.
        '
        Dim ilgen As ILGenerator = factory.GetILGenerator()

        Dim retVal As LocalBuilder = ilgen.DeclareLocal(TOutput)
        Dim ic As LocalBuilder = ilgen.DeclareLocal(icollOfTInput)
        Dim input As LocalBuilder = _
            ilgen.DeclareLocal(TInput.MakeArrayType())
        Dim index As LocalBuilder = _
            ilgen.DeclareLocal(GetType(Integer))

        Dim enterLoop As Label = ilgen.DefineLabel()
        Dim loopAgain As Label = 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. 
        '
        Dim createInst As MethodInfo = _
            GetType(Activator).GetMethod("CreateInstance", Type.EmptyTypes)
        Dim createInstOfTOutput As MethodInfo = _
            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(Of TInput), and save it.
        ilgen.Emit(OpCodes.Ldloc_S, retVal)
        ilgen.Emit(OpCodes.Box, icollOfTInput)
        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(Of TInput).Add, it is necessary to first 
        ' get the Add method for the generic type defintion,
        ' ICollection(Of 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.
        '
        Dim mAddPrep As MethodInfo = icoll.GetMethod("Add")
        Dim mAdd As MethodInfo = _
            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.
        Dim dt As Type = demoType.CreateType()
        ' Save the assembly, so it can be examined with Ildasm.exe.
        demoAssembly.Save(asmName.Name & ".dll")

        ' 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(Of String)
        ' for TOutput. 
        '
        Dim m As MethodInfo = dt.GetMethod("Factory")
        Dim bound As MethodInfo = m.MakeGenericMethod( _
            GetType(String), GetType(List(Of String)))

        ' Display a string representing the bound method.
        Console.WriteLine(bound)


        ' 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'.
        '
        Dim o As Object = bound.Invoke(Nothing, New Object() { arr })
        Dim list2 As List(Of String) = CType(o, List(Of String))

        Console.WriteLine("The first element is: {0}", list2(0))


        ' 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.
        '
        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 list3 As List(Of String) = test(arr)
        Console.WriteLine("The first element is: {0}", list3(0))

    End Sub  
End Class 

' 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
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 GenericMethodBuilder
{
    // This method shows how to declare, in Visual Basic, 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 Main()
    {
        // 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 = 
            GenericMethodBuilder.Factory<string, List <string>>(arr);
        Console.WriteLine("The first element is: {0}", 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.RunAndSave);

        // 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, 
                asmName.Name+".dll");

        // 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, icollOfTInput);
        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();
        // Save the assembly, so it can be examined with Ildasm.exe.
        demoAssembly.Save(asmName.Name+".dll");

        // 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.
        Console.WriteLine(bound);


        // 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;

        Console.WriteLine("The first element is: {0}", list2[0]);


        // 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);
        Console.WriteLine("The first element is: {0}", list3[0]);
    }
}

/* 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
 */

编译代码

  • 代码包含编译所需的 C# using 语句(在 Visual Basic 中为 Imports)。

  • 不需要其他程序集引用。

  • 使用 csc.exe、vbc.exe 或 cl.exe 在命令行编译代码。若要在 Visual Studio 中编译代码,请将其置于控制台应用程序项目模板中。

请参见

任务

如何:定义具有反射发出的泛型类型

参考

MethodBuilder