Compartilhar via


Como: Definir um método genérico com reflexão Emit

O primeiro procedimento mostra como criar um método genérico simples com dois parâmetros de tipo e como aplicar restrições de classe, restrições de interface e restrições especiais aos parâmetros de tipo.

O segundo procedimento mostra como emitir o corpo do método e como usar os parâmetros de tipo de método genérico para criar instâncias de tipos genéricos e para telefonar seus métodos.

O terceiro procedimento mostra como invocar o método genérico.

Observação importante:

Um método não é genérico apenas porque ele pertence a um tipo genérico e usa os parâmetros de tipo desse tipo.Um método é genérico somente se ele tiver sua própria lista de parâmetro de tipo.Um método genérico pode aparecer em um tipo não genérico, sistema autônomo no exemplo.Para obter um exemplo de um método não genérico em um tipo genérico, consulte Como: Definir um tipo genérico com reflexão Emit.

Para definir um método genérico

  1. Antes de começar, é útil examinar como o método genérico aparece quando escritos com uma linguagem de alto nível.O código seguinte está incluído no código de exemplo para este tópico, juntamente com código para chamar o método genérico.O método tem dois parâmetros de tipo, TInput e TOutput, o segundo dos quais deve ser uma referência digite ()class), deve ter um construtor sem parâmetros (new) e deve implementar ICollection(Of TInput) (ICollection<TInput> em translation from VPE for Csharp). Essa restrição de interface garante que o ICollection<T>.Add método pode ser usado para adicionar elementos para o TOutput coleção que o método cria. O método tem um parâmetro formal, input, que é uma matriz de TInput. O método cria uma coleção de tipo TOutput e copia os elementos de input a coleção.

    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. Defina um assembly dinâmico e módulo dinâmico para conter o tipo de método genérico pertence.Nesse caso, o assembly tem apenas um módulo, chamado DemoMethodBuilder1, e o nome do módulo é o mesmo sistema autônomo o nome do assembly mais uma extensão. Neste exemplo, o assembly é salvo em disco e também executado caso AssemblyBuilderAccess.RunAndSave foi especificado. Você pode usar o Desassemblador do MSIL (ILDASM.exe) para examinar DemoMethodBuilder1.dll e compará-lo para a Microsoft intermediate linguagem (MSIL) para o método mostrado na etapa 1.

    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. Defina o tipo de que método genérico pertence.O tipo não precisa ser genérico.Um método genérico pode pertencer a um tipo genérico ou não genérico.Neste exemplo, o tipo é uma classe, não é genérico e é denominado DemoType.

    Dim demoType As TypeBuilder = demoModule.DefineType( _
        "DemoType", _
        TypeAttributes.Public) 
    
    TypeBuilder demoType = 
        demoModule.DefineType("DemoType", TypeAttributes.Public);
    
  4. Defina o método genérico.Se os tipos de parâmetros formais de um método genérico forem especificados por parâmetros de tipo genérico do método genérico, use o DefineMethod(String, MethodAttributes) sobrecarga de método para definir o método. Os parâmetros de tipo genérico do método são ainda não definidos, para que você não pode especificar os tipos de parâmetros formal do método na telefonar para DefineMethod. Neste exemplo, o método é chamado Factory. O método é público e static (Shared no Visual Basic).

    Dim factory As MethodBuilder = _
        demoType.DefineMethod("Factory", _
            MethodAttributes.Public Or MethodAttributes.Static)
    
    MethodBuilder factory = 
        demoType.DefineMethod("Factory", 
            MethodAttributes.Public | MethodAttributes.Static);
    
  5. Defina os parâmetros de tipo genérico de DemoMethod passando uma matriz de seqüências de caracteres que contém os nomes dos parâmetros para o MethodBuilder.DefineGenericParameters método. Isso torna o método de um método genérico.O código a seguir torna Factory um método genérico com parâmetros de tipo TInput e TOutput. Para tornar o código mais fácil de ler, variáveis com esses nomes são criadas para conter o GenericTypeParameterBuilder objetos que representam dois tipos dos parâmetros.

    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. Opcionalmente, adicione restrições especiais aos parâmetros de tipo.Restrições especiais são adicionadas usando o SetGenericParameterAttributes método. Neste exemplo, TOutput é restrito para ser um tipo de referência e tem um construtor sem parâmetros.

    TOutput.SetGenericParameterAttributes( _
        GenericParameterAttributes.ReferenceTypeConstraint Or _
        GenericParameterAttributes.DefaultConstructorConstraint)
    
    TOutput.SetGenericParameterAttributes(
        GenericParameterAttributes.ReferenceTypeConstraint | 
        GenericParameterAttributes.DefaultConstructorConstraint);
    
  7. Opcionalmente, adicione as restrições de interface e classe os parâmetros de tipo.Neste exemplo, digite o parâmetro TOutput restrita a tipos que implementam a ICollection(Of TInput) (ICollection<TInput> em translation from VPE for Csharp) da interface. Isso garante que o Add método pode ser usado para adicionar elementos.

    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. Defina os parâmetros do método, usando o formaisSetParameters método. Neste exemplo, a Factory método tem um parâmetro, uma matriz de TInput. Esse tipo é criado chamando o MakeArrayType método na GenericTypeParameterBuilder que representa o TInput. O argumento de SetParameters é uma matriz de Type objetos.

    Dim params() As Type = { TInput.MakeArrayType() }
    factory.SetParameters(params)
    
    Type[] parms = {TInput.MakeArrayType()};
    factory.SetParameters(parms);
    
  9. Definir o tipo de retorno para o método, usando o SetReturnType método. Neste exemplo, uma instância de TOutput será retornado.

    factory.SetReturnType(TOutput)
    
    factory.SetReturnType(TOutput);
    
  10. Emitir o corpo de método, usando ILGenerator. Para obter detalhes, consulte o procedimento que o acompanha Para o corpo do método de emissão.

    Observação importante:

    Quando você emite chamadas para métodos de tipos genéricos e os argumentos de tipo esses tipo s são o tipo de parâmetros do método genérico, você deve usar o staticGetConstructor(Type, ConstructorInfo), GetMethod(Type, MethodInfo), e GetField(Type, FieldInfo) método sobrecargas da TypeBuilder classe obter formulários construídos dos métodos. O procedimento que o acompanha para emitir o corpo do método demonstra isso.

  11. Concluir o tipo que contém o método e salvar o assembly.O procedimento que o acompanha Para chamar o método genérico mostra duas maneiras para chamar o método completo.

    ' 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");
    

