Sdílet prostřednictvím


Postupy: Definování obecné metody pomocí generování reflexe

První procedura ukazuje, jak vytvořit jednoduchou obecnou metodu se dvěma parametry typu a jak použít omezení třídy, omezení rozhraní a zvláštní omezení pro parametry typu.

Druhý postup ukazuje, jak vygenerovat tělo metody, jak používat parametry typu obecné metody k vytvoření instance obecných typů a jak volat jejich metody.

Třetí postup ukazuje, jak zavolat obecnou metodu.

Důležité

Metoda není obecná jen proto, že patří obecnému typu a používá parametry typu daného typu. Metoda je obecná pouze v případě, že má svůj vlastní seznam parametrů typu. Obecná metoda se může objevit v neobecném typu, jako je tomu v následujícím příkladu. Příklad negenerické metody pro obecný typ naleznete v tématu Postupy: Definování obecného typu s Reflexe ion Emit.

Definování obecné metody

  1. Než začnete, je užitečné si prohlédnout, jak obecná metoda vypadá, když je napsána pomocí jazyka vysoké úrovně. Následující kód je součástí ukázkového kódu pro tento článek spolu s kódem pro volání obecné metody. Metoda má dva parametry typu a TInputTOutput, druhý z nichž musí být odkazový typ (class), musí mít konstruktor bez parametrů (new) a musí implementovat ICollection<TInput>. Toto omezení rozhraní zajišťuje, že metodu ICollection<T>.Add lze použít k přidání prvků do kolekce TOutput, kterou metoda vytvoří. Tato metoda má jeden formální parametr input, což je pole TInput. Metoda vytvoří kolekci typu TOutput a zkopíruje prvky parametru input do této kolekce.

    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 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
    
  2. Definujte dynamické sestavení a dynamický modul obsahující typ, ke kterému tato obecná metoda patří. V tomto případě má sestavení pouze jeden modul s názvem DemoMethodBuilder1 a název tohoto modulu je stejný jako název sestavení s příponou souboru. V tomto příkladu je sestavení uloženo na disk a také spuštěno, takže je zadáno AssemblyBuilderAccess.RunAndSave. Pomocí Ildasm.exe (IL Disassembler) můžete prozkoumat DemoMethodBuilder1.dll a porovnat ho s běžným zprostředkujícím jazykem (CIL) pro metodu uvedenou v kroku 1.

    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");
    
    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")
    
  3. Definujte typ, ke kterému obecná metoda patří. Tento typ nemusí být obecný. Obecná metoda může patřit obecnému nebo neobecnému typu. V příkladu je tento typ třída, není obecná a má název DemoType.

    TypeBuilder demoType =
        demoModule.DefineType("DemoType", TypeAttributes.Public);
    
    Dim demoType As TypeBuilder = demoModule.DefineType( _
        "DemoType", _
        TypeAttributes.Public)
    
  4. Definujte obecnou metodu. Pokud jsou typy formálních parametrů obecné metody určeny parametry obecného typu obecné metody, použijte přetížení metody DefineMethod(String, MethodAttributes) k definici této metody. Parametry obecného typu metody nejsou ještě definovány, proto nelze určit typy formálních parametrů metody při volání metody DefineMethod. Metoda v tomto příkladu je pojmenována Factory. Tato metoda je veřejná a deklarovaná jako static (Shared v jazyce Visual Basic).

    MethodBuilder factory =
        demoType.DefineMethod("Factory",
            MethodAttributes.Public | MethodAttributes.Static);
    
    Dim factory As MethodBuilder = _
        demoType.DefineMethod("Factory", _
            MethodAttributes.Public Or MethodAttributes.Static)
    
  5. Definujte parametry obecného typu metody DemoMethod předáním pole řetězců obsahujícího názvy parametrů metodě MethodBuilder.DefineGenericParameters. To tuto metodu učiní obecnou metodou. Následující kód učiní metodu Factory obecnou metodou s parametry typu TInput a TOutput. Pro usnadnění čtení kódu jsou vytvořeny proměnné s těmito názvy pro uchování objektů GenericTypeParameterBuilder, které představují dva parametry typu.

    string[] typeParameterNames = {"TInput", "TOutput"};
    GenericTypeParameterBuilder[] typeParameters =
        factory.DefineGenericParameters(typeParameterNames);
    
    GenericTypeParameterBuilder TInput = typeParameters[0];
    GenericTypeParameterBuilder TOutput = typeParameters[1];
    
    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)
    
  6. Volitelně lze těmto parametrům typu přidat zvláštní omezení. Zvláštní omezení jsou přidána pomocí metody SetGenericParameterAttributes. V tomto příkladu je parametr TOutput omezen na odkazový typ a musí mít konstruktor bez parametrů.

    TOutput.SetGenericParameterAttributes(
        GenericParameterAttributes.ReferenceTypeConstraint |
        GenericParameterAttributes.DefaultConstructorConstraint);
    
    TOutput.SetGenericParameterAttributes( _
        GenericParameterAttributes.ReferenceTypeConstraint Or _
        GenericParameterAttributes.DefaultConstructorConstraint)
    
  7. Volitelně lze těmto parametrům typu přidat omezení rozhraní a třídy. V tomto příkladu je parametr typu TOutput omezen na typy, které implementují rozhraní ICollection(Of TInput) (ICollection<TInput> v jazyce C#). To zajišťuje, že metodu Add lze použít k přidání prvků.

    Type icoll = typeof(ICollection<>);
    Type icollOfTInput = icoll.MakeGenericType(TInput);
    Type[] constraints = {icollOfTInput};
    TOutput.SetInterfaceConstraints(constraints);
    
    Dim icoll As Type = GetType(ICollection(Of ))
    Dim icollOfTInput As Type = icoll.MakeGenericType(TInput)
    Dim constraints() As Type = {icollOfTInput}
    TOutput.SetInterfaceConstraints(constraints)
    
  8. Definujte formální parametry metody pomocí metody SetParameters. V tomto příkladu má metoda Factory jen jeden parametr, pole TInput. Tento typ je vytvořen zavoláním metody MakeArrayType třídy GenericTypeParameterBuilder, která představuje parametr TInput. Argument metody SetParameters je pole objektů Type.

    Type[] parms = {TInput.MakeArrayType()};
    factory.SetParameters(parms);
    
    Dim params() As Type = {TInput.MakeArrayType()}
    factory.SetParameters(params)
    
  9. Definujte návratový typ metody pomocí metody SetReturnType. V tomto příkladu je vrácena instance typu parametru TOutput.

    factory.SetReturnType(TOutput);
    
    factory.SetReturnType(TOutput)
    
  10. Vygenerujte tělo metody pomocí typu ILGenerator. Podrobnosti naleznete v souvisejícím postupu pro vygenerování těla metody.

    Důležité

    Při generování volání metod obecných typů v případě, že typy argumentů těchto typů jsou parametry typu obecné metody, je nutné pomocí přetížení metod static, GetConstructor(Type, ConstructorInfo) a GetMethod(Type, MethodInfo) deklarovaných jako GetField(Type, FieldInfo) třídy TypeBuilder získat zkonstruované formy této metody. Tento fakt ukazuje související postup vygenerování těla metody.

  11. Dokončete typ obsahující tuto metodu a sestavení uložte. Související postup volání obecné metody ukazuje dva způsoby volání dokončené metody.

    // Complete the type.
    Type dt = demoType.CreateType();
    // Save the assembly, so it can be examined with Ildasm.exe.
    demoAssembly.Save(asmName.Name+".dll");
    
    ' 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")
    

Vygenerování těla metody

  1. Získejte generátor kódu a deklarujte místní proměnné a popisky. Metoda DeclareLocal se používá při deklarování lokálních proměnných. Metoda Factory má čtyři místní proměnné: retVal pro uložení nové TOutput , která je vrácena metodou, ic pro uložení TOutput při přetypování na ICollection<TInput>, input pro uchování vstupní pole TInput objektů a index iterace prostřednictvím pole. Tato metoda má také dva popisky, jeden pro vstup do smyčky (enterLoop) a jeden pro začátek smyčky (loopAgain), definované pomocí metody DefineLabel.

    První věc, kterou metoda provede, je načtení jejího argumentu pomocí operačního kódu Ldarg_0 a jeho uložení do místní proměnné input pomocí operačního kódu Stloc_S.

    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);
    
    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)
    
  2. Vygenerujte kód pro vytvoření instance objektu TOutput pomocí obecného přetížení metody Activator.CreateInstance. Toto přetížení vyžaduje, aby zadaný typ měl konstruktor bez parametrů, což je důvodem pro přidání tohoto omezení parametru TOutput. Vytvořte konstruovanou obecnou metodu předáním parametru TOutput metodě MakeGenericMethod. Po vygenerování kódu pro volání této metody vygenerujte kód pro uložení do místní proměnné retVal pomocí operačního kódu Stloc_S

    MethodInfo createInst =
        typeof(Activator).GetMethod("CreateInstance", Type.EmptyTypes);
    MethodInfo createInstOfTOutput =
        createInst.MakeGenericMethod(TOutput);
    
    ilgen.Emit(OpCodes.Call, createInstOfTOutput);
    ilgen.Emit(OpCodes.Stloc_S, retVal);
    
    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)
    
  3. Vygenerujte kód pro přetypování nového objektu TOutput na rozhraní ICollection(Of TInput) a uložte jej do místní proměnné ic.

    ilgen.Emit(OpCodes.Ldloc_S, retVal);
    ilgen.Emit(OpCodes.Box, TOutput);
    ilgen.Emit(OpCodes.Castclass, icollOfTInput);
    ilgen.Emit(OpCodes.Stloc_S, ic);
    
    ilgen.Emit(OpCodes.Ldloc_S, retVal)
    ilgen.Emit(OpCodes.Box, TOutput)
    ilgen.Emit(OpCodes.Castclass, icollOfTInput)
    ilgen.Emit(OpCodes.Stloc_S, ic)
    
  4. Získejte objekt MethodInfo představující metodu ICollection<T>.Add. Metoda funguje na objektu ICollection<TInput>, takže je nutné získat metodu Add specifickou pro tento vytvořený typ. Nelze použít metodu GetMethod pro získání objektu MethodInfo přímo z proměnné icollOfTInput, protože metoda GetMethod není podporována typem, který byl zkonstruován pomocí typu GenericTypeParameterBuilder. Namísto toho zavolejte metodu GetMethod proměnné icoll, která obsahuje definici obecného typu obecného rozhraní ICollection<T>. Poté použijte metodu GetMethod(Type, MethodInfo) deklarovanou jako static pro vytvoření typu MethodInfo pro konstruovaný typ. Tento fakt ukazuje následující kód.

    MethodInfo mAddPrep = icoll.GetMethod("Add");
    MethodInfo mAdd = TypeBuilder.GetMethod(icollOfTInput, mAddPrep);
    
    Dim mAddPrep As MethodInfo = icoll.GetMethod("Add")
    Dim mAdd As MethodInfo = _
        TypeBuilder.GetMethod(icollOfTInput, mAddPrep)
    
  5. Vygenerujte kód pro inicializaci proměnné index načtením 32bitového celého čísla 0 a uložte jej do proměnné. Vygenerujte kód větvení pro popisek enterLoop. Tento popisek dosud není označen, protože je uvnitř smyčky. Kód smyčky je vygenerován v dalším kroku.

    // 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. Vygenerujte kód smyčky. Prvním krokem je označit začátek smyčky voláním metody MarkLabel s popiskem loopAgain. Příkazy větvení, které používají tento popisek, se budou nyní větvit do tohoto bodu kódu. Dalším krokem je vložení objektu TOutput, přetypovaného na rozhraní ICollection(Of TInput), do zásobníku. To není nutné provádět okamžitě, ale objekt musí být vložen pro volání metody Add. Příště, když je vstupní pole vloženo do zásobníku, bude proměnná index obsahovat aktuální index pole. Operační kód Ldelem vyjme index a pole ze zásobníku a vloží indexovaný prvek pole do zásobníku. Zásobník je nyní připraven pro volání metody ICollection<T>.Add, která vyjme kolekci a nový prvek ze zásobníku a přidá prvek do kolekce.

    Zbytek kódu ve smyčce zvýší index a testuje, zda je smyčka dokončena. Index a 32bitové celé číslo 1 se vloží do zásobníku a jsou přičteny, což v zásobníku zanechá součet. Tento součet je uložen v proměnné index. Voláním metody MarkLabel je tento bod nastaven jako vstupní bod smyčky. Index je znovu načten. Vstupní pole je vloženo do zásobníku a operační kód Ldlen je vygenerován pro získání jeho délky. Index a délka jsou nyní v zásobníku a operační kód Clt je vygenerován pro jejich porovnání. Pokud je index menší než délka, operační kód Brtrue_S provede větvení zpět na začátek smyčky.

    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. Vygenerujte kód pro vložení objektu TOutput do zásobníku a vrácení z metody. Místní proměnné retVal a ic obsahují odkazy na nový objekt TOutput. Proměnná ic se používá pouze pro přístup k metodě ICollection<T>.Add.

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

