如何:使用反映發出定義泛型方法
第一個程序示範如何建立有兩個類型參數的簡單泛型方法,以及如何將類別條件約束、介面條件約束和特殊條件約束套用至類型參數。
第二個程序示範如何發出方法主體,以及如何使用泛型方法的類型參數建立泛型型別的執行個體並呼叫其方法。
第三個程序示範如何叫用泛型方法。
重要
方法不是只因為屬於泛型型別並使用該類型的型別參數,而成為泛型。 方法只有在有自己的類型參數清單時,才會是泛型。 泛型方法可以出現在非泛型型別中,如本例所示。 如需泛型型別的非泛型方法範例,請參閱如何︰使用反映發出定義泛型型別。
定義泛型方法
開始之前,最好先查看使用高階語言撰寫時的泛型方法顯示方式。 下列程式代碼包含在本文的範例程式代碼中,以及呼叫泛型方法的程序代碼。 方法有兩個型別參數,
TInput
第TOutput
二個參數必須是參考型別 (class
)、必須有無參數建構函式 (new
),而且必須實作ICollection<TInput>
。 此介面條件約束確保 ICollection<T>.Add 方法可用來將項目新增至該方法所建立的TOutput
集合。 方法具有一個型式參數input
,這是一個TInput
陣列。 此方法會建立類型TOutput
的集合,並將input
的項目複製至集合。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
定義動態組件和動態模組,以包含泛型方法所屬的類型。 在此情況下,組件僅有一個模組,名為
DemoMethodBuilder1
,而且模組名稱是組件名稱加上副檔名。 在本例中,組件儲存到磁碟並執行,因此指定了 AssemblyBuilderAccess.RunAndSave。 您可以使用 Ildasm.exe (IL 反組譯器) 來檢查DemoMethodBuilder1.dll,並將其與步驟 1 所示方法的通用中繼語言 (CIL) 進行比較。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")
定義泛型方法所屬的類型。 類型不必是泛型。 泛型方法可以屬於泛型或非泛型型別。 在本例中,類型是類別、不是泛型,且名為
DemoType
。TypeBuilder demoType = demoModule.DefineType("DemoType", TypeAttributes.Public);
Dim demoType As TypeBuilder = demoModule.DefineType( _ "DemoType", _ TypeAttributes.Public)
定義泛型方法。 如果泛型方法的型式參數類型是由泛型方法的泛型型別參數所指定,請使用 DefineMethod(String, MethodAttributes) 方法多載來定義方法。 方法的泛型型別參數尚未定義,因此您無法在對 DefineMethod 的呼叫中指定方法的型式參數類型。 在本例中,方法名為
Factory
。 方法是公用且static
(Visual Basic 為Shared
)。MethodBuilder factory = demoType.DefineMethod("Factory", MethodAttributes.Public | MethodAttributes.Static);
Dim factory As MethodBuilder = _ demoType.DefineMethod("Factory", _ MethodAttributes.Public Or MethodAttributes.Static)
將包含參數名稱的字串陣列傳遞至 MethodBuilder.DefineGenericParameters 方法,以定義
DemoMethod
的泛型型別參數。 這會讓方法成為泛型方法。 下列程式碼使Factory
成為具有類型參數TInput
和TOutput
的泛型方法。 為更方便閱讀程式碼,會建立有這些名稱的變數以保留代表兩個類型參數的 GenericTypeParameterBuilder 物件。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)
或者將特殊條件約束新增至類型參數。 使用 SetGenericParameterAttributes 方法新增特殊條件約束。 在本例中,
TOutput
受限為參考型別,且具有無參數建構函式。TOutput.SetGenericParameterAttributes( GenericParameterAttributes.ReferenceTypeConstraint | GenericParameterAttributes.DefaultConstructorConstraint);
TOutput.SetGenericParameterAttributes( _ GenericParameterAttributes.ReferenceTypeConstraint Or _ GenericParameterAttributes.DefaultConstructorConstraint)
或者將類別和介面條件約束新增至類型參數。 在本例中,類型參數
TOutput
受限為實作ICollection(Of TInput)
(C# 為ICollection<TInput>
) 介面的類型。 這會確保 Add 方法可用來新增項目。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)
使用 SetParameters 方法定義方法的型式參數。 在本例中,
Factory
方法有一個參數,是TInput
的陣列。 此類型是透過對代表TInput
的 GenericTypeParameterBuilder 呼叫 MakeArrayType 方法所建立。 SetParameters 的引數是 Type 物件的陣列。Type[] parms = {TInput.MakeArrayType()}; factory.SetParameters(parms);
Dim params() As Type = {TInput.MakeArrayType()} factory.SetParameters(params)
使用 SetReturnType 方法定義方法的傳回類型。 在本例中,會傳回
TOutput
的執行個體。factory.SetReturnType(TOutput);
factory.SetReturnType(TOutput)
使用 ILGenerator 發出方法主體。 如需詳細資訊,請參閱發出方法主體所伴隨的程序。
重要
當您向泛型型別的方法發出呼叫,而這些類型的類型引數是泛型方法的類型參數時,您必須使用 TypeBuilder 類別的
static
GetConstructor(Type, ConstructorInfo)、GetMethod(Type, MethodInfo) 和 GetField(Type, FieldInfo) 方法多載取得方法的建構形式。 發出方法主體的伴隨程序會示範此作業。完成包含方法的類型,並儲存組件。 叫用泛型方法伴隨的程序將示範叫用此完成的方法的兩種方式。
// 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")
發出方法主體
取得程式碼產生器,並宣告區域變數和標籤。 宣告區域變數是使用 DeclareLocal 方法。 方法
Factory
有四個局部變數:retVal
若要保存 方法傳回的新TOutput
變數,ic
在轉換成ICollection<TInput>
input
時保留 ,以保存TOutput
對象的輸入陣列TInput
,以及index
逐一查看數位。 此方法也有兩個標籤,一個是進入迴圈 (enterLoop
),另一個用於迴圈的頂端 (loopAgain
),使用 DefineLabel 方法所定義。此方法做的第一件事是使用 Ldarg_0 作業碼載入其引數,並使用 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);
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)
使用 Activator.CreateInstance 方法的泛型方法多載,發出程式碼以建立
TOutput
的執行個體。 指定的類型需要有無參數建構函式才能使用此多載,這是在TOutput
新增該條件約束的原因。 將TOutput
傳遞到 MakeGenericMethod,以建立建構的泛型方法。 發出程式碼以呼叫方法後,再使用 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);
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)
發出程式碼將新的
TOutput
物件投射成ICollection(Of TInput)
,並將它儲存在區域變數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)
取得代表 ICollection<T>.Add 方法的 MethodInfo。 方法在 上
ICollection<TInput>
作用,因此必須取得Add
該建構型別特有的方法。 您不能使用 GetMethod 方法直接從icollOfTInput
取得此 MethodInfo,因為已使用 GenericTypeParameterBuilder 建構的類型不支援 GetMethod。 請改呼叫icoll
的 GetMethod,它包含 ICollection<T> 泛型介面的泛型型別定義。 然後使用 GetMethod(Type, MethodInfo)static
方法產生建構類型的 MethodInfo。 下列程式碼可示範這項處理。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)
載入 32 位元的整數 0 並將其儲存在變數中,以發出程式碼來初始化
index
變數。 發出程式碼以分支到標籤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);
' 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)
發出此迴圈的程式碼。 第一個步驟是呼叫有
loopAgain
標籤的 MarkLabel,來標記迴圈頂端。 使用標籤的分支陳述式現在會分支到程式碼的這個點。 下個步驟推送TOutput
物件、投射到ICollection(Of TInput)
,再到堆疊。 它不是立即需要,但要就位以待呼叫Add
方法。 接下來,將輸入陣列推送至堆疊,再將包含目前索引的index
變數推送至陣列。 Ldelem 作業碼會從堆疊取出索引和陣列,並將索引的陣列項目推送至堆疊。 堆疊已就緒可呼叫 ICollection<T>.Add 方法,它會從堆疊取出集合和新的項目,並將項目新增至集合。迴圈中其餘的程式碼會遞增索引,並測試看迴圈是否已結束︰索引和 32 位元整數 1 會推送到堆疊並相加,將總和留在堆疊上,總和會儲存在
index
中。 呼叫 MarkLabel 以將此點設定為迴圈的進入點。 再次載入索引。 輸入陣列會推送到堆疊,並發出 Ldlen 取得其長度。 索引和長度現在都在堆疊上,而 Clt 會發出以比較兩者。 如果索引小於長度,Brtrue_S 就會分支回到迴圈的開頭。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)
發出程式碼將
TOutput
物件推送至堆疊,並從方法傳回。 區域變數retVal
和ic
同時包含新TOutput
的參考;ic
僅用於存取 ICollection<T>.Add 方法。ilgen.Emit(OpCodes.Ldloc_S, retVal); ilgen.Emit(OpCodes.Ret);
ilgen.Emit(OpCodes.Ldloc_S, retVal) ilgen.Emit(OpCodes.Ret)
叫用泛型方法
Factory
是泛型方法定義。 為叫用它,您必須將類型指派給其泛型型別參數。 請使用 MakeGenericMethod 方法完成此作業。 下列程式碼會建立建構泛型方法,為TInput
指定 String,為TOutput
指定List(Of String)
(C# 為List<string>
),並顯示代表方法的字串。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)
若要叫用晚期繫結方法,請使用 Invoke 方法。 下列程式碼會建立 Object 的陣列,將其唯一項目包含為字串陣列,並將其當成泛型方法的引數清單傳遞。 Invoke 的第一個參數為 Null 參考,因為方法是
static
。 傳回值會投射到List(Of String)
,並顯示其第一個項目。object o = bound.Invoke(null, new object[]{arr}); List<string> list2 = (List<string>) o; Console.WriteLine("The first element is: {0}", list2[0]);
Dim o As Object = bound.Invoke(Nothing, New Object() {arr}) Dim list2 As List(Of String) = CType(o, List(Of String)) Console.WriteLine("The first element is: {0}", list2(0))
若要使用委派叫用方法,您必須有符合建構泛型方法簽章的委派。 執行此作業的簡單方法是建立泛型委派。 下列程式碼會使用 Delegate.CreateDelegate(Type, MethodInfo) 方法多載建立在範例程式碼中定義之泛型委派
D
的執行個體,並叫用委派。 委派執行優於晚期繫結呼叫。Type dType = typeof(D<string, List <string>>); D<string, List <string>> test; test = (D<string, List <string>>) Delegate.CreateDelegate(dType, bound); List<string> list3 = test(arr); Console.WriteLine("The first element is: {0}", list3[0]);
Dim dType As Type = GetType(D(Of String, List(Of String))) Dim test As D(Of String, List(Of String)) test = CType( _ [Delegate].CreateDelegate(dType, bound), _ D(Of String, List(Of String))) Dim list3 As List(Of String) = test(arr) Console.WriteLine("The first element is: {0}", list3(0))
您也可以從指向已儲存組件的程式呼叫發出的方法。
範例
下列程式碼範例會建立具有泛型方法 Factory
的非泛型型別 DemoType
。 此方法有兩個泛型型別參數:TInput
指定輸入類型,而 TOutput
指定輸出類型。 TOutput
類型參數受限實作 ICollection<TInput>
(Visual Basic 為 ICollection(Of TInput)
)、為參考型別,且具有無參數建構函式。
方法具有一個型式參數,這是一個 TInput
陣列。 此方法會傳回 TOutput
的執行個體,包含該輸入陣列的所有項目。 TOutput
可以是任何實作 ICollection<T> 泛型介面的泛型集合類型。
執行程式碼時,動態組件會儲存為 DemoGenericMethod1.dll,以及可以使用檢查Ildasm.exe (IL 反組譯工具)。
注意
瞭解如何發出程式代碼的好方法是撰寫程式,以執行您嘗試發出的工作,並使用反組譯程式檢查編譯程式所產生的 CIL。
程式代碼範例包含相當於所發出方法的原始程式碼。 發出的方法會叫用晚期繫結,也使用泛型委派宣告在程式碼範例。
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
// Declare a generic delegate that can be used to execute the
// finished method.
//
public delegate TOut D<TIn, TOut>(TIn[] input);
class GenericMethodBuilder
{
// This method shows how to declare, in Visual Basic, the generic
// method this program emits. The method has two type parameters,
// TInput and TOutput, the second of which must be a reference type
// (class), must have a parameterless constructor (new()), and must
// implement ICollection<TInput>. This interface constraint
// ensures that ICollection<TInput>.Add can be used to add
// elements to the TOutput object the method creates. The method
// has one formal parameter, input, which is an array of TInput.
// The elements of this array are copied to the new TOutput.
//
public static TOutput Factory<TInput, TOutput>(TInput[] tarray)
where TOutput : class, ICollection<TInput>, new()
{
TOutput ret = new TOutput();
ICollection<TInput> ic = ret;
foreach (TInput t in tarray)
{
ic.Add(t);
}
return ret;
}
public static void Main()
{
// The following shows the usage syntax of the C#
// version of the generic method emitted by this program.
// Note that the generic parameters must be specified
// explicitly, because the compiler does not have enough
// context to infer the type of TOutput. In this case, TOutput
// is a generic List containing strings.
//
string[] arr = {"a", "b", "c", "d", "e"};
List<string> list1 =
GenericMethodBuilder.Factory<string, List <string>>(arr);
Console.WriteLine("The first element is: {0}", list1[0]);
// Creating a dynamic assembly requires an AssemblyName
// object, and the current application domain.
//
AssemblyName asmName = new AssemblyName("DemoMethodBuilder1");
AppDomain domain = AppDomain.CurrentDomain;
AssemblyBuilder demoAssembly =
domain.DefineDynamicAssembly(asmName,
AssemblyBuilderAccess.RunAndSave);
// Define the module that contains the code. For an
// assembly with one module, the module name is the
// assembly name plus a file extension.
ModuleBuilder demoModule =
demoAssembly.DefineDynamicModule(asmName.Name,
asmName.Name+".dll");
// Define a type to contain the method.
TypeBuilder demoType =
demoModule.DefineType("DemoType", TypeAttributes.Public);
// Define a public static method with standard calling
// conventions. Do not specify the parameter types or the
// return type, because type parameters will be used for
// those types, and the type parameters have not been
// defined yet.
//
MethodBuilder factory =
demoType.DefineMethod("Factory",
MethodAttributes.Public | MethodAttributes.Static);
// Defining generic type parameters for the method makes it a
// generic method. To make the code easier to read, each
// type parameter is copied to a variable of the same name.
//
string[] typeParameterNames = {"TInput", "TOutput"};
GenericTypeParameterBuilder[] typeParameters =
factory.DefineGenericParameters(typeParameterNames);
GenericTypeParameterBuilder TInput = typeParameters[0];
GenericTypeParameterBuilder TOutput = typeParameters[1];
// Add special constraints.
// The type parameter TOutput is constrained to be a reference
// type, and to have a parameterless constructor. This ensures
// that the Factory method can create the collection type.
//
TOutput.SetGenericParameterAttributes(
GenericParameterAttributes.ReferenceTypeConstraint |
GenericParameterAttributes.DefaultConstructorConstraint);
// Add interface and base type constraints.
// The type parameter TOutput is constrained to types that
// implement the ICollection<T> interface, to ensure that
// they have an Add method that can be used to add elements.
//
// To create the constraint, first use MakeGenericType to bind
// the type parameter TInput to the ICollection<T> interface,
// returning the type ICollection<TInput>, then pass
// the newly created type to the SetInterfaceConstraints
// method. The constraints must be passed as an array, even if
// there is only one interface.
//
Type icoll = typeof(ICollection<>);
Type icollOfTInput = icoll.MakeGenericType(TInput);
Type[] constraints = {icollOfTInput};
TOutput.SetInterfaceConstraints(constraints);
// Set parameter types for the method. The method takes
// one parameter, an array of type TInput.
Type[] parms = {TInput.MakeArrayType()};
factory.SetParameters(parms);
// Set the return type for the method. The return type is
// the generic type parameter TOutput.
factory.SetReturnType(TOutput);
// Generate a code body for the method.
// -----------------------------------
// Get a code generator and declare local variables and
// labels. Save the input array to a local variable.
//
ILGenerator ilgen = factory.GetILGenerator();
LocalBuilder retVal = ilgen.DeclareLocal(TOutput);
LocalBuilder ic = ilgen.DeclareLocal(icollOfTInput);
LocalBuilder input = ilgen.DeclareLocal(TInput.MakeArrayType());
LocalBuilder index = ilgen.DeclareLocal(typeof(int));
Label enterLoop = ilgen.DefineLabel();
Label loopAgain = ilgen.DefineLabel();
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Stloc_S, input);
// Create an instance of TOutput, using the generic method
// overload of the Activator.CreateInstance method.
// Using this overload requires the specified type to have
// a parameterless constructor, which is the reason for adding
// that constraint to TOutput. Create the constructed generic
// method by passing TOutput to MakeGenericMethod. After
// emitting code to call the method, emit code to store the
// new TOutput in a local variable.
//
MethodInfo createInst =
typeof(Activator).GetMethod("CreateInstance", Type.EmptyTypes);
MethodInfo createInstOfTOutput =
createInst.MakeGenericMethod(TOutput);
ilgen.Emit(OpCodes.Call, createInstOfTOutput);
ilgen.Emit(OpCodes.Stloc_S, retVal);
// Load the reference to the TOutput object, cast it to
// ICollection<TInput>, and save it.
//
ilgen.Emit(OpCodes.Ldloc_S, retVal);
ilgen.Emit(OpCodes.Box, TOutput);
ilgen.Emit(OpCodes.Castclass, icollOfTInput);
ilgen.Emit(OpCodes.Stloc_S, ic);
// Loop through the array, adding each element to the new
// instance of TOutput. Note that in order to get a MethodInfo
// for ICollection<TInput>.Add, it is necessary to first
// get the Add method for the generic type defintion,
// ICollection<T>.Add. This is because it is not possible
// to call GetMethod on icollOfTInput. The static overload of
// TypeBuilder.GetMethod produces the correct MethodInfo for
// the constructed type.
//
MethodInfo mAddPrep = icoll.GetMethod("Add");
MethodInfo mAdd = TypeBuilder.GetMethod(icollOfTInput, mAddPrep);
// Initialize the count and enter the loop.
ilgen.Emit(OpCodes.Ldc_I4_0);
ilgen.Emit(OpCodes.Stloc_S, index);
ilgen.Emit(OpCodes.Br_S, enterLoop);
// Mark the beginning of the loop. Push the ICollection
// reference on the stack, so it will be in position for the
// call to Add. Then push the array and the index on the
// stack, get the array element, and call Add (represented
// by the MethodInfo mAdd) to add it to the collection.
//
// The other ten instructions just increment the index
// and test for the end of the loop. Note the MarkLabel
// method, which sets the point in the code where the
// loop is entered. (See the earlier Br_S to enterLoop.)
//
ilgen.MarkLabel(loopAgain);
ilgen.Emit(OpCodes.Ldloc_S, ic);
ilgen.Emit(OpCodes.Ldloc_S, input);
ilgen.Emit(OpCodes.Ldloc_S, index);
ilgen.Emit(OpCodes.Ldelem, TInput);
ilgen.Emit(OpCodes.Callvirt, mAdd);
ilgen.Emit(OpCodes.Ldloc_S, index);
ilgen.Emit(OpCodes.Ldc_I4_1);
ilgen.Emit(OpCodes.Add);
ilgen.Emit(OpCodes.Stloc_S, index);
ilgen.MarkLabel(enterLoop);
ilgen.Emit(OpCodes.Ldloc_S, index);
ilgen.Emit(OpCodes.Ldloc_S, input);
ilgen.Emit(OpCodes.Ldlen);
ilgen.Emit(OpCodes.Conv_I4);
ilgen.Emit(OpCodes.Clt);
ilgen.Emit(OpCodes.Brtrue_S, loopAgain);
ilgen.Emit(OpCodes.Ldloc_S, retVal);
ilgen.Emit(OpCodes.Ret);
// Complete the type.
Type dt = demoType.CreateType();
// Save the assembly, so it can be examined with Ildasm.exe.
demoAssembly.Save(asmName.Name+".dll");
// To create a constructed generic method that can be
// executed, first call the GetMethod method on the completed
// type to get the generic method definition. Call MakeGenericType
// on the generic method definition to obtain the constructed
// method, passing in the type arguments. In this case, the
// constructed method has string for TInput and List<string>
// for TOutput.
//
MethodInfo m = dt.GetMethod("Factory");
MethodInfo bound =
m.MakeGenericMethod(typeof(string), typeof(List<string>));
// Display a string representing the bound method.
Console.WriteLine(bound);
// Once the generic method is constructed,
// you can invoke it and pass in an array of objects
// representing the arguments. In this case, there is only
// one element in that array, the argument 'arr'.
//
object o = bound.Invoke(null, new object[]{arr});
List<string> list2 = (List<string>) o;
Console.WriteLine("The first element is: {0}", list2[0]);
// You can get better performance from multiple calls if
// you bind the constructed method to a delegate. The
// following code uses the generic delegate D defined
// earlier.
//
Type dType = typeof(D<string, List <string>>);
D<string, List <string>> test;
test = (D<string, List <string>>)
Delegate.CreateDelegate(dType, bound);
List<string> list3 = test(arr);
Console.WriteLine("The first element is: {0}", list3[0]);
}
}
/* This code example produces the following output:
The first element is: a
System.Collections.Generic.List`1[System.String] Factory[String,List`1](System.String[])
The first element is: a
The first element is: a
*/
Imports System.Collections.Generic
Imports System.Reflection
Imports System.Reflection.Emit
' Declare a generic delegate that can be used to execute the
' finished method.
'
Delegate Function D(Of TIn, TOut)(ByVal input() As TIn) As TOut
Class GenericMethodBuilder
' This method shows how to declare, in Visual Basic, the generic
' method this program emits. The method has two type parameters,
' TInput and TOutput, the second of which must be a reference type
' (Class), must have a parameterless constructor (New), and must
' implement ICollection(Of TInput). This interface constraint
' ensures that ICollection(Of TInput).Add can be used to add
' elements to the TOutput object the method creates. The method
' has one formal parameter, input, which is an array of TInput.
' The elements of this array are copied to the new TOutput.
'
Public Shared Function Factory(Of TInput, _
TOutput As {ICollection(Of TInput), Class, New}) _
(ByVal input() As TInput) As TOutput
Dim retval As New TOutput()
Dim ic As ICollection(Of TInput) = retval
For Each t As TInput In input
ic.Add(t)
Next
Return retval
End Function
Public Shared Sub Main()
' The following shows the usage syntax of the Visual Basic
' version of the generic method emitted by this program.
' Note that the generic parameters must be specified
' explicitly, because the compiler does not have enough
' context to infer the type of TOutput. In this case, TOutput
' is a generic List containing strings.
'
Dim arr() As String = {"a", "b", "c", "d", "e"}
Dim list1 As List(Of String) = _
GenericMethodBuilder.Factory(Of String, List(Of String))(arr)
Console.WriteLine("The first element is: {0}", list1(0))
' Creating a dynamic assembly requires an AssemblyName
' object, and the current application domain.
'
Dim asmName As New AssemblyName("DemoMethodBuilder1")
Dim domain As AppDomain = AppDomain.CurrentDomain
Dim demoAssembly As AssemblyBuilder = _
domain.DefineDynamicAssembly(asmName, _
AssemblyBuilderAccess.RunAndSave)
' Define the module that contains the code. For an
' assembly with one module, the module name is the
' assembly name plus a file extension.
Dim demoModule As ModuleBuilder = _
demoAssembly.DefineDynamicModule( _
asmName.Name, _
asmName.Name & ".dll")
' Define a type to contain the method.
Dim demoType As TypeBuilder = demoModule.DefineType( _
"DemoType", _
TypeAttributes.Public)
' Define a Shared, Public method with standard calling
' conventions. Do not specify the parameter types or the
' return type, because type parameters will be used for
' those types, and the type parameters have not been
' defined yet.
'
Dim factory As MethodBuilder = _
demoType.DefineMethod("Factory", _
MethodAttributes.Public Or MethodAttributes.Static)
' Defining generic type parameters for the method makes it a
' generic method. To make the code easier to read, each
' type parameter is copied to a variable of the same name.
'
Dim typeParameterNames() As String = {"TInput", "TOutput"}
Dim typeParameters() As GenericTypeParameterBuilder = _
factory.DefineGenericParameters(typeParameterNames)
Dim TInput As GenericTypeParameterBuilder = typeParameters(0)
Dim TOutput As GenericTypeParameterBuilder = typeParameters(1)
' Add special constraints.
' The type parameter TOutput is constrained to be a reference
' type, and to have a parameterless constructor. This ensures
' that the Factory method can create the collection type.
'
TOutput.SetGenericParameterAttributes( _
GenericParameterAttributes.ReferenceTypeConstraint Or _
GenericParameterAttributes.DefaultConstructorConstraint)
' Add interface and base type constraints.
' The type parameter TOutput is constrained to types that
' implement the ICollection(Of T) interface, to ensure that
' they have an Add method that can be used to add elements.
'
' To create the constraint, first use MakeGenericType to bind
' the type parameter TInput to the ICollection(Of T) interface,
' returning the type ICollection(Of TInput), then pass
' the newly created type to the SetInterfaceConstraints
' method. The constraints must be passed as an array, even if
' there is only one interface.
'
Dim icoll As Type = GetType(ICollection(Of ))
Dim icollOfTInput As Type = icoll.MakeGenericType(TInput)
Dim constraints() As Type = {icollOfTInput}
TOutput.SetInterfaceConstraints(constraints)
' Set parameter types for the method. The method takes
' one parameter, an array of type TInput.
Dim params() As Type = {TInput.MakeArrayType()}
factory.SetParameters(params)
' Set the return type for the method. The return type is
' the generic type parameter TOutput.
factory.SetReturnType(TOutput)
' Generate a code body for the method.
' -----------------------------------
' Get a code generator and declare local variables and
' labels. Save the input array to a local variable.
'
Dim ilgen As ILGenerator = factory.GetILGenerator()
Dim retVal As LocalBuilder = ilgen.DeclareLocal(TOutput)
Dim ic As LocalBuilder = ilgen.DeclareLocal(icollOfTInput)
Dim input As LocalBuilder = _
ilgen.DeclareLocal(TInput.MakeArrayType())
Dim index As LocalBuilder = _
ilgen.DeclareLocal(GetType(Integer))
Dim enterLoop As Label = ilgen.DefineLabel()
Dim loopAgain As Label = ilgen.DefineLabel()
ilgen.Emit(OpCodes.Ldarg_0)
ilgen.Emit(OpCodes.Stloc_S, input)
' Create an instance of TOutput, using the generic method
' overload of the Activator.CreateInstance method.
' Using this overload requires the specified type to have
' a parameterless constructor, which is the reason for adding
' that constraint to TOutput. Create the constructed generic
' method by passing TOutput to MakeGenericMethod. After
' emitting code to call the method, emit code to store the
' new TOutput in a local variable.
'
Dim createInst As MethodInfo = _
GetType(Activator).GetMethod("CreateInstance", Type.EmptyTypes)
Dim createInstOfTOutput As MethodInfo = _
createInst.MakeGenericMethod(TOutput)
ilgen.Emit(OpCodes.Call, createInstOfTOutput)
ilgen.Emit(OpCodes.Stloc_S, retVal)
' Load the reference to the TOutput object, cast it to
' ICollection(Of TInput), and save it.
ilgen.Emit(OpCodes.Ldloc_S, retVal)
ilgen.Emit(OpCodes.Box, TOutput)
ilgen.Emit(OpCodes.Castclass, icollOfTInput)
ilgen.Emit(OpCodes.Stloc_S, ic)
' Loop through the array, adding each element to the new
' instance of TOutput. Note that in order to get a MethodInfo
' for ICollection(Of TInput).Add, it is necessary to first
' get the Add method for the generic type defintion,
' ICollection(Of T).Add. This is because it is not possible
' to call GetMethod on icollOfTInput. The static overload of
' TypeBuilder.GetMethod produces the correct MethodInfo for
' the constructed type.
'
Dim mAddPrep As MethodInfo = icoll.GetMethod("Add")
Dim mAdd As MethodInfo = _
TypeBuilder.GetMethod(icollOfTInput, mAddPrep)
' Initialize the count and enter the loop.
ilgen.Emit(OpCodes.Ldc_I4_0)
ilgen.Emit(OpCodes.Stloc_S, index)
ilgen.Emit(OpCodes.Br_S, enterLoop)
' Mark the beginning of the loop. Push the ICollection
' reference on the stack, so it will be in position for the
' call to Add. Then push the array and the index on the
' stack, get the array element, and call Add (represented
' by the MethodInfo mAdd) to add it to the collection.
'
' The other ten instructions just increment the index
' and test for the end of the loop. Note the MarkLabel
' method, which sets the point in the code where the
' loop is entered. (See the earlier Br_S to enterLoop.)
'
ilgen.MarkLabel(loopAgain)
ilgen.Emit(OpCodes.Ldloc_S, ic)
ilgen.Emit(OpCodes.Ldloc_S, input)
ilgen.Emit(OpCodes.Ldloc_S, index)
ilgen.Emit(OpCodes.Ldelem, TInput)
ilgen.Emit(OpCodes.Callvirt, mAdd)
ilgen.Emit(OpCodes.Ldloc_S, index)
ilgen.Emit(OpCodes.Ldc_I4_1)
ilgen.Emit(OpCodes.Add)
ilgen.Emit(OpCodes.Stloc_S, index)
ilgen.MarkLabel(enterLoop)
ilgen.Emit(OpCodes.Ldloc_S, index)
ilgen.Emit(OpCodes.Ldloc_S, input)
ilgen.Emit(OpCodes.Ldlen)
ilgen.Emit(OpCodes.Conv_I4)
ilgen.Emit(OpCodes.Clt)
ilgen.Emit(OpCodes.Brtrue_S, loopAgain)
ilgen.Emit(OpCodes.Ldloc_S, retVal)
ilgen.Emit(OpCodes.Ret)
' Complete the type.
Dim dt As Type = demoType.CreateType()
' Save the assembly, so it can be examined with Ildasm.exe.
demoAssembly.Save(asmName.Name & ".dll")
' To create a constructed generic method that can be
' executed, first call the GetMethod method on the completed
' type to get the generic method definition. Call MakeGenericType
' on the generic method definition to obtain the constructed
' method, passing in the type arguments. In this case, the
' constructed method has String for TInput and List(Of String)
' for TOutput.
'
Dim m As MethodInfo = dt.GetMethod("Factory")
Dim bound As MethodInfo = m.MakeGenericMethod( _
GetType(String), GetType(List(Of String)))
' Display a string representing the bound method.
Console.WriteLine(bound)
' Once the generic method is constructed,
' you can invoke it and pass in an array of objects
' representing the arguments. In this case, there is only
' one element in that array, the argument 'arr'.
'
Dim o As Object = bound.Invoke(Nothing, New Object() {arr})
Dim list2 As List(Of String) = CType(o, List(Of String))
Console.WriteLine("The first element is: {0}", list2(0))
' You can get better performance from multiple calls if
' you bind the constructed method to a delegate. The
' following code uses the generic delegate D defined
' earlier.
'
Dim dType As Type = GetType(D(Of String, List(Of String)))
Dim test As D(Of String, List(Of String))
test = CType( _
[Delegate].CreateDelegate(dType, bound), _
D(Of String, List(Of String)))
Dim list3 As List(Of String) = test(arr)
Console.WriteLine("The first element is: {0}", list3(0))
End Sub
End Class
' This code example produces the following output:
'
'The first element is: a
'System.Collections.Generic.List`1[System.String] Factory[String,List`1](System.String[])
'The first element is: a
'The first element is: a