Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Das erste Verfahren zeigt, wie Sie eine einfache generische Methode mit zwei Typparametern erstellen und wie Klasseneinschränkungen, Schnittstelleneinschränkungen und spezielle Einschränkungen auf die Typparameter angewendet werden.
Die zweite Prozedur zeigt, wie der Methodentext ausgegeben wird und wie Sie die Typparameter der generischen Methode verwenden, um Instanzen generischer Typen zu erstellen und ihre Methoden aufzurufen.
Die dritte Prozedur zeigt, wie die generische Methode aufgerufen wird.
Von Bedeutung
Eine Methode ist nicht generisch, nur weil sie zu einem generischen Typ gehört und die Typparameter dieses Typs verwendet. Eine Methode ist nur generisch, wenn sie über eine eigene Typparameterliste verfügt. Eine generische Methode kann auf einem nichtgenerischen Typ angezeigt werden, wie in diesem Beispiel. Ein Beispiel für eine nichtgenerische Methode für einen generischen Typ finden Sie unter How to: Define a Generic Type with Reflection Emit.
Definieren einer generischen Methode
Bevor Sie beginnen, sollten Sie sich ansehen, wie die generische Methode angezeigt wird, wenn sie mit einer allgemeinen Sprache geschrieben wird. Der folgende Code ist im Beispielcode für diesen Artikel enthalten, zusammen mit Code zum Aufrufen der generischen Methode. Die Methode hat zwei Typparameter,
TInput
undTOutput
. Der zweite muss ein Verweistyp (class
) sein, einen parameterlosen Konstruktor aufweisen undnew
implementieren. Diese Schnittstelleneinschränkung stellt sicher, dass die ICollection<T>.Add Methode verwendet werden kann, um derTOutput
Von der Methode erstellten Auflistung Elemente hinzuzufügen. Die Methode hat einen formalen Parameter,input
, der ein Array vonTInput
ist. Die Methode erstellt eine Auflistung vom TypTOutput
und kopiert die Elemente voninput
in die Auflistung.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
Definieren Sie eine dynamische Assembly und ein dynamisches Modul, das den Typ enthält, zu dem die generische Methode gehört. In diesem Fall hat die Assembly nur ein Modul, den Namen
DemoMethodBuilder1
, und der Modulname ist identisch mit dem Assemblynamen plus einer Erweiterung. In diesem Beispiel wird die Assembly auf dem Datenträger gespeichert und auch ausgeführt. Dies ist also AssemblyBuilderAccess.RunAndSave angegeben. Sie können die Ildasm.exe (IL Disassembler) verwenden, um DemoMethodBuilder1.dll zu untersuchen und mit der gemeinsamen Zwischensprache (CIL) für die in Schritt 1 gezeigte Methode zu vergleichen.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")
Definieren Sie den Typ, zu dem die generische Methode gehört. Der Typ muss nicht generisch sein. Eine generische Methode kann entweder zu einem generischen oder nichtgenerischen Typ gehören. In diesem Beispiel ist der Typ eine Klasse, nicht generisch und benannt
DemoType
.TypeBuilder demoType = demoModule.DefineType("DemoType", TypeAttributes.Public);
Dim demoType As TypeBuilder = demoModule.DefineType( _ "DemoType", _ TypeAttributes.Public)
Definieren Sie die generische Methode. Wenn die Typen der formalen Parameter einer generischen Methode durch generische Typparameter der generischen Methode angegeben werden, verwenden Sie die DefineMethod(String, MethodAttributes) Methodenüberladung, um die Methode zu definieren. Die generischen Typparameter der Methode sind noch nicht definiert, sodass Sie die Typen der formalen Parameter der Methode nicht im Aufruf DefineMethodangeben können. In diesem Beispiel wird die Methode benannt
Factory
. Die Methode ist öffentlich undstatic
(Shared
in Visual Basic).MethodBuilder factory = demoType.DefineMethod("Factory", MethodAttributes.Public | MethodAttributes.Static);
Dim factory As MethodBuilder = _ demoType.DefineMethod("Factory", _ MethodAttributes.Public Or MethodAttributes.Static)
Definieren Sie die generischen Typparameter
DemoMethod
, indem Sie ein Array von Zeichenfolgen übergeben, das die Namen der Parameter an die MethodBuilder.DefineGenericParameters Methode enthält. Dadurch wird die Methode zu einer generischen Methode. Der folgende Code erstelltFactory
eine generische Methode mit TypparameternTInput
undTOutput
. Damit der Code leichter zu lesen ist, werden Variablen mit diesen Namen erstellt, um die Objekte zu speichern, die die beiden Typparameter GenericTypeParameterBuilder darstellen.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)
Fügen Sie optional den Typparametern spezielle Einschränkungen hinzu. Mithilfe der SetGenericParameterAttributes Methode werden spezielle Einschränkungen hinzugefügt. In diesem Beispiel
TOutput
ist die Beschränkung auf einen Verweistyp und auf einen parameterlosen Konstruktor beschränkt.TOutput.SetGenericParameterAttributes( GenericParameterAttributes.ReferenceTypeConstraint | GenericParameterAttributes.DefaultConstructorConstraint);
TOutput.SetGenericParameterAttributes( _ GenericParameterAttributes.ReferenceTypeConstraint Or _ GenericParameterAttributes.DefaultConstructorConstraint)
Fügen Sie optional Klassen- und Schnittstelleneinschränkungen zu den Typparametern hinzu. In diesem Beispiel ist der Typparameter
TOutput
auf Typen beschränkt, die dieICollection(Of TInput)
Schnittstelle (ICollection<TInput>
in C#) implementieren. Dadurch wird sichergestellt, dass die Add Methode zum Hinzufügen von Elementen verwendet werden kann.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)
Definieren Sie die formalen Parameter der Methode mithilfe der SetParameters Methode. In diesem Beispiel verfügt die
Factory
Methode über einen Parameter, ein Array vonTInput
. Dieser Typ wird durch Aufrufen der Methode MakeArrayType an dem GenericTypeParameterBuilder erstellt, dasTInput
darstellt. Das Argument von SetParameters ist ein Array von Type Objekten.Type[] parms = {TInput.MakeArrayType()}; factory.SetParameters(parms);
Dim params() As Type = {TInput.MakeArrayType()} factory.SetParameters(params)
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)
Geben Sie den Methodentext mithilfe von ILGenerator aus. Weitere Einzelheiten hierzu finden Sie im zugehörigen Verfahren zum Ausgeben des Methodentexts.
Von Bedeutung
Wenn Sie Aufrufe von Methoden generischer Typen ausgeben und die Typargumente dieser Typen Typparameter der generischen Methode sind, müssen Sie die
static
GetConstructor(Type, ConstructorInfo), GetMethod(Type, MethodInfo), und GetField(Type, FieldInfo) Methodenüberladungen der TypeBuilder Klasse verwenden, um konstruierte Methodenformen zu erhalten. Dies wird im zugehörigen Verfahren zum Ausgeben von Methodentext veranschaulicht.Vervollständigen Sie den Typ, der die Methode enthält, und speichern Sie die Assembly-Datei. Das begleitende Verfahren zum Aufrufen der generischen Methode zeigt zwei Möglichkeiten zum Aufrufen der abgeschlossenen Methode.
// 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")
Ausgeben des Methodentexts
Rufen Sie einen Codegenerator ab, und deklarieren Sie lokale Variablen und Bezeichnungen. Die DeclareLocal Methode wird verwendet, um lokale Variablen zu deklarieren. Die
Factory
-Methode verfügt über vier lokale Variablen:retVal
, um das neueTOutput
zu halten, das von der Methode zurückgegeben wird;ic
, um dasTOutput
zu halten, wenn es inICollection<TInput>
umgewandelt wird;input
, um das Eingabearray vonTInput
-Objekten zu halten; undindex
, um durch das Array zu iterieren. Die Methode verfügt auch über zwei Beschriftungen, eine zum Eingeben der Schleife (enterLoop
) und eine für den oberen Rand der Schleife (loopAgain
), die mithilfe der DefineLabel Methode definiert ist.Zuerst lädt die Methode ihr Argument mithilfe von Ldarg_0 Opcode und speichert es mithilfe von
input
Opcode in der lokalen Variablen 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)
Geben Sie Code aus, um mithilfe der generischen Methodenüberladung der
TOutput
-Methode eine Instanz von Activator.CreateInstance zu erstellen. Die Verwendung dieser Überladung erfordert, dass der angegebene Typ über einen parameterlosen Konstruktor verfügt. Dies ist der Grund für das Hinzufügen dieser Einschränkung zuTOutput
. Erstellen Sie die konstruierte generische Methode durch ÜbergebenTOutput
an MakeGenericMethod. Geben Sie nach der Ausgabe von Code für den Methodenaufruf mithilfe vonretVal
Code zum Speichern in der lokalen Variable Stloc_S aus.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)
Geben Sie Code aus, um das neue
TOutput
-Objekt inICollection(Of TInput)
umzuformen und es in der lokalen Variablenic
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)
Rufen Sie eine MethodInfo ab, die die ICollection<T>.Add-Methode darstellt. Da diese Methode
ICollection<TInput>
behandelt, muss die für diesen konstruierten Typ spezifischeAdd
-Methode abgerufen werden. Sie können die GetMethod Methode nicht verwenden, um MethodInfo direkt vonicollOfTInput
abzurufen, da GetMethod für einen Typ, der mit einem GenericTypeParameterBuilder konstruiert wurde, nicht unterstützt wird. Rufen Sie stattdessen GetMethod füricoll
auf, der die Definition des generischen Typs für die generische ICollection<T>-Schnittstelle enthält. Verwenden Sie dann die Methode GetMethod(Type, MethodInfo)static
, um den Typ MethodInfo für die Konstruktion zu erzeugen. Der folgende Code veranschaulicht dies.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)
Geben Sie Code aus, um die
index
Variable zu initialisieren, indem Sie eine 32-Bit-Ganzzahl 0 laden und in der Variablen speichern. Geben Sie Code aus, um zur BezeichnungenterLoop
zu verzweigen. Dieses Label wurde noch nicht markiert, weil es innerhalb der Schleife ist. 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)
Geben Sie Code für die Schleife aus. Der erste Schritt besteht darin, den Anfang der Schleife zu markieren, indem MarkLabel mit dem
loopAgain
Label aufgerufen wird. Verzweigungsanweisungen, die die Bezeichnung verwenden, verzweigen jetzt zu diesem Punkt im Code. Anschließend wird das inTOutput
umgewandelteICollection(Of TInput)
-Objekt auf dem Stapel abgelegt. Es ist nicht sofort erforderlich, muss aber bereit sein für den Aufruf derAdd
Methode. Als Nächstes wird das Eingabearray auf den Stapel verschoben, dann dieindex
Variable, die den aktuellen Index enthält, in das Array. Der Ldelem-Opcode nimmt den Index und das Array vom Stapel auf und legt das indizierte Arrayelement auf dem Stapel ab. Der Stapel ist nun für den Aufruf der ICollection<T>.Add Methode bereit, welche die Sammlung und das neue Element vom Stapel entfernt und das Element zur Sammlung hinzufügt.Der rest des Codes in der Schleife erhöht den Index und überprüft, ob die Schleife abgeschlossen ist: Der Index und eine 32-Bit-Ganzzahl 1 werden auf den Stapel verschoben und hinzugefügt, wobei die Summe im Stapel bleibt; die Summe wird gespeichert in
index
. 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. Der Index und die Länge befinden sich jetzt im Stack, 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)
Geben Sie Code aus, um das
TOutput
-Objekt auf dem Stapel abzulegen und die Methode zu beenden. Die lokalen VariablenretVal
undic
beide enthalten Verweise auf das neueTOutput
;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)
Generische Methode aufrufen
Factory
ist eine generische Methodendefinition. Um sie aufzurufen, müssen Sie ihren generischen Typparametern Typen zuweisen. Verwenden Sie dazu die MakeGenericMethod Methode. Im folgenden Code wird eine konstruierte generische Methode erstellt. Dabei wird String fürTInput
undList(Of String)
(List<string>
in C#) fürTOutput
angegeben, und es wird eine Zeichenfolgendarstellung der Methode angezeigt.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)
Um die Methode spät gebunden aufzurufen, verwenden Sie die Invoke Methode. Der folgende Code erstellt ein Array von Object, das als einziges Element ein Array von Zeichenfolgen enthält, und übergibt es als Argumentliste für die generische Methode. Der erste Parameter von Invoke ist ein NULL-Verweis, da die Methode
static
ist. Der Rückgabewert wird in den TypList(Of String)
umgewandelt, und dessen erstes Element wird angezeigt.object o = bound.Invoke(null, new object[]{arr}); List<string> list2 = (List<string>) o; Console.WriteLine($"The first element is: {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))
Um die Methode mithilfe eines Delegaten aufzurufen, müssen Sie über einen Delegaten verfügen, der der Signatur der konstruierten generischen Methode entspricht. Eine einfache Möglichkeit, dies zu tun, ist das Erstellen eines generischen Delegaten. Der folgende Code erstellt eine Instanz des generischen Delegaten
D
, der im Beispielcode definiert ist, mithilfe der Delegate.CreateDelegate(Type, MethodInfo) Methodenüberladung und ruft den Delegaten auf. Delegaten bieten eine bessere Leistung als spät gebundene Aufrufe.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: {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))
Die ausgegebene Methode kann auch von einem Programm aufgerufen werden, das auf die gespeicherte Assembly verweist.
Beispiel
Im folgenden Codebeispiel wird ein nichtgenerischer Typ DemoType
mit einer generischen Methode Factory
erstellt. Diese Methode verfügt über zwei generische Typparameter, TInput
um einen Eingabetyp anzugeben und TOutput
einen Ausgabetyp anzugeben. Der TOutput
Typparameter ist auf die Implementierung ICollection<TInput>
(ICollection(Of TInput)
in Visual Basic) beschränkt, um ein Verweistyp zu sein und über einen parameterlosen Konstruktor zu verfügen.
Die Methode verfügt über einen formalen Parameter, bei dem es sich um ein Array von TInput
. Die Methode gibt eine Instanz von TOutput
zurück, die alle Elemente des Eingabearrays enthält.
TOutput
kann ein beliebiger generischer Sammlungstyp sein, der die ICollection<T> generische Schnittstelle implementiert.
Wenn der Code ausgeführt wird, wird die dynamische Assembly als DemoGenericMethod1.dllgespeichert und kann mithilfe des Ildasm.exe (IL Disassembler) untersucht werden.
Hinweis
Eine gute Möglichkeit zum Ausgeben von Code ist das Schreiben eines Programms, das die Aufgabe ausführt, die Sie ausgeben möchten, und den Disassembler verwenden, um die vom Compiler erzeugte CIL 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.
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: {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: {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: {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