Vyvolání obecné metody

  1. Factory je definice obecné metody. Aby ji bylo možné vyvolat, je nutné přiřadit typy parametrů obecného typu. To lze učinit použitím metody MakeGenericMethod. Následující kód vytvoří konstruovanou obecnou metodu, určí typ String jako typ parametru TInput a typ List(Of String) (List<string> v jazyce C#) jako typ parametru TOutput a zobrazí řetězec představující tuto metodu.

    MethodInfo m = dt.GetMethod("Factory");
    MethodInfo bound =
        m.MakeGenericMethod(typeof(string), typeof(List<string>));
    
    // Display a string representing the bound method.
    Console.WriteLine(bound);
    
    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)
    
  2. Chcete-li vyvolat metodu pomocí pozdní vazby, použijte metodu Invoke. Následující kód vytvoří pole typů Object obsahující jako jediný prvek pole řetězců a předá jej jako seznam argumentů obecné metodě. První parametr metody Invoke je nulový odkaz, protože je tato metoda deklarovaná jako static. Vrácená hodnota je přetypována na typ List(Of String) a zobrazí se její první prvek.

    object o = bound.Invoke(null, new object[]{arr});
    List<string> list2 = (List<string>) o;
    
    Console.WriteLine("The first element is: {0}", list2[0]);
    
    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))
    
  3. Chcete-li tuto metodu vyvolat pomocí delegáta, musíte mít delegáta, který odpovídá signatuře konstruované obecné metody. To lze snadno provést vytvořením obecného delegáta. Následující kód vytvoří instanci obecného delegáta D definovaného v příkladu kódu pomocí přetížení metody Delegate.CreateDelegate(Type, MethodInfo) a tohoto delegáta vyvolá. Delegáti poskytují vyšší výkon než volání s pozdní vazbou.

    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]);
    
    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))
    
  4. Vygenerovanou metodu lze také volat z programu, který odkazuje na uložené sestavení.

