방법: 리플렉션 내보내기를 사용하여 제네릭 메서드 정의
첫 번째 절차에서는 두 가지의 형식 매개 변수를 사용하여 간단한 제네릭 메서드를 만드는 방법과 클래스 제약 조건, 인터페이스 제약 조건 및 특수 제약 조건을 해당 형식 매개 변수에 적용하는 방법을 보여 줍니다.
두 번째 절차에서는 메서드 본문을 내보내는 방법과 제네릭 메서드의 형식 매개 변수를 사용하여 제네릭 형식의 인스턴스를 만들고 해당 메서드를 호출하는 방법을 보여 줍니다.
세 번째 절차에서는 제네릭 메서드를 호출하는 방법을 보여 줍니다.
중요 |
---|
메서드는 제네릭 형식에 속하고 해당 형식의 형식 매개 변수를 사용하므로 제네릭이 아닙니다.메서드는 고유한 형식 매개 변수 목록이 있어야만 제네릭입니다.제네릭 메서드는 다음 예제와 같이 제네릭이 아닌 형식에 나타날 수 있습니다.제네릭 형식에 있는 제네릭이 아닌 메서드의 예제를 보려면 방법: 리플렉션 내보내기를 사용하여 제네릭 형식 정의를 참조하십시오. |
제네릭 메서드를 정의하려면
상위 수준 언어를 사용하여 작성된 경우에는 시작하기 전에 제네릭 메서드가 표시되는 방식을 확인하는 것이 좋습니다. 다음 코드는 제네릭 메서드를 호출하는 코드와 함께 이 항목의 예제 코드에 포함됩니다. 메서드에는 TInput 및 TOutput의 두 가지 형식 매개 변수가 있는데, 이 중 후자는 참조 형식(class)이어야 하고 매개 변수가 없는 생성자(new)가 있어야 하며 ICollection(Of TInput)(C#의 경우 ICollection<TInput>)을 구현해야 합니다. 이 인터페이스 제약 조건을 사용하면 ICollection<T>.Add 메서드를 사용하여 이 메서드가 만드는 TOutput 컬렉션에 요소를 추가할 수 있습니다. 메서드에는 TInput의 배열인 input이라는 하나의 정식 매개 변수가 있습니다. 이 메서드는 형식 TOutput의 컬렉션을 만들고 input의 요소를 해당 컬렉션에 복사합니다.
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 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; }
동적 어셈블리와 동적 모듈을 정의하여 제네릭 메서드가 속하는 형식을 포함시킵니다. 이 경우 어셈블리에는 DemoMethodBuilder1이라는 하나의 모듈만 있고 해당 모듈 이름은 어셈블리 이름에 확장명을 추가하여 사용합니다. 이 예제에서는 어셈블리가 디스크에 저장되고 실행되므로 AssemblyBuilderAccess.RunAndSave가 지정됩니다. Ildasm.exe(MSIL 디스어셈블러)를 사용하여 DemoMethodBuilder1.dll을 검사하고 이 파일과 MSIL(Microsoft intermediate language)에서 메서드(1단계에 표시)를 비교합니다.
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")
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");
제네릭 메서드가 속하는 형식을 정의합니다. 해당 형식이 제네릭일 필요는 없습니다. 제네릭 메서드는 제네릭 형식 또는 제네릭이 아닌 형식에 속할 수 있습니다. 이 예제에서는 형식이 DemoType이라는 이름의 클래스이며 제네릭이 아닙니다.
Dim demoType As TypeBuilder = demoModule.DefineType( _ "DemoType", _ TypeAttributes.Public)
TypeBuilder demoType = demoModule.DefineType("DemoType", TypeAttributes.Public);
제네릭 메서드를 정의합니다. 제네릭 메서드의 정식 매개 변수 형식이 제네릭 메서드의 제네릭 매개 변수로 지정되는 경우 DefineMethod(String, MethodAttributes) 메서드 오버로드를 사용하여 해당 메서드를 정의합니다. 메서드의 제네릭 정식 매개 변수가 아직 정의되지 않은 경우 DefineMethod에 대한 호출에서 해당 메서드의 형식 매개 변수의 형식을 지정할 수 없습니다. 이 예제에서 메서드 이름은 Factory로 지정됩니다. 메서드는 공용이고 static(Visual Basic의 경우 Shared)입니다.
Dim factory As MethodBuilder = _ demoType.DefineMethod("Factory", _ MethodAttributes.Public Or MethodAttributes.Static)
MethodBuilder factory = demoType.DefineMethod("Factory", MethodAttributes.Public | MethodAttributes.Static);
매개 변수 이름이 포함된 문자열 배열을 MethodBuilder.DefineGenericParameters 메서드로 전달하여 DemoMethod의 제네릭 형식 매개 변수를 정의합니다. 이렇게 하면 메서드가 제네릭 메서드가 됩니다. 다음 코드에서 Factory는 TInput 및 TOutput 형식 매개 변수를 가진 제네릭 메서드가 됩니다. 코드를 보다 읽기 쉽게 만들기 위해 이러한 이름을 가진 변수를 만들어 두 가지 형식 매개 변수를 나타내는 GenericTypeParameterBuilder 개체를 유지합니다.
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)
string[] typeParameterNames = {"TInput", "TOutput"}; GenericTypeParameterBuilder[] typeParameters = factory.DefineGenericParameters(typeParameterNames); GenericTypeParameterBuilder TInput = typeParameters[0]; GenericTypeParameterBuilder TOutput = typeParameters[1];
형식 매개 변수에 특수 제약 조건을 선택적으로 추가합니다. SetGenericParameterAttributes 메서드를 사용하여 특수 제약 조건을 추가합니다. 이 예제에서는 TOutput을 참조 형식으로 제한하고 매개 변수가 없는 생성자를 갖도록 제한합니다.
TOutput.SetGenericParameterAttributes( _ GenericParameterAttributes.ReferenceTypeConstraint Or _ GenericParameterAttributes.DefaultConstructorConstraint)
TOutput.SetGenericParameterAttributes( GenericParameterAttributes.ReferenceTypeConstraint | GenericParameterAttributes.DefaultConstructorConstraint);
형식 매개 변수에 클래스 제약 조건과 인터페이스 제약 조건을 선택적으로 추가합니다. 이 예제에서는 형식 매개 변수 TOutput을 ICollection(Of TInput)(C#의 경우 ICollection<TInput>) 인터페이스를 구현하는 형식으로 제한합니다. 이렇게 하면 Add 메서드를 사용하여 요소를 추가할 수 있습니다.
Dim icoll As Type = GetType(ICollection(Of )) Dim icollOfTInput As Type = icoll.MakeGenericType(TInput) Dim constraints() As Type = { icollOfTInput } TOutput.SetInterfaceConstraints(constraints)
Type icoll = typeof(ICollection<>); Type icollOfTInput = icoll.MakeGenericType(TInput); Type[] constraints = {icollOfTInput}; TOutput.SetInterfaceConstraints(constraints);
SetParameters 메서드를 사용하여 메서드의 정식 매개 변수를 정의합니다. 이 예제에서 Factory 메서드에는 TInput의 배열인 하나의 매개 변수가 있습니다. 이 형식은 TInput을 나타내는 GenericTypeParameterBuilder에서 MakeArrayType 메서드를 호출하여 만듭니다. SetParameters의 인수는 Type 개체의 배열입니다.
Dim params() As Type = { TInput.MakeArrayType() } factory.SetParameters(params)
Type[] parms = {TInput.MakeArrayType()}; factory.SetParameters(parms);
SetReturnType 메서드를 사용하여 메서드의 반환 형식을 정의합니다. 이 예제에서는 TOutput의 인스턴스가 반환됩니다.
factory.SetReturnType(TOutput)
factory.SetReturnType(TOutput);
ILGenerator를 사용하여 메서드 본문을 내보냅니다. 자세한 내용은 다음에 나오는 절차인 메서드 본문을 내보내려면을 참조하십시오.
중요 제네릭 형식의 메서드에 대한 호출을 내보내고 해당 형식의 형식 인수가 제네릭 메서드의 형식 매개 변수인 경우 TypeBuilder 클래스의 static GetConstructor(Type, ConstructorInfo), GetMethod(Type, MethodInfo), and GetField(Type, FieldInfo) 메서드 오버로드를 사용하여 해당 메서드의 생성된 폼을 가져와야 합니다.메서드 본문 내보내기에 수반되는 절차에서 이 작업에 대해 설명합니다.
메서드가 포함된 형식을 완료하고 어셈블리를 저장합니다. 다음에 나오는 절차인 제네릭 메서드를 호출하려면에서는 완성된 메서드를 호출하는 두 가지 방법을 보여 줍니다.
' 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")
// Complete the type. Type dt = demoType.CreateType(); // Save the assembly, so it can be examined with Ildasm.exe. demoAssembly.Save(asmName.Name+".dll");
메서드 본문을 내보내려면
코드 생성기를 가져오고 지역 변수와 레이블을 선언합니다. DeclareLocal 메서드는 지역 변수를 선언하는 데 사용됩니다. 메서드에 의해 반환되는 새 TOutput을 보유하는 retVal, TOutput이 ICollection(Of TInput)(C#의 경우 ICollection<TInput>)으로 캐스팅될 때 이를 보유하는 ic, TInput 개체의 입력 배열을 보유하는 input 및 배열을 통해 반복되는 index의 네 가지 지역 변수가 Factory 메서드에 있습니다. 또한 이 메서드에는 루프에 들어가는(enterLoop) 레이블과 루프 위쪽(loopAgain)에 사용되는 레이블이 있습니다. 이러한 레이블은 DefineLabel 메서드를 사용하여 정의합니다.
메서드가 맨 처음 수행하는 작업은 Ldarg_0 opcode를 사용하여 인수를 로드하고 Stloc_S opcode를 사용하여 지역 변수 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)
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);
Activator.CreateInstance 메서드의 제네릭 메서드 오버로드를 사용하여 코드를 내보내고 TOutput의 인스턴스를 만듭니다. 이 오버로드를 사용하려면 지정된 형식에 매개 변수가 없는 생성자가 있어야 하는데, 이를 위해 제약 조건을 TOutput에 추가합니다. TOutput을 MakeGenericMethod에 전달하여 생성된 제네릭 메서드를 만듭니다. 메서드를 호출하기 위해 코드를 내보낸 다음 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)
MethodInfo createInst = typeof(Activator).GetMethod("CreateInstance", Type.EmptyTypes); MethodInfo createInstOfTOutput = 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(Of TInput)(C#의 경우 ICollection<TInput>)에서 작동하므로 해당 생성된 형식에 적용되는 Add 메서드를 가져와야 합니다. GetMethod 메서드는 GenericTypeParameterBuilder를 사용하여 생성된 형식에서 지원되지 않기 때문에 GetMethod 메서드를 사용하여 icollOfTInput에서 이 MethodInfo를 직접 가져올 수 없습니다. 대신 ICollection<T> 제네릭 인터페이스에 대한 제네릭 형식 정의가 들어 있는 icoll에서 GetMethod를 호출합니다. 그런 다음 GetMethod(Type, MethodInfo) static 메서드를 사용하여 생성된 형식에 대한 MethodInfo를 만듭니다. 다음 코드에서는 이 작업에 대해 설명합니다.
Dim mAddPrep As MethodInfo = icoll.GetMethod("Add") Dim mAdd As MethodInfo = _ TypeBuilder.GetMethod(icollOfTInput, mAddPrep)
MethodInfo mAddPrep = icoll.GetMethod("Add"); MethodInfo mAdd = 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 opcode는 스택에서 인덱스와 배열을 팝하고 인덱싱된 배열 요소를 스택으로 푸시합니다. 스택에서 컬렉션과 새 요소를 팝하고 해당 요소를 컬렉션에 추가하여 스택이 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>)를 지정하여 생성된 제네릭 메서드를 만든 다음 해당 메서드에 대한 문자열 표현을 표시합니다.
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)
MethodInfo m = dt.GetMethod("Factory"); MethodInfo bound = m.MakeGenericMethod(typeof(string), typeof(List<string>)); // Display a string representing the bound method. Console.WriteLine(bound);
런타임에 바인딩된 메서드를 호출하려면 Invoke 메서드를 사용합니다. 다음 코드에서는 유일한 요소로서 문자열의 배열을 포함하는 Object의 배열을 만들고 이 배열을 제네릭 메서드의 인수 목록으로 전달합니다. 메서드가 static이므로 Invoke의 첫 매개 변수는 null 참조입니다. 반환 값은 List(Of String)에 캐스팅되고 해당 요소가 표시됩니다.
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))
object o = bound.Invoke(null, new object[]{arr}); List<string> list2 = (List<string>) o; Console.WriteLine("The first element is: {0}", list2[0]);
대리자를 사용하여 메서드를 호출하려면 생성된 제네릭 메서드의 시그니처와 일치하는 대리자가 있어야 합니다. 이를 위한 쉬운 방법은 제네릭 대리자를 만드는 것입니다. 다음 코드에서는 Delegate.CreateDelegate(Type, MethodInfo) 메서드 오버로드를 사용하여 예제 코드에서 정의된 제네릭 대리자 D의 인스턴스를 만든 다음 해당 대리자를 호출합니다. 대리자가 런타임에 바인딩된 호출보다 역할을 더 잘 수행합니다.
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))
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]);
내보낸 메서드는 저장된 어셈블리를 참조하는 프로그램에서 호출할 수도 있습니다.
예제
다음 코드 예제에서는 제네릭 메서드 Factory를 사용하여 제네릭이 아닌 형식 DemoType을 만듭니다. 이 메서드에는 입력 형식을 지정하는 TInput과 출력 형식을 지정하는 TOutput의 두 가지 제네릭 형식 매개 변수가 있습니다. TOutput 형식 매개 변수는 ICollection<TInput>(Visual Basic의 경우 ICollection(Of TInput))을 구현하도록 제한되고, 참조 형식으로 제한되고, 매개 변수가 없는 생성자를 갖도록 제한됩니다.
이 메서드에는 TInput의 배열인 하나의 정식 매개 변수가 있습니다. 이 메서드는 입력 배열의 모든 요소가 들어 있는 TOutput을 반환합니다. TOutput은 ICollection<T> 제네릭 인터페이스를 구현하는 임의의 제네릭 컬렉션 형식이 될 수 있습니다.
코드를 실행할 때 동적 어셈블리는 DemoGenericMethod1.dll으로 저장되고 Ildasm.exe(MSIL 디스어셈블러)를 사용하여 검사할 수 있습니다.
참고 |
---|
코드를 내보내는 방법을 배우려면 내보내려는 작업을 수행하는 Visual Basic, C# 또는 Visual C++ 프로그램을 작성한 다음 디스어셈블러를 사용하여 컴파일러에서 만든 MSIL을 검사해 보는 것이 좋습니다. |
코드 예제에는 내보낸 메서드에 해당하는 소스 코드가 포함되어 있습니다. 내보낸 메서드는 런타임에 바인딩되어 호출되고 코드 예제에서 선언한 제네릭 대리자를 사용하여 호출됩니다.
Imports System
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
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
*/
코드 컴파일
이 코드에는 컴파일에 필요한 C# using 문(Visual Basic의 경우 Imports)이 포함되어 있습니다.
추가 어셈블리 참조는 필요하지 않습니다.
csc.exe, vbc.exe 또는 cl.exe를 사용하여 명령줄에서 코드를 컴파일합니다. Visual Studio에서 코드를 컴파일하려면 콘솔 응용 프로젝트 템플릿에 코드를 삽입합니다.