첫 번째 절차에서는 두 개의 형식 매개 변수를 사용하여 간단한 제네릭 메서드를 만드는 방법과 형식 매개 변수에 클래스 제약 조건, 인터페이스 제약 조건 및 특수 제약 조건을 적용하는 방법을 보여 줍니다.
두 번째 절차에서는 메서드 본문을 내보내는 방법과 제네릭 메서드의 형식 매개 변수를 사용하여 제네릭 형식의 인스턴스를 만들고 해당 메서드를 호출하는 방법을 보여 줍니다.
세 번째 프로시저는 제네릭 메서드를 호출하는 방법을 보여줍니다.
중요합니다
메서드는 제네릭 형식에 속하고 해당 형식의 형식 매개 변수를 사용하기 때문에 제네릭이 아닙니다. 메서드는 고유한 형식 매개 변수 목록이 있는 경우에만 제네릭입니다. 제네릭 메서드는 이 예제와 같이 제네릭이 아닌 형식에 나타날 수 있습니다. 제네릭 형식에 대한 비제네릭 메서드의 예는 방법: 리플렉션 내보내기를 사용하여 제네릭 형식 정의를 참조하세요.
제네릭 메서드 정의
시작하기 전에 상위 수준 언어를 사용하여 작성할 때 제네릭 메서드가 어떻게 표시되는지 살펴보는 것이 유용합니다. 다음 코드는 제네릭 메서드를 호출하는 코드와 함께 이 문서의 예제 코드에 포함되어 있습니다. 메서드에는 두 개의 형식 매개 변수가 있으며
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
.입니다. 이 메서드는 public이며static
(Shared
는 Visual Basic에서는)MethodBuilder factory = demoType.DefineMethod("Factory", MethodAttributes.Public | MethodAttributes.Static);
Dim factory As MethodBuilder = _ demoType.DefineMethod("Factory", _ MethodAttributes.Public Or MethodAttributes.Static)
매개 변수 이름이 포함된 문자열 배열을
DemoMethod
메서드에 전달하여 MethodBuilder.DefineGenericParameters 제네릭 형식 매개 변수를 정의합니다. 이렇게 하면 메서드가 제네릭 메서드로 만들어집니다. 다음 코드는 형식 매개 변수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
배열이 있습니다. 이 형식은 MakeArrayType를 나타내는 GenericTypeParameterBuilder에서TInput
메서드를 호출하여 생성됩니다. 인수 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메서드 본문을 내보낸다. 자세한 내용은 메서드 본문을 내보내는 데 수반되는 절차를 참조하세요.
중요합니다
제네릭 형식의 메서드에 대한 호출을 내보내고, 이러한 형식의 형식 인수가 제네릭 메서드의 형식 매개 변수인 경우, 메서드의 생성된 형태를 얻기 위해
static
, GetConstructor(Type, ConstructorInfo), GetMethod(Type, MethodInfo), GetField(Type, FieldInfo) 메서드 오버로드를 TypeBuilder 클래스에서 사용해야 합니다. 메서드 본문을 내보내는 데 수반되는 프로시저가 이를 보여 줍니다.메서드가 포함된 형식을 완료하고 어셈블리를 저장합니다. 제네릭 메서드를 호출하는 프로시저는 완료된 메서드를 호출하는 두 가지 방법을 보여줍니다.
// 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
를TOutput
로 캐스팅할 때 사용하는 변수ICollection<TInput>
,input
개체 배열 입력을 보관하기 위한 변수TInput
, 그리고 배열을 반복하기 위한 변수index
입니다. 메서드에는 루프를 입력하기 위한 레이블(enterLoop
)과 루프의 맨 위에 위치하는 레이블(loopAgain
)이 있습니다. 이들은 DefineLabel 메서드를 사용하여 정의됩니다.이 메서드가 가장 먼저 수행하는 작업은 opcode를 사용하여 Ldarg_0 인수를 로드하고 opcode를 사용하여
input
로컬 변수 Stloc_S 에 저장하는 것입니다.ILGenerator ilgen = factory.GetILGenerator(); LocalBuilder retVal = ilgen.DeclareLocal(TOutput); LocalBuilder ic = ilgen.DeclareLocal(icollOfTInput); LocalBuilder input = ilgen.DeclareLocal(TInput.MakeArrayType()); LocalBuilder index = ilgen.DeclareLocal(typeof(int)); Label enterLoop = ilgen.DefineLabel(); Label loopAgain = ilgen.DefineLabel(); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Stloc_S, input);
Dim ilgen As ILGenerator = factory.GetILGenerator() Dim retVal As LocalBuilder = ilgen.DeclareLocal(TOutput) Dim ic As LocalBuilder = ilgen.DeclareLocal(icollOfTInput) Dim input As LocalBuilder = _ ilgen.DeclareLocal(TInput.MakeArrayType()) Dim index As LocalBuilder = _ ilgen.DeclareLocal(GetType(Integer)) Dim enterLoop As Label = ilgen.DefineLabel() Dim loopAgain As Label = ilgen.DefineLabel() ilgen.Emit(OpCodes.Ldarg_0) ilgen.Emit(OpCodes.Stloc_S, input)
TOutput
메서드의 제네릭 메서드 오버로드를 사용하여 Activator.CreateInstance의 인스턴스를 생성하는 코드를 내보낸다. 이 오버로드를 사용하려면 지정된 형식에 매개 변수가 없는 생성자가 있어야 합니다. 이는 해당 제약TOutput
조건을 추가하는 이유입니다.TOutput
을 MakeGenericMethod에 전달하여 생성된 제네릭 메서드를 만듭니다. 메서드를 호출하는 코드를 내보낸 후 다음을 사용하여 로컬 변수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)
메서드 MethodInfo를 나타내는 값을 ICollection<T>.Add 가져옵니다.
ICollection<TInput>
에 작동하는 메서드이므로 해당 생성된 형식에 특화된Add
메서드를 가져와야 합니다. GetMethod에서 직접 MethodInfo을(를) 가져오는icollOfTInput
메서드를 사용할 수 없습니다. 그 이유는 GetMethod로 생성된 형식에서는 GenericTypeParameterBuilder가 지원되지 않기 때문입니다. 대신, GetMethod에서icoll
를 호출하세요. 여기에는 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)
루프에 대한 코드를 내보냅니다. 첫 번째 단계는 MarkLabel를
loopAgain
레이블로 호출하여 루프의 맨 위를 표시하는 것입니다. 이제 레이블을 사용하는 분기 문이 코드의 이 지점으로 분기됩니다. 다음 단계는 개체를TOutput
로 캐스팅한 후ICollection(Of TInput)
스택에 푸시하는 것입니다. 즉시 필요하지는 않지만 메서드를 호출Add
하기 위한 위치에 있어야 합니다. 그런 다음 입력 배열이 스택으로 푸시된 다음index
현재 인덱스가 포함된 변수를 배열로 푸시합니다. opcode는 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 작업을 수행하려면 이 메서드를 사용합니다. 다음 코드는 String가TInput
에 지정되고 C#의List(Of String)
가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는 메서드가
static
이기 때문에 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))
대리자를 사용하여 메서드를 호출하려면 생성된 제네릭 메서드의 서명과 일치하는 대리자가 있어야 합니다. 이 작업을 수행하는 쉬운 방법은 제네릭 대리자를 만드는 것입니다. 다음 코드는 예제 코드에 정의된 제네릭 대리자
D
의 인스턴스를 Delegate.CreateDelegate(Type, MethodInfo) 메서드 오버로드를 사용하여 만들고 대리자를 호출합니다. 대리자는 지연 바인딩된 호출보다 성능이 우수합니다.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))
내보낸 메서드는 저장된 어셈블리를 참조하는 프로그램에서도 호출할 수 있습니다.
예시
다음 코드 예제에서는 제네릭 메서드DemoType
를 사용하여 제네릭이 아닌 형식Factory
을 만듭니다. 이 메서드에는 입력 형식을 지정하고 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: {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