最初の手順では、2 つの型パラメーターを持つ単純なジェネリック メソッドを作成する方法と、クラス制約、インターフェイス制約、および特殊な制約を型パラメーターに適用する方法を示します。
2 番目の手順では、メソッド本体を出力する方法と、ジェネリック メソッドの型パラメーターを使用してジェネリック型のインスタンスを作成し、そのメソッドを呼び出す方法を示します。
3 番目の手順では、ジェネリック メソッドを呼び出す方法を示します。
Von Bedeutung
メソッドはジェネリック型に属し、その型の型パラメーターを使用するため、ジェネリックではありません。 メソッドは、独自の型パラメーター リストがある場合にのみジェネリックです。 ジェネリック メソッドは、次の例のように、非ジェネリック型で使用できます。 ジェネリック型の非ジェネリック メソッドの例については、「 方法: リフレクション出力を使用してジェネリック型を定義する」を参照してください。
ジェネリック メソッドを定義する
開始する前に、高度な言語を使用して記述するときにジェネリック メソッドがどのように表示されるかを確認すると便利です。 この記事のコード例には、ジェネリック メソッドを呼び出すコードと共に、次のコードが含まれています。 このメソッドには、
TInput
とTOutput
の 2 つの型パラメーターがあり、2 つ目は参照型 (class
) である必要があり、パラメーターなしのコンストラクター (new
) を持つ必要があり、ICollection<TInput>
を実装する必要があります。 このインターフェイス制約により、 ICollection<T>.Add メソッドを使用して、メソッドが作成するTOutput
コレクションに要素を追加できます。 メソッドには、TInput
の配列である 1 つの仮パラメーターinput
があります。 このメソッドは、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
という名前のモジュールが 1 つだけあり、モジュール名はアセンブリ名と拡張機能と同じです。 この例では、アセンブリはディスクに保存され、また実行されるため、 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)
パラメーターの名前を含む文字列の配列を
DemoMethod
メソッドに渡して、MethodBuilder.DefineGenericParametersのジェネリック型パラメーターを定義します。 これにより、メソッドがジェネリック メソッドになります。 次のコードでは、TInput
型パラメーターとTOutput
を持つジェネリック メソッドFactory
します。 コードを読みやすくするために、これらの名前を持つ変数が作成され、2 つの型パラメーターを表す 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
メソッドには 1 つのパラメーター (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を使用してメソッド本体を出力します。 詳細については、メソッド本体を出力するための付随する手順を参照してください。
Von Bedeutung
ジェネリック型のメソッドの呼び出しを出力し、それらの型の型引数がジェネリック メソッドの型パラメーターである場合は、TypeBuilder クラスの
static
GetConstructor(Type, ConstructorInfo)、GetMethod(Type, MethodInfo)、およびGetField(Type, FieldInfo)メソッドオーバーロードを使用して、構築された形式のメソッドを取得する必要があります。 メソッド本体を出力するための付随する手順は、これを示しています。メソッドを含む型を完了し、アセンブリを保存します。 ジェネリック メソッドを呼び出すための付随する手順は、完成したメソッドを呼び出す 2 つの方法を示しています。
// 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
メソッドには、4 つのローカル変数があります。メソッドによって返される新しいTOutput
を保持するretVal
、ICollection<TInput>
にキャストされたときにTOutput
を保持するic
、TInput
オブジェクトの入力配列を保持するinput
、配列を反復処理するindex
。 また、このメソッドには 2 つのラベルがあり、1 つはループ (enterLoop
) に入り、1 つはループの先頭 (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
に追加する理由です。 MakeGenericMethodにTOutput
を渡して、構築されたジェネリック メソッドを作成します。 メソッドを呼び出すコードを出力した後、それを使用retVal
ローカル変数に格納するコードを出力します。 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)
新しい
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メソッドを使用してこのMethodInfoをicollOfTInput
から直接取得することはできません。GetMethodは、GenericTypeParameterBuilderで構築された型ではサポートされていないためです。 代わりに、ICollection<T> ジェネリック インターフェイスのジェネリック型定義を含むicoll
でGetMethodを呼び出します。 次に、 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を呼び出して、ループの先頭にマークを付けます。 ラベルを使用する分岐ステートメントは、コード内のこの時点まで分岐するようになりました。 次の手順では、ICollection(Of TInput)
にキャストされたTOutput
オブジェクトをスタックにプッシュします。 すぐには必要ありませんが、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 メソッドを使用します。 次のコードでは、構築されたジェネリック メソッドを作成し、TOutput
のTInput
とList(Of String)
(C# でList<string>
) の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の配列を作成し、ジェネリック メソッドの引数リストとして渡します。 メソッドが
static
されているため、Invokeの最初のパラメーターは null 参照です。 戻り値はList(Of String)
にキャストされ、その最初の要素が表示されます。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))
デリゲートを使用してメソッドを呼び出すには、構築されたジェネリック メソッドのシグネチャに一致するデリゲートが必要です。 これを行う簡単な方法は、汎用デリゲートを作成する方法です。 次のコードでは、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: {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
の 2 つのジェネリック型パラメーターがあります。
TOutput
型パラメーターは、ICollection<TInput>
(Visual Basic では ICollection(Of TInput)
) を実装し、参照型にし、パラメーターなしのコンストラクターを持つ制約があります。
メソッドには、 TInput
の配列である 1 つの仮パラメーターがあります。 このメソッドは、入力配列のすべての要素を含む 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: {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
こちらも参照ください
.NET