Příklad

Následující příklad kódu vytvoří neobecný typ DemoType s obecnou metodou Factory. Tato metoda má dva parametry obecného typu, TInput určující vstupní typ a TOutput určující výstupní typ. Parametr typu TOutput je omezen na implementaci rozhraní ICollection<TInput> (ICollection(Of TInput) v jazyce Visual Basic), musí být odkazovým typem a mít konstruktor bez parametrů.

Tato metoda má jeden formální parametr, což je pole TInput. Metoda vrací instanci typu TOutput, která obsahuje všechny prvky vstupního pole. Parametr TOutput může být libovolný typ obecné kolekce, která implementuje obecné rozhraní ICollection<T>.

Při spuštění kódu se dynamické sestavení uloží jako DemoGenericMethod1.dll a lze ho prozkoumat pomocí Ildasm.exe (IL Disassembler).

Poznámka:

Dobrým způsobem, jak zjistit, jak generovat kód, je napsat program, který provádí úlohu, kterou se pokoušíte generovat, a pomocí zpětného překladu prozkoumat soubor CIL vytvořený kompilátorem.

Příklad kódu obsahuje zdrojový kód, který odpovídá vygenerované metodě. Vygenerovaná metoda je zavolána s pozdní vazbou a také pomocí obecného delegáta deklarovaného v příkladu kódu.

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, TOutput);
        ilgen.Emit(OpCodes.Castclass, icollOfTInput);
        ilgen.Emit(OpCodes.Stloc_S, ic);

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

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

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

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

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

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

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

        // Complete the type.
        Type dt = demoType.CreateType();
        // 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
 */
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, TOutput)
        ilgen.Emit(OpCodes.Castclass, icollOfTInput)
        ilgen.Emit(OpCodes.Stloc_S, ic)

        ' Loop through the array, adding each element to the new
        ' instance of TOutput. Note that in order to get a MethodInfo
        ' for ICollection(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

Viz také