Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
První postup 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 generovat tělo metody a jak použít parametry typu obecné metody k vytvoření instancí obecných typů a volání jejich metod.
Třetí postup ukazuje, jak vyvolat obecnou metodu.
Důležité
Metoda není obecná pouze proto, že patří do obecného typu a používá parametry typu daného typu. Metoda je obecná pouze v případě, že má vlastní seznam parametrů typu. Obecná metoda se může objevit v negenerickém typu, jako v tomto příkladu. Příklad neobecné metody obecného typu najdete v tématu Postupy: Definování obecného typu pomocí emitování reflexe.
Definování obecné metody
Než začnete, je užitečné se podívat, jak se obecná metoda zobrazuje při psaní 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,
TInput
aTOutput
, druhý z nichž musí být referenční typ (class
), musí mít konstruktor bez parametrů (new
) a musí implementovatICollection<TInput>
. Toto omezení rozhraní zajišťuje, že ICollection<T>.Add metodu lze použít k přidání prvků doTOutput
kolekce, kterou metoda vytvoří. Metoda má jeden formální parametr,input
, což je poleTInput
. Metoda vytvoří kolekci typuTOutput
a zkopíruje prvkyinput
do 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
Definujte dynamické sestavení a dynamický modul, který bude obsahovat typ, do které obecná metoda patří. V tomto případě má sestavení pouze jeden modul s názvem
DemoMethodBuilder1
a název modulu je stejný jako název sestavení a rozšíření. V tomto příkladu se sestavení uloží na disk a poté se také spustí, takže je zadán 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")
Definujte typ, do které obecná metoda patří. Typ nemusí být obecný. Obecná metoda může patřit obecnému nebo negenerickému typu. V tomto příkladu je 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)
Definujte obecnou metodu. Pokud jsou typy formálních parametrů obecné metody určeny obecnými parametry typu obecné metody, použijte k definování metody přetížení metody DefineMethod(String, MethodAttributes). Parametry obecného typu metody ještě nejsou definovány, takže nelze určit typy formálních parametrů metody ve volání DefineMethod. V tomto příkladu je metoda pojmenována
Factory
. Metoda je veřejná astatic
(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)
Definujte parametry obecného typu
DemoMethod
předáním pole řetězců obsahující názvy parametrů do MethodBuilder.DefineGenericParameters metody. Tím se metoda změní na obecnou metodu. Následující kód dělá zFactory
obecnou metodu s parametry typuTInput
aTOutput
. Aby se kód snadněji četl, vytvoří se proměnné s těmito názvy, které budou obsahovat GenericTypeParameterBuilder objekty představující 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)
Volitelně můžete do parametrů typu přidat zvláštní omezení. Speciální omezení se přidají pomocí metody SetGenericParameterAttributes. V tomto příkladu je
TOutput
omezeno na to, že musí být referenčního typu a musí mít konstruktor bez parametrů.TOutput.SetGenericParameterAttributes( GenericParameterAttributes.ReferenceTypeConstraint | GenericParameterAttributes.DefaultConstructorConstraint);
TOutput.SetGenericParameterAttributes( _ GenericParameterAttributes.ReferenceTypeConstraint Or _ GenericParameterAttributes.DefaultConstructorConstraint)
Volitelně můžete do parametrů typu přidat omezení třídy a rozhraní. V tomto příkladu je parametr typu
TOutput
omezen na typy, které implementují rozhraníICollection(Of TInput)
(ICollection<TInput>
v jazyce C#). Tím zajistíte, že Add metodu 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)
Definujte formální parametry metody pomocí metody SetParameters. V tomto příkladu má metoda
Factory
jeden parametr, poleTInput
. Tento typ je vytvořen voláním metody MakeArrayType na GenericTypeParameterBuilder, které představujeTInput
. Argumentem SetParameters je pole Type objektů.Type[] parms = {TInput.MakeArrayType()}; factory.SetParameters(parms);
Dim params() As Type = {TInput.MakeArrayType()} factory.SetParameters(params)
Definujte návratový typ metody pomocí metody SetReturnType. V tomto příkladu se vrátí instance
TOutput
.factory.SetReturnType(TOutput);
factory.SetReturnType(TOutput)
Vygenerujte tělo metody pomocí ILGenerator. Podrobnosti najdete v přiloženém postupu pro generování těla metody.
Důležité
Při generování volání metod obecných typů, kdy argumenty těchto typů jsou parametry typu obecné metody, musíte použít přetížení metod
static
GetConstructor(Type, ConstructorInfo), GetMethod(Type, MethodInfo)a GetField(Type, FieldInfo) třídy TypeBuilder, abyste získali vytvořené formy metod. Tento postup ukazuje, jak emitovat tělo metody.Dokončete typ, který obsahuje metodu, a uložte sestavení. Doprovodný postup pro vyvolání obecné metody ukazuje dva způsoby vyvolá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
Získejte generátor kódu a deklarujte místní proměnné a popisky. Metoda DeclareLocal slouží k deklaraci místních proměnných. Metoda
Factory
má čtyři místní proměnné:retVal
k uložení novéhoTOutput
vráceného metodou,ic
k uloženíTOutput
při přetypování naICollection<TInput>
,input
pro uložení vstupního poleTInput
objektů aindex
iterace polem. Metoda má také dva popisky, jeden pro zadání smyčky (enterLoop
) a jeden pro začátek smyčky (loopAgain
), definovaný pomocí DefineLabel metody.První věcí, kterou metoda dělá, je načíst svůj argument pomocí Ldarg_0 opcode a uložit ho do místní proměnné
input
pomocí Stloc_S opcode.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)
Vygenerujte kód pro vytvoření instance
TOutput
pomocí obecného přetížení metody Activator.CreateInstance. Použití tohoto přetížení vyžaduje, aby zadaný typ měl konstruktor bez parametrů, což je důvod pro přidání tohoto omezení naTOutput
. Vytvořte konstruovanou obecnou metodu předánímTOutput
do MakeGenericMethod. Po vygenerování kódu pro volání metody vygenerujte kód, který ho uloží do místní proměnnéretVal
pomocí Stloc_SMethodInfo 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)
Vygenerujte kód pro přetypování nového objektu
TOutput
naICollection(Of TInput)
a uložte ho 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)
Získejte MethodInfo, která představuje metodu ICollection<T>.Add. Jelikož se metoda používá na
ICollection<TInput>
, je nutné získat metoduAdd
specifickou pro tento vytvořený typ. K získání tohoto GetMethod přímo z MethodInfonelze použít metoduicollOfTInput
, protože GetMethod není podporováno u typu vytvořeného pomocí GenericTypeParameterBuilder. Místo toho volejte GetMethod naicoll
, které obsahuje definici obecného typu pro obecné rozhraní ICollection<T>. Pak pomocí GetMethod(Type, MethodInfo)static
metody vytvořte MethodInfo pro konstruovaný typ. Následující kód to demonstruje.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)
Vygenerujte kód pro inicializaci proměnné
index
načtením 32bitového celého čísla 0 a jeho uložením do proměnné. Vytvořte kód pro přesměrování na štítekenterLoop
. Tento štítek ještě nebyl označen, protože je uvnitř smyčky. Kód smyčky se v dalším kroku vygeneruje.// 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)
Vygenerujte kód smyčky. Prvním krokem je označení horní části smyčky voláním MarkLabel s popiskem
loopAgain
. Příkazy větve, které používají štítek, nyní skočí na tento bod v kódu. Dalším krokem je přetypování objektuTOutput
naICollection(Of TInput)
a jeho vložení do zásobníku. Není potřeba okamžitě, ale musí být v pozici pro volání metodyAdd
. Dále se vstupní pole vloží do zásobníku a pak proměnnáindex
obsahující aktuální index v poli. Ldelem opcode odstraní index a pole ze zásobníku a vloží na zásobník indexovaný prvek pole. Zásobník je teď připravený na volání metody ICollection<T>.Add, která vyvolá 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 sečtou se, takže součet zůstane na zásobníku; součet je uložen v
index
. MarkLabel je volána k nastavení tohoto bodu jako vstupního bodu smyčky. Index se znovu načte. Vstupní pole se nasdílí do zásobníku a Ldlen se vygeneruje, aby se získala jeho délka. Index a délka jsou teď v zásobníku a Clt se vygeneruje pro jejich porovnání. Pokud je index menší než délka, Brtrue_S se vrá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)
Vygenerovat kód pro umístění objektu
TOutput
na zásobník a návratu z metody. Místní proměnnéretVal
aic
oba obsahují odkazy na novouTOutput
;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
Factory
je definice obecné metody. Chcete-li jej vyvolat, je nutné přiřadit typy k jeho obecným parametrům typu. K tomu použijte metodu MakeGenericMethod. Následující kód vytvoří konstruovanou obecnou metodu, která určuje String proTInput
aList(Of String)
(List<string>
v jazyce C#) proTOutput
a zobrazí řetězcovou reprezentaci metody.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)
Chcete-li vyvolat metodu s pozdní vazbou, použijte metodu Invoke. Následující kód vytvoří pole Object, který obsahuje jako jediný prvek pole řetězců a předá ho jako seznam argumentů pro obecnou metodu. První parametr Invoke je nulový odkaz, protože metoda je
static
. Vrácená hodnota je přetypována naList(Of String)
a její první prvek se zobrazí.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))
Chcete-li vyvolat metodu pomocí delegáta, musíte mít delegáta, který odpovídá podpisu vytvořené obecné metody. Snadným způsobem, jak to udělat, je vytvořit obecného delegáta. Následující kód vytvoří instanci obecného delegáta
D
definované v ukázkovém kódu pomocí přetížení metody Delegate.CreateDelegate(Type, MethodInfo) a vyvolá delegáta. Delegáti pracují lépe než opožděná volání.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))
Emitovanou metodu lze také volat z programu, který odkazuje na uložené sestavení.
Příklad
Následující příklad kódu vytvoří negenerický typ, DemoType
, s obecnou metodou Factory
. Tato metoda má dva parametry obecného typu, TInput
zadat vstupní typ a TOutput
zadat výstupní typ. Parametr typu TOutput
je omezen na implementaci ICollection<TInput>
(ICollection(Of TInput)
v jazyce Visual Basic), aby byl referenčním typem a aby měl konstruktor bez parametrů.
Metoda má jeden formální parametr, což je pole TInput
. Metoda vrátí instanci TOutput
, která obsahuje všechny prvky vstupního pole.
TOutput
může být libovolný obecný typ kolekce, který implementuje ICollection<T> obecné rozhraní.
Při spuštění kódu se dynamické sestavení uloží jako DemoGenericMethod1.dlla lze jej 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ě. Emitovaná metoda je vyvolána způsobem pozdní vazby a také pomocí obecného zástupce 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: {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