Freigeben über


Gewusst wie: Definieren einer generischen Methode mit Reflektionsausgabe

Im ersten Verfahren wird veranschaulicht, wie eine einfache generische Methode mit zwei Typparametern erstellt wird und wie auf die Typparameter Klasseneinschränkungen, Schnittstelleneinschränkungen und besonderen Einschränkungen angewendet werden.

Im zweiten Verfahren wird veranschaulicht, wie der Methodentext ausgegeben wird und wie mithilfe der Typparameter der generischen Methode Instanzen generischer Typen erstellt und deren Methoden aufgerufen werden.

Im dritten Verfahren wird das Aufrufen der generischen Methode veranschaulicht.

Wichtiger HinweisWichtig

Eine Methode ist nicht generisch, weil sie zu einem generischen Typ gehört und die Typparameter dieses Typs verwendet.Eine Methode ist nur dann generisch, wenn sie über eine eigene Typparameterliste verfügt.Eine generische Methode kann zu einem nicht generischen Typ gehören, wie im vorliegenden Beispiel.Ein Beispiel für eine nicht generische Methode für einen generischen Typ finden Sie unter Gewusst wie: Definieren eines generischen Typs mit Reflektionsausgabe.

So definieren Sie eine generische Methode

  1. Sie sollten sich zunächst einmal klarmachen, wie eine in einer hoch entwickelten Sprache geschriebene generische Methode aussieht. Der folgende Code befindet sich, zusammen mit dem Code zum Aufrufen der generischen Methode, im Beispielcode für dieses Thema. Die Methode verfügt über die beiden Typparameter TInput und TOutput, wobei der zweite ein Referenztyp sein muss (class), einen parameterlosen Konstruktor (new) aufweisen und ICollection(Of TInput) (ICollection<TInput> in C#) implementieren muss. Dank dieser Schnittstelleneinschränkung kann die ICollection<T>.Add-Methode zum Hinzufügen von Elementen zur TOutput-Auflistung verwendet werden, die von der Methode erstellt wird. Die Methode verfügt über einen formalen Parameter input, der ein Array von TInput ist. Die Methode erstellt eine Auflistung vom Typ TOutput und kopiert die Elemente von input in die Auflistung.

    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. Definieren Sie eine dynamische Assembly und ein dynamisches Modul, um den Typ aufzunehmen, dem die generische Methode angehört. Die Assembly enthält in diesem Fall nur ein Modul mit der Bezeichnung DemoMethodBuilder1. Der Modulname setzt sich aus dem Assemblynamen und einer Erweiterung zusammen. In diesem Beispiel wird die Assembly ausgeführt und auf einem Datenträger gespeichert, daher wird AssemblyBuilderAccess.RunAndSave angegeben. Mithilfe von Ildasm.exe (MSIL Disassembler-Tool) können Sie DemoMethodBuilder1.dll untersuchen und mit dem in Schritt 1 gezeigten Microsoft Intermediate Language (MSIL)-Code für die Methode vergleichen.

    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. Definieren Sie den Typ, zu dem die generische Methode gehört. Der Typ muss nicht generisch sein. Eine generische Methode kann sowohl zu einem generischen als auch zu einem nicht generischen Typ gehören. Der Typ ist hier eine Klasse, ist nicht generisch und trägt die Bezeichnung DemoType.

    Dim demoType As TypeBuilder = demoModule.DefineType( _
        "DemoType", _
        TypeAttributes.Public) 
    
    TypeBuilder demoType = 
        demoModule.DefineType("DemoType", TypeAttributes.Public);
    
  4. Definieren Sie die generische Methode. Wenn die Typen der formalen Parameter einer generischen Methode durch generische Typparameter der generischen Methode angegeben werden, definieren Sie die Methode mithilfe der DefineMethod(String, MethodAttributes)-Methodenüberladung. Da die generischen Typparameter der Methode noch nicht definiert sind, können Sie die Typen der formalen Parameter der Methode im Aufruf von DefineMethod nicht angeben. In diesem Beispiel trägt die Methode die Bezeichnung Factory. Die Methode ist öffentlich und static (Shared in Visual Basic).

    Dim factory As MethodBuilder = _
        demoType.DefineMethod("Factory", _
            MethodAttributes.Public Or MethodAttributes.Static)
    
    MethodBuilder factory = 
        demoType.DefineMethod("Factory", 
            MethodAttributes.Public | MethodAttributes.Static);
    
  5. Definieren Sie die generischen Typparameter von DemoMethod, indem Sie ein Array von Zeichenfolgen, das die Namen der Parameter enthält, an die MethodBuilder.DefineGenericParameters-Methode übergeben. Dadurch wird die Methode zu einer generischen Methode. Im folgenden Code wird Factory zu einer generischen Methode mit den beiden Typparametern TInput und TOutput gemacht. Um den Code verständlicher zu gestalten, werden gleichnamige Variablen erstellt, die die GenericTypeParameterBuilder-Objekte aufnehmen, die die beiden Typparameter darstellen.

    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. Fügen Sie den Typparametern bei Bedarf besondere Einschränkungen hinzu. Besondere Einschränkungen werden mit der SetGenericParameterAttributes-Methode hinzugefügt. In diesem Beispiel wird TOutput durch eine Einschränkung zu einem Referenztyp, der über einen parameterlosen Konstruktor verfügt.

    TOutput.SetGenericParameterAttributes( _
        GenericParameterAttributes.ReferenceTypeConstraint Or _
        GenericParameterAttributes.DefaultConstructorConstraint)
    
    TOutput.SetGenericParameterAttributes(
        GenericParameterAttributes.ReferenceTypeConstraint | 
        GenericParameterAttributes.DefaultConstructorConstraint);
    
  7. Fügen Sie den Typparametern bei Bedarf Klassen- und Schnittstelleneinschränkungen hinzu. In diesem Beispiel wird der Typparameter TOutput auf Typen eingeschränkt, die die ICollection(Of TInput)-Schnittstelle (ICollection<TInput> in C#) implementieren. Dadurch kann die Add-Methode zum Hinzufügen von Elementen verwendet werden.

    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. Definieren Sie die formalen Parameter der Methode mithilfe der SetParameters-Methode. In diesem Beispiel verfügt die Factory-Methode über einen Parameter, ein Array von TInput. Dieser Typ wird durch aufrufen der MakeArrayType-Methode für GenericTypeParameterBuilder erstellt, der TInput darstellt. Das Argument von SetParameters ist ein Array von Type-Objekten.

    Dim params() As Type = { TInput.MakeArrayType() }
    factory.SetParameters(params)
    
    Type[] parms = {TInput.MakeArrayType()};
    factory.SetParameters(parms);
    
  9. Definieren Sie den Rückgabetyp für die Methode mithilfe der SetReturnType-Methode. In diesem Beispiel wird eine Instanz von TOutput zurückgegeben.

    factory.SetReturnType(TOutput)
    
    factory.SetReturnType(TOutput);
    
  10. Geben Sie den Methodentext mithilfe von ILGenerator aus. Weitere Einzelheiten hierzu finden Sie im zugehörigen Verfahren So geben Sie den Methodentext aus.

    Wichtiger HinweisWichtig

    Wenn Sie Aufrufe von Methoden generischer Typen ausgeben und es sich bei den Typargumenten dieser Typen um Typparameter der generischen Methode handelt, müssen Sie über die static-Methodenüberladungen GetConstructor(Type, ConstructorInfo), GetMethod(Type, MethodInfo) und GetField(Type, FieldInfo) der TypeBuilder-Klasse erstellte Formulare der Methoden abrufen.Dies wird im zugehörigen Verfahren zum Ausgeben von Methodentext veranschaulicht.

  11. Vervollständigen Sie den Typ, der die Methode enthält, und speichern Sie die Assembly. Im zugehörigen Verfahren So rufen Sie die generische Methode auf werden zwei Möglichkeiten zum Aufrufen der vollständigen Methode veranschaulicht.

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

So geben Sie den Methodentext aus

  1. Rufen Sie einen Code-Generator ab, und deklarieren Sie lokale Variablen und Bezeichnungen. Die lokalen Variablen werden mit der DeclareLocal-Methode deklariert. Die Factory-Methode verfügt über vier lokale Variablen: retVal nimmt den neuen TOutput auf, der von der Methode zurückgegeben wird, ic nimmt den TOutput bei der Umwandlung in ICollection(Of TInput) (ICollection<TInput> in C#) auf, input nimmt das Eingabearray von TInput-Objekten auf, und index durchläuft das Array. Die Methode enthält auch zwei Bezeichnungen, eine zum Starten der Schleife (enterLoop) und eine für den Anfang der Schleife (loopAgain), die mit der DefineLabel-Methode definiert werden.

    Die Methode lädt zunächst mit dem Ldarg_0-Opcode ihre Argumente und speichert diese mit dem in dem Stloc_S-Opcode in der lokalen Variable 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. Geben Sie Code aus, um mithilfe der generischen Methodenüberladung der Activator.CreateInstance-Methode eine Instanz von TOutput zu erstellen. Für die Verwendung dieser Überladung muss der angegebene Typ über einen parameterlosen Konstruktor verfügen, daher wird TOutput diese Einschränkung hinzugefügt. Erstellen Sie die erstellte generische Methode, indem Sie TOutput an MakeGenericMethod übergeben. Geben Sie nach der Ausgabe von Code für den Methodenaufruf mithilfe von Stloc_S Code zum Speichern in der lokalen Variable retVal aus.

    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. Geben Sie Code aus, um das neue TOutput-Objekt in ICollection(Of TInput) umzuwandeln und dieses in der lokalen Variable ic zu speichern.

    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. Rufen Sie eine MethodInfo ab, die die ICollection<T>.Add-Methode darstellt. Da diese Methode ICollection(Of TInput) (ICollection<TInput> in C#) behandelt, muss die für diesen erstellten Typ spezifische Add-Methode abgerufen werden. Sie können diese MethodInfo nicht mithilfe der GetMethod-Methode direkt aus icollOfTInput abrufen, da GetMethod nicht für einen Typ unterstützt wird, der mit einem GenericTypeParameterBuilder erstellt wurde. Rufen Sie stattdessen GetMethod für icoll auf, der die Definition des generischen Typs für die generische ICollection<T>-Schnittstelle enthält. Erzeugen Sie dann mithilfe der static GetMethod(Type, MethodInfo)-Methode die MethodInfo für den konstruierten Typ. Im folgenden Code wird dies veranschaulicht.

    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. Geben Sie Code aus, um die index-Variable zu initialisieren, indem Sie eine 32-Bit-Ganzzahl 0 laden und diese in der Variablen speichern. Geben Sie Code aus, um zur Bezeichnung enterLoop zu verzweigen. Diese Bezeichnung wurde noch nicht markiert, da sie sich in der Schleife befindet. Der Code für die Schleife wird im nächsten Schritt ausgegeben.

    ' 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. Geben Sie Code für die Schleife aus. Zunächst wird der Anfang der Schleife markiert, indem MarkLabel mit der loopAgain-Bezeichnung aufgerufen wird. Verzweigungsanweisungen, die die Bezeichnung verwenden, verzweigen jetzt zu diesem Punkt im Code. Anschließend wird das in ICollection(Of TInput) umgewandelte TOutput-Objekt auf dem Stapel abgelegt. Es wird zwar nicht sofort benötigt, muss jedoch zum Aufrufen der Add-Methode vorhanden sein. Nun werden das Eingabearray auf dem Stapel und die index-Variable mit dem aktuellen Index im Array abgelegt. Der Ldelem-Opcode nimmt den Index und das Array vom Stapel auf und legt das indizierte Arrayelement auf dem Stapel ab. Der Stapel ist jetzt für das Aufrufen der ICollection<T>.Add-Methode bereit, die die Auflistung und das neue Element vom Stapel aufnimmt und das Element zur Auflistung hinzufügt.

    Der folgende Code in der Schleife erhöht den Index und überprüft, ob die Schleife beendet ist: Der Index und eine 32-Bit-Ganzzahl 1 werden auf dem Stapel abgelegt und addiert, wobei die Summe auf dem Stapel verbleibt. Die Summe wird in index gespeichert. MarkLabel wird aufgerufen, um diesen Punkt als Einstiegspunkt für die Schleife festzulegen. Der Index wird erneut geladen. Das Eingabearray wird auf dem Stapel abgelegt und Ldlen wird ausgegeben, um seine Länge abzurufen. Nun befinden sich der Index und die Länge auf dem Stapel, und Clt wird ausgegeben, um sie zu vergleichen. Wenn der Index kleiner als die Länge ist, verzweigt Brtrue_S zurück zum Anfang der Schleife.

    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. Geben Sie Code aus, um das TOutput-Objekt auf dem Stapel abzulegen und die Methode zu beenden. Die lokalen Variablen retVal und ic enthalten beide Verweise auf die neue TOutput. ic wird nur für den Zugriff auf die ICollection<T>.Add-Methode verwendet.

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

So rufen Sie die generische Methode auf

  1. Factory ist eine generische Methodendefinition. Um sie aufzurufen, müssen Sie ihren generischen Typparametern Typen zuweisen. Verwenden Sie hierfür die MakeGenericMethod-Methode. Im folgenden Code wird eine konstruierte generische Methode erstellt. Dabei wird String für TInput und List(Of String) (List<string> in C#) für TOutput angegeben, und es wird eine Zeichenfolgendarstellung der Methode angezeigt.

    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. Um die Methode spät gebunden aufzurufen, verwenden Sie die Invoke-Methode. Im folgenden Code wird ein Array von Object erstellt, dessen einziges Element ein Array von Zeichenfolgen ist, und als Argumentliste für die generische Methode übergeben. Der erste Parameter von Invoke ist ein NULL-Verweis, da die Methode static ist. Der Rückgabewert wird in List(Of String) umgewandelt, und sein erstes Element wird angezeigt.

    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. Um die Methode mithilfe eines Delegaten aufzurufen, benötigen Sie einen Delegaten, der der Signatur der konstruierten generischen Methode entspricht. Dafür erstellen Sie ganz einfach einen generischen Delegaten. Im folgenden Code wird mithilfe der Delegate.CreateDelegate(Type, MethodInfo)-Methodenüberladung eine Instanz des im Beispielcode definierten generischen Delegaten D erstellt und aufgerufen. Delegaten bieten eine bessere Leistung als spät gebundene Aufrufe.

    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. Die ausgegebene Methode kann auch von einem Programm aufgerufen werden, das auf die gespeicherte Assembly verweist.

Beispiel

Im folgenden Codebeispiel wird ein nicht generischer Typ DemoType mit der generischen Methode Factory erstellt. Diese Methode verfügt über zwei generische Typparameter, TInput zum Angeben eines Eingabetyps und TOutput zum Angeben eines Ausgabetyps. Der TOutput-Typparameter wird durch Implementieren von ICollection<TInput> (ICollection(Of TInput) in Visual Basic) zu einem Referenztyp eingeschränkt, der über einen parameterlosen Konstruktor verfügt.

Die Methode verfügt über einen formalen Parameter, der ein Array von TInput ist. Die Methode gibt eine Instanz von TOutput zurück, die alle Elemente des Eingabearrays enthält. TOutput kann ein beliebiger generischer Auflistungstyp sein, der die generische ICollection<T>-Schnittstelle implementiert.

Bei der Codeausführung wird die dynamische Assembly als DemoGenericMethod1.dll gespeichert. Sie kann mit Ildasm.exe (MSIL Disassembler-Tool) untersucht werden.

HinweisHinweis

Wenn Sie das Ausgeben von Code erlernen möchten, empfiehlt es sich, ein Visual Basic-, C#- oder Visual C++-Programm zu schreiben, das die Aufgabe ausführt, die Sie ausgeben möchten, und anschließend anhand eines Disassemblers den vom Compiler generierten MSIL-Code zu untersuchen.

Das Codebeispiel enthält Quellcode, der der ausgegebenen Methode entspricht. Die ausgegebene Methode wird spät gebunden anhand eines generischen Delegaten aufgerufen, der im Codebeispiel deklariert ist.

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

Kompilieren des Codes

  • Der Code enthält die für die Kompilierung erforderlichen using-Anweisungen für C# (Imports in Visual Basic).

  • Er werden keine weiteren Assemblyverweise benötigt.

  • Kompilieren Sie den Code über die Befehlszeile mit csc.exe, vbc.exe oder cl.exe. Um den Code in Visual Studio zu kompilieren, fügen Sie ihn in eine Projektvorlage für eine Konsolenanwendung ein.

Siehe auch

Aufgaben

Gewusst wie: Definieren eines generischen Typs mit Reflektionsausgabe

Referenz

MethodBuilder