Para emitir o corpo do método

  1. Obtenha um gerador de código e declarar variáveis locais e rótulos.The DeclareLocal método é usado para declarar variáveis locais. The Factory método tem quatro variáveis locais: retVal para manter o novo TOutput que é retornado pelo método, ic para manter o TOutput Quando ele é um conversão para ICollection(Of TInput) (ICollection<TInput> em translation from VPE for Csharp) input para manter a matriz de entrada de TInput objetos, e index para iterar por meio do array. O método também possui dois rótulos, um para entrar no loop (enterLoop) e outro para a parte superior do (looploopAgain), definido usando o DefineLabel método.

    A primeira coisa que o método faz é carregar seu argumento usando Ldarg_0 código de operação e armazená-lo na variável local input usando o Stloc_S código de operação.

    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. Emita código para criar uma instância de TOutput, usando a sobrecarga de método genérico da Activator.CreateInstance método. Essa sobrecarga de uso requer o tipo especificado ter um construtor sem parâmetros, que é o motivo para adicionar essa restrição a TOutput. Crie o método genérico construído passando TOutput para MakeGenericMethod. Depois de emitir código telefonar o método emitir código armazená-lo em local variável retVal usando o Stloc_S

    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. Emita código para converter o novo TOutput objeto para ICollection(Of TInput) e armazená-lo na variável local 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. Obtenha um MethodInfo que representa o ICollection<T>.Add método. O método está agindo em um ICollection(Of TInput) (ICollection<TInput> em translation from VPE for Csharp), portanto, é necessário obter o Add método específico para que construído tipo. Não é possível usar o GetMethod método para que isso MethodInfo diretamente do icollOfTInput, pois GetMethod não é suportado em um tipo que tenha sido construído com uma GenericTypeParameterBuilder. Em vez disso, chame MethodInfo em icoll, que contém a definição de tipo genérico para o ICollection<T> interface genérica. Then use the GetMethod(Type, MethodInfo)static method to produce the MethodInfo for the constructed type.O código a seguir demonstra isso.

    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. Emita código para inicializar o index variável, carregar um inteiro de 32 bit 0 e armazenando-o na variável. Emita código para a ramificar para o rótulo enterLoop. Esse rótulo foi não ainda marcado, porque ele está dentro do loop.Código para o loop é emitido na próxima etapa.

    ' 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. Emita código para loop.A primeira etapa é marcar a parte superior do loop, chamando MarkLabel com o loopAgain rótulo. Demonstrativos de ramificar que usam o rótulo agora serão ramificar este ponto no código.A próxima etapa é empurrar o TOutput objeto, converter para ICollection(Of TInput), na pilha. Não é necessária imediatamente, mas precisa estar na posição para chamar o Add método. Em seguida a matriz de entrada é enviada na pilha, em seguida, a index variável que contém o índice corrente para a matriz. The Ldelem Opcode aparece o índice e a matriz na pilha e envia o elemento de matriz indexada na pilha. A pilha agora está pronta para a telefonar para o ICollection<T>.Add método, que aciona a coleta e o novo elemento na pilha e adiciona o elemento à coleção.

    O restante do código no loop incrementa o índice e testa para ver se o loop foi concluído: O índice e um número inteiro de 32 bit 1 são enviados na pilha e adicionado, deixando a soma na pilha, a soma é armazenada em index. MarkLabel é chamado para conjunto esse ponto sistema autônomo o ponto de entrada para o loop. O índice é carregado novamente.A matriz de entrada é enviada na pilha e Ldlen é emitida para obter seu comprimento. O índice e o comprimento agora estão na pilha e Clt é emitida para compará-las. Se o índice for menor que o comprimento, Brtrue_S ramificações de volta para o início do 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)
    
    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. Emita código para estender o TOutput objeto na pilha e retornar do método. As variáveis locais retVal e ic ambos contêm referências para o novo TOutput; ic é usado somente para acesso a ICollection<T>.Add método.

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

Para chamar o método genérico

  1. Factory é uma definição de método genérico. Para invocá-lo, você deve atribuir tipos de seus parâmetros de tipo genérico.Use o MakeGenericMethod método para fazer isso. O código a seguir cria um método genérico construído especificando String para TInput e List(Of String) (List<string> em translation from VPE for Csharp) para TOutpute exibe uma representação de seqüência de caracteres do método.

    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. Para chamar o método atrasado-limite, use o Invoke método. O código a seguir cria uma matriz de Object, que sistema autônomo seu único elemento contém uma matriz de seqüências de caracteres e passa-lo sistema autônomo a lista de argumentos de método genérico. O primeiro parâmetro de Invoke é uma referência nula porque é o método static. O valor retornado é um conversão para List(Of String), e o primeiro elemento é exibido.

    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. Para chamar o método usando um representante, você deve ter um delegado que corresponde à assinatura do método genérico construído.Uma maneira fácil de fazer isso é criar um delegado genérico.O código a seguir cria uma instância do delegado genérico D definido no código de exemplo, usando o Delegate.CreateDelegate(Type, MethodInfo) sobrecarga de método e invoca o delegado. Delegados desempenho melhor que atrasado-limite chamadas.

    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. O método emitido também pode ser chamado de um programa que se refere ao conjunto salvo.

Exemplo

O exemplo de código a seguir cria um tipo não genérico, DemoType, com um método genérico, Factory. Esse método tem dois parâmetros de tipo genérico, TInput Para especificar um tipo de entrada e TOutput Para especificar um tipo de saída. The TOutput o parâmetro de tipo é restrito a implementar ICollection<TInput> (ICollection(Of TInput) no Visual Basic), para um tipo de referência e tem um construtor sem parâmetros.

O método tem um parâmetro formal, que é uma matriz de TInput. O método retorna uma instância de TOutput que contém todos os elementos da matriz de entrada. TOutput pode ser qualquer tipo de coleção genérica que implementa o ICollection<T> interface genérica.

Quando o código é executado, o assembly dinâmico é salva sistema autônomo DemoGenericMethod1.dll e pode ser examinado com o uso do Desassemblador do MSIL (ILDASM.exe).

Observação:

Uma mercadoria maneira de aprender a emitir código é escrever um programa do Visual Basic, translation from VPE for Csharp ou Visual C++ que executa a tarefa é um Re tentando emitir e usar o desmontador para examinar o MSIL produzido pelo compilador.

O exemplo de código inclui código-fonte que é equivalente ao método emitido.O método emitido é chamado ligação tardia e também por meio de um delegado genérico declaradas no exemplo de código.

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

Compilando o código

  • O código contém o translation from VPE for Csharp using () instruçõesImports no Visual Basic) necessário para compilação.

  • Nenhuma referência de assembly adicionais é necessária.

  • compilar o código na linha de comando usando csc.exe, vbc.exe ou cl.exe.Para compilar o código no Visual Studio, coloque-o em um modelo de projeto de aplicativo de console.

Consulte também

Tarefas

Como: Definir um tipo genérico com reflexão Emit

Referência

MethodBuilder