방법: 리플렉션 내보내기를 사용하여 제네릭 메서드 정의

첫 번째 절차에서는 두 개의 형식 매개 변수가 있는 간단한 제네릭 메서드를 만드는 방법 및 형식 매개 변수에 클래스 제약 조건, 인터페이스 제약 조건 및 특수 제약 조건을 적용하는 방법을 보여 줍니다.

두 번째 절차에서는 메서드 본문을 내보내는 방법 및 제네릭 메서드의 형식 매개 변수를 사용하여 제네릭 형식 인스턴스를 만들고 해당 메서드를 호출하는 방법을 보여 줍니다.

세 번째 절차에서는 제네릭 메서드를 호출하는 방법을 보여 줍니다.

Important

제네릭 형식에 속하고 해당 형식의 형식 매개 변수를 사용한다고 해서 제네릭 메서드가 되는 것은 아닙니다. 고유한 형식 매개 변수 목록을 포함하는 경우에만 제네릭 메서드입니다. 제네릭 메서드는 이 예제와 같이 제네릭이 아닌 형식에 나타날 수 있습니다. 제네릭 형식의 제네릭이 아닌 메서드에 대한 예제는 방법: 리플렉션 내보내기를 사용하여 제네릭 형식 정의를 참조하세요.

제네릭 메서드 정의

  1. 시작하기 전에, 상위 수준 언어를 사용하여 작성할 경우 제네릭 메서드가 표시되는 방식을 확인하는 것이 좋습니다. 다음 코드는 제네릭 메서드를 호출하는 코드와 함께 이 문서의 예제 코드에 포함되어 있습니다. 메서드에는 두 개의 형식 매개 변수가 있으며TOutput, TInput 두 번째 매개 변수는 참조 형식(class)이어야 하고 매개 변수가 없는 생성자(new)가 있어야 하며 구현ICollection<TInput>해야 합니다. 이 인터페이스 제약 조건은 ICollection<T>.Add 메서드를 사용하여 이 메서드가 만드는 TOutput 컬렉션에 요소를 추가할 수 있도록 합니다. 메서드에는 TInput 배열인 하나의 형식 매개 변수 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
    
  2. 제네릭 메서드가 속하는 형식을 포함할 동적 모듈 및 동적 어셈블리를 정의합니다. 이 경우 어셈블리에는 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")
    
  3. 제네릭 메서드가 속하는 형식을 정의합니다. 제네릭 형식일 필요는 없습니다. 제네릭 메서드는 제네릭 형식 또는 제네릭이 아닌 형식에 속할 수 있습니다. 이 예제에서 형식은 클래스이고, 제네릭이 아니며, 이름이 DemoType입니다.

    TypeBuilder demoType =
        demoModule.DefineType("DemoType", TypeAttributes.Public);
    
    Dim demoType As TypeBuilder = demoModule.DefineType( _
        "DemoType", _
        TypeAttributes.Public)
    
  4. 제네릭 메서드를 정의합니다. 제네릭 메서드의 공식 매개 변수 형식이 제네릭 메서드의 제네릭 형식 매개 변수로 지정된 경우 DefineMethod(String, MethodAttributes) 메서드 오버로드를 사용하여 메서드를 정의합니다. 메서드의 제네릭 형식 매개 변수는 아직 정의되지 않았으므로 DefineMethod 호출에서 메서드의 공식 매개 변수 형식을 지정할 수 없습니다. 이 예제에서 메서드 이름은 Factory입니다. 메서드가 public이고 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)
    
  5. 매개 변수 이름이 포함된 문자열 배열을 MethodBuilder.DefineGenericParameters 메서드에 전달하여 DemoMethod의 제네릭 형식 매개 변수를 정의합니다. 이렇게 하면 메서드가 제네릭 메서드가 됩니다. 다음 코드에서는 형식 매개 변수 TInputTOutput을 사용하여 Factory를 제네릭 메서드로 만듭니다. 코드를 읽기 쉽도록 두 개의 형식 매개 변수를 나타내는 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)
    
  6. 필요에 따라 형식 매개 변수에 특수 제약 조건을 추가합니다. 특수 제약 조건은 SetGenericParameterAttributes 메서드를 사용하여 추가됩니다. 이 예제에서 TOutput은 참조 형식으로, 매개 변수가 없는 생성자를 갖도록 제한됩니다.

    TOutput.SetGenericParameterAttributes(
        GenericParameterAttributes.ReferenceTypeConstraint |
        GenericParameterAttributes.DefaultConstructorConstraint);
    
    TOutput.SetGenericParameterAttributes( _
        GenericParameterAttributes.ReferenceTypeConstraint Or _
        GenericParameterAttributes.DefaultConstructorConstraint)
    
  7. 필요에 따라 형식 매개 변수에 클래스 및 인터페이스 제약 조건을 추가합니다. 이 예제에서 형식 매개 변수 TOutputICollection(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)
    
  8. SetParameters 메서드를 사용하여 메서드의 공식 매개 변수를 정의합니다. 이 예제에서 Factory 메서드에는 TInput 배열인 매개 변수 하나가 있습니다. TInput을 나타내는 GenericTypeParameterBuilder에서 MakeArrayType 메서드를 호출하면 이 형식이 생성됩니다. SetParameters의 인수는 Type 개체 배열입니다.

    Type[] parms = {TInput.MakeArrayType()};
    factory.SetParameters(parms);
    
    Dim params() As Type = {TInput.MakeArrayType()}
    factory.SetParameters(params)
    
  9. SetReturnType 메서드를 사용하여 메서드의 반환 형식을 정의합니다. 이 예제에서는 TOutput 인스턴스가 반환됩니다.

    factory.SetReturnType(TOutput);
    
    factory.SetReturnType(TOutput)
    
  10. ILGenerator를 사용하여 메서드 본문을 내보냅니다. 자세한 내용은 다음에 나오는 메서드 본문을 내보내기 위한 절차를 참조하십시오.

    Important

    제네릭 형식의 메서드 호출을 내보내고 이러한 형식의 형식 인수가 제네릭 메서드의 형식 매개 변수인 경우 TypeBuilder 클래스의 staticGetConstructor(Type, ConstructorInfo), GetMethod(Type, MethodInfo)GetField(Type, FieldInfo) 메서드 오버로드를 사용하여 생성된 형식의 메서드를 가져옵니다. 자세한 내용은 다음에 나오는 메서드 본문을 내보내는 절차를 참조하세요.

  11. 메서드를 포함하는 형식을 완성하고 어셈블리를 저장합니다. 다음에 나오는 제네릭 메서드 호출에 대한 절차에서는 완성된 메서드를 호출하는 두 가지 방법을 보여 줍니다.

    // 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")
    

메서드 본문 내보내기

  1. 코드 생성기를 가져오고 지역 변수와 레이블을 선언합니다. DeclareLocal 메서드는 지역 변수를 선언하는 데 사용됩니다. 메서드에는 Factory 메서드에서 반환하는 새 TOutput 변수를 보관하고, 캐스팅 ICollection<TInput>input 할 때를 보관 TOutput 하고, ic 개체의 입력 배열을 보관하고index, 배열을 반복하는 네 가지 TInput 지역 변수 retVal 가 있습니다. 메서드에는 루프를 시작하는 레이블(enterLoop)과 DefineLabel 메서드를 사용하여 정의된 루프 맨 위 레이블(loopAgain)이 있습니다.

    메서드에서 수행하는 첫 번째 작업은 Ldarg_0 opcode를 사용하여 해당 인수를 로드하고 Stloc_S opcode를 사용하여 지역 변수 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)
    
  2. Activator.CreateInstance 메서드의 제네릭 메서드 오버로드를 사용하여 TOutput 인스턴스를 만드는 코드를 내보냅니다. 이 오버로드를 사용하려면 지정된 형식이 매개 변수가 없는 생성자를 포함해야 하며, 이런 이유로 해당 제약 조건을 TOutput에 추가합니다. TOutputMakeGenericMethod에 전달하여 생성된 제네릭 메서드를 만듭니다. 메서드를 호출하는 코드를 내보낸 후 Stloc_S를 사용하여 지역 변수 retVal에 저장하는 코드를 내보냅니다.

    MethodInfo createInst =
        typeof(Activator).GetMethod("CreateInstance", Type.EmptyTypes);
    MethodInfo createInstOfTOutput =
        createInst.MakeGenericMethod(TOutput);
    
    ilgen.Emit(OpCodes.Call, createInstOfTOutput);
    ilgen.Emit(OpCodes.Stloc_S, retVal);
    
    Dim createInst As MethodInfo = _
        GetType(Activator).GetMethod("CreateInstance", Type.EmptyTypes)
    Dim createInstOfTOutput As MethodInfo = _
        createInst.MakeGenericMethod(TOutput)
    
    ilgen.Emit(OpCodes.Call, createInstOfTOutput)
    ilgen.Emit(OpCodes.Stloc_S, retVal)
    
  3. 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)
    
  4. ICollection<T>.Add 메서드를 나타내는 MethodInfo를 가져옵니다. 메서드가 작동하므로 ICollection<TInput>생성된 형식과 관련된 메서드를 Add 가져와야 합니다. GetMethodGenericTypeParameterBuilder로 생성된 형식에서 지원되지 않으므로 GetMethod 메서드를 사용하여 이 MethodInfoicollOfTInput에서 직접 가져올 수 없습니다. 대신, 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)
    
  5. 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)
    
  6. 루프에 대한 코드를 내보냅니다. 첫 번째 단계는 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)
    
  7. TOutput 개체를 스택에 푸시하는 코드를 내보내고 메서드에서 반환합니다. 지역 변수 retValic 둘 다에 새 TOutput에 대한 참조가 포함되어 있습니다. icICollection<T>.Add 메서드에 액세스하는 데만 사용됩니다.

    ilgen.Emit(OpCodes.Ldloc_S, retVal);
    ilgen.Emit(OpCodes.Ret);
    
    ilgen.Emit(OpCodes.Ldloc_S, retVal)
    ilgen.Emit(OpCodes.Ret)
    

제네릭 메서드 호출

  1. Factory는 제네릭 메서드 정의입니다. 호출하려면 해당 제네릭 형식 매개 변수에 형식을 할당해야 합니다. 이렇게 하려면 MakeGenericMethod 메서드를 사용합니다. 다음 코드에서는 TInput에 대해 String을 지정하고 TOutput에 대해 List(Of String)(C#에서는 List<string>)을 지정하여 생성된 제네릭 메서드를 만들고 메서드의 문자열 표현을 표시합니다.

    MethodInfo m = dt.GetMethod("Factory");
    MethodInfo bound =
        m.MakeGenericMethod(typeof(string), typeof(List<string>));
    
    // Display a string representing the bound method.
    Console.WriteLine(bound);
    
    Dim m As MethodInfo = dt.GetMethod("Factory")
    Dim bound As MethodInfo = m.MakeGenericMethod( _
        GetType(String), GetType(List(Of String)))
    
    ' Display a string representing the bound method.
    Console.WriteLine(bound)
    
  2. 런타임에 바인딩된 메서드를 호출하려면 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: {0}", list2[0]);
    
    Dim o As Object = bound.Invoke(Nothing, New Object() {arr})
    Dim list2 As List(Of String) = CType(o, List(Of String))
    
    Console.WriteLine("The first element is: {0}", list2(0))
    
  3. 대리자를 사용하여 메서드를 호출하려면 생성된 제네릭 메서드의 시그니처와 일치하는 대리자가 있어야 합니다. 이 작업을 수행하는 편리한 방법은 제네릭 대리자를 만드는 것입니다. 다음 코드는 Delegate.CreateDelegate(Type, MethodInfo) 메서드 오버로드를 사용하여 예제 코드에서 정의된 제네릭 대리자 D의 인스턴스를 만들고 대리자를 호출합니다. 대리자는 런타임에 바인딩된 호출보다 성능이 우수합니다.

    Type dType = typeof(D<string, List <string>>);
    D<string, List <string>> test;
    test = (D<string, List <string>>)
        Delegate.CreateDelegate(dType, bound);
    
    List<string> list3 = test(arr);
    Console.WriteLine("The first element is: {0}", list3[0]);
    
    Dim dType As Type = GetType(D(Of String, List(Of String)))
    Dim test As D(Of String, List(Of String))
    test = CType( _
        [Delegate].CreateDelegate(dType, bound), _
        D(Of String, List(Of String)))
    
    Dim list3 As List(Of String) = test(arr)
    Console.WriteLine("The first element is: {0}", list3(0))
    
  4. 저장된 어셈블리를 참조하는 프로그램에서 내보낸 메서드를 호출할 수도 있습니다.

예시

다음 코드 예제에서는 제네릭 메서드 Factory를 사용하여 제네릭이 아닌 형식 DemoType을 만듭니다. 이 메서드에는 두 개의 제네릭 형식 매개 변수가 있습니다. TInput은 입력 형식을 지정하고 TOutput은 출력 형식을 지정합니다. TOutput 형식 매개 변수는 ICollection<TInput>(Visual Basic에서는 ICollection(Of TInput))을 매개 변수가 없는 생성자를 갖는 참조 형식으로 구현하도록 제한됩니다.

메서드에는 TInput 배열인 하나의 공식 매개 변수가 있습니다. 메서드는 입력 배열의 모든 요소를 포함하는 TOutput 인스턴스를 반환합니다. TOutputICollection<T> 제네릭 인터페이스를 구현하는 제네릭 컬렉션 형식일 수 있습니다.

코드를 실행하면 동적 어셈블리가 DemoGenericMethod1.dll로 저장되며 Ildasm.exe(IL 디스어셈블러)를 사용하여 검사할 수 있습니다.

참고 항목

코드를 내보내는 방법을 알아보는 좋은 방법은 내보내려는 작업을 수행하는 프로그램을 작성하고 디스어셈블러를 사용하여 컴파일러에서 생성된 CIL을 검사하는 것입니다.

코드 예제에는 내보낸 메서드와 동일한 소스 코드가 포함됩니다. 내보낸 메서드는 런타임에 바인딩되고 코드 예제에서 선언된 제네릭 대리자를 사용하여 호출됩니다.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

// Declare a generic delegate that can be used to execute the
// finished method.
//
public delegate TOut D<TIn, TOut>(TIn[] input);

class GenericMethodBuilder
{
    // This method shows how to declare, in Visual Basic, the generic
    // method this program emits. The method has two type parameters,
    // TInput and TOutput, the second of which must be a reference type
    // (class), must have a parameterless constructor (new()), and must
    // implement ICollection<TInput>. This interface constraint
    // ensures that ICollection<TInput>.Add can be used to add
    // elements to the TOutput object the method creates. The method
    // has one formal parameter, input, which is an array of TInput.
    // The elements of this array are copied to the new TOutput.
    //
    public static TOutput Factory<TInput, TOutput>(TInput[] tarray)
        where TOutput : class, ICollection<TInput>, new()
    {
        TOutput ret = new TOutput();
        ICollection<TInput> ic = ret;

        foreach (TInput t in tarray)
        {
            ic.Add(t);
        }
        return ret;
    }

    public static void Main()
    {
        // The following shows the usage syntax of the C#
        // version of the generic method emitted by this program.
        // Note that the generic parameters must be specified
        // explicitly, because the compiler does not have enough
        // context to infer the type of TOutput. In this case, TOutput
        // is a generic List containing strings.
        //
        string[] arr = {"a", "b", "c", "d", "e"};
        List<string> list1 =
            GenericMethodBuilder.Factory<string, List <string>>(arr);
        Console.WriteLine("The first element is: {0}", list1[0]);

        // Creating a dynamic assembly requires an AssemblyName
        // object, and the current application domain.
        //
        AssemblyName asmName = new AssemblyName("DemoMethodBuilder1");
        AppDomain domain = AppDomain.CurrentDomain;
        AssemblyBuilder demoAssembly =
            domain.DefineDynamicAssembly(asmName,
                AssemblyBuilderAccess.RunAndSave);

        // Define the module that contains the code. For an
        // assembly with one module, the module name is the
        // assembly name plus a file extension.
        ModuleBuilder demoModule =
            demoAssembly.DefineDynamicModule(asmName.Name,
                asmName.Name+".dll");

        // Define a type to contain the method.
        TypeBuilder demoType =
            demoModule.DefineType("DemoType", TypeAttributes.Public);

        // Define a public static method with standard calling
        // conventions. Do not specify the parameter types or the
        // return type, because type parameters will be used for
        // those types, and the type parameters have not been
        // defined yet.
        //
        MethodBuilder factory =
            demoType.DefineMethod("Factory",
                MethodAttributes.Public | MethodAttributes.Static);

        // Defining generic type parameters for the method makes it a
        // generic method. To make the code easier to read, each
        // type parameter is copied to a variable of the same name.
        //
        string[] typeParameterNames = {"TInput", "TOutput"};
        GenericTypeParameterBuilder[] typeParameters =
            factory.DefineGenericParameters(typeParameterNames);

        GenericTypeParameterBuilder TInput = typeParameters[0];
        GenericTypeParameterBuilder TOutput = typeParameters[1];

        // Add special constraints.
        // The type parameter TOutput is constrained to be a reference
        // type, and to have a parameterless constructor. This ensures
        // that the Factory method can create the collection type.
        //
        TOutput.SetGenericParameterAttributes(
            GenericParameterAttributes.ReferenceTypeConstraint |
            GenericParameterAttributes.DefaultConstructorConstraint);

        // Add interface and base type constraints.
        // The type parameter TOutput is constrained to types that
        // implement the ICollection<T> interface, to ensure that
        // they have an Add method that can be used to add elements.
        //
        // To create the constraint, first use MakeGenericType to bind
        // the type parameter TInput to the ICollection<T> interface,
        // returning the type ICollection<TInput>, then pass
        // the newly created type to the SetInterfaceConstraints
        // method. The constraints must be passed as an array, even if
        // there is only one interface.
        //
        Type icoll = typeof(ICollection<>);
        Type icollOfTInput = icoll.MakeGenericType(TInput);
        Type[] constraints = {icollOfTInput};
        TOutput.SetInterfaceConstraints(constraints);

        // Set parameter types for the method. The method takes
        // one parameter, an array of type TInput.
        Type[] parms = {TInput.MakeArrayType()};
        factory.SetParameters(parms);

        // Set the return type for the method. The return type is
        // the generic type parameter TOutput.
        factory.SetReturnType(TOutput);

        // Generate a code body for the method.
        // -----------------------------------
        // Get a code generator and declare local variables and
        // labels. Save the input array to a local variable.
        //
        ILGenerator ilgen = factory.GetILGenerator();

        LocalBuilder retVal = ilgen.DeclareLocal(TOutput);
        LocalBuilder ic = ilgen.DeclareLocal(icollOfTInput);
        LocalBuilder input = ilgen.DeclareLocal(TInput.MakeArrayType());
        LocalBuilder index = ilgen.DeclareLocal(typeof(int));

        Label enterLoop = ilgen.DefineLabel();
        Label loopAgain = ilgen.DefineLabel();

        ilgen.Emit(OpCodes.Ldarg_0);
        ilgen.Emit(OpCodes.Stloc_S, input);

        // Create an instance of TOutput, using the generic method
        // overload of the Activator.CreateInstance method.
        // Using this overload requires the specified type to have
        // a parameterless constructor, which is the reason for adding
        // that constraint to TOutput. Create the constructed generic
        // method by passing TOutput to MakeGenericMethod. After
        // emitting code to call the method, emit code to store the
        // new TOutput in a local variable.
        //
        MethodInfo createInst =
            typeof(Activator).GetMethod("CreateInstance", Type.EmptyTypes);
        MethodInfo createInstOfTOutput =
            createInst.MakeGenericMethod(TOutput);

        ilgen.Emit(OpCodes.Call, createInstOfTOutput);
        ilgen.Emit(OpCodes.Stloc_S, retVal);

        // Load the reference to the TOutput object, cast it to
        // ICollection<TInput>, and save it.
        //
        ilgen.Emit(OpCodes.Ldloc_S, retVal);
        ilgen.Emit(OpCodes.Box, TOutput);
        ilgen.Emit(OpCodes.Castclass, icollOfTInput);
        ilgen.Emit(OpCodes.Stloc_S, ic);

        // Loop through the array, adding each element to the new
        // instance of TOutput. Note that in order to get a MethodInfo
        // for ICollection<TInput>.Add, it is necessary to first
        // get the Add method for the generic type defintion,
        // ICollection<T>.Add. This is because it is not possible
        // to call GetMethod on icollOfTInput. The static overload of
        // TypeBuilder.GetMethod produces the correct MethodInfo for
        // the constructed type.
        //
        MethodInfo mAddPrep = icoll.GetMethod("Add");
        MethodInfo mAdd = TypeBuilder.GetMethod(icollOfTInput, mAddPrep);

        // Initialize the count and enter the loop.
        ilgen.Emit(OpCodes.Ldc_I4_0);
        ilgen.Emit(OpCodes.Stloc_S, index);
        ilgen.Emit(OpCodes.Br_S, enterLoop);

        // Mark the beginning of the loop. Push the ICollection
        // reference on the stack, so it will be in position for the
        // call to Add. Then push the array and the index on the
        // stack, get the array element, and call Add (represented
        // by the MethodInfo mAdd) to add it to the collection.
        //
        // The other ten instructions just increment the index
        // and test for the end of the loop. Note the MarkLabel
        // method, which sets the point in the code where the
        // loop is entered. (See the earlier Br_S to enterLoop.)
        //
        ilgen.MarkLabel(loopAgain);

        ilgen.Emit(OpCodes.Ldloc_S, ic);
        ilgen.Emit(OpCodes.Ldloc_S, input);
        ilgen.Emit(OpCodes.Ldloc_S, index);
        ilgen.Emit(OpCodes.Ldelem, TInput);
        ilgen.Emit(OpCodes.Callvirt, mAdd);

        ilgen.Emit(OpCodes.Ldloc_S, index);
        ilgen.Emit(OpCodes.Ldc_I4_1);
        ilgen.Emit(OpCodes.Add);
        ilgen.Emit(OpCodes.Stloc_S, index);

        ilgen.MarkLabel(enterLoop);
        ilgen.Emit(OpCodes.Ldloc_S, index);
        ilgen.Emit(OpCodes.Ldloc_S, input);
        ilgen.Emit(OpCodes.Ldlen);
        ilgen.Emit(OpCodes.Conv_I4);
        ilgen.Emit(OpCodes.Clt);
        ilgen.Emit(OpCodes.Brtrue_S, loopAgain);

        ilgen.Emit(OpCodes.Ldloc_S, retVal);
        ilgen.Emit(OpCodes.Ret);

        // Complete the type.
        Type dt = demoType.CreateType();
        // Save the assembly, so it can be examined with Ildasm.exe.
        demoAssembly.Save(asmName.Name+".dll");

        // To create a constructed generic method that can be
        // executed, first call the GetMethod method on the completed
        // type to get the generic method definition. Call MakeGenericType
        // on the generic method definition to obtain the constructed
        // method, passing in the type arguments. In this case, the
        // constructed method has string for TInput and List<string>
        // for TOutput.
        //
        MethodInfo m = dt.GetMethod("Factory");
        MethodInfo bound =
            m.MakeGenericMethod(typeof(string), typeof(List<string>));

        // Display a string representing the bound method.
        Console.WriteLine(bound);

        // Once the generic method is constructed,
        // you can invoke it and pass in an array of objects
        // representing the arguments. In this case, there is only
        // one element in that array, the argument 'arr'.
        //
        object o = bound.Invoke(null, new object[]{arr});
        List<string> list2 = (List<string>) o;

        Console.WriteLine("The first element is: {0}", list2[0]);

        // You can get better performance from multiple calls if
        // you bind the constructed method to a delegate. The
        // following code uses the generic delegate D defined
        // earlier.
        //
        Type dType = typeof(D<string, List <string>>);
        D<string, List <string>> test;
        test = (D<string, List <string>>)
            Delegate.CreateDelegate(dType, bound);

        List<string> list3 = test(arr);
        Console.WriteLine("The first element is: {0}", list3[0]);
    }
}

/* This code example produces the following output:

The first element is: a
System.Collections.Generic.List`1[System.String] Factory[String,List`1](System.String[])
The first element is: a
The first element is: a
 */
Imports System.Collections.Generic
Imports System.Reflection
Imports System.Reflection.Emit

' Declare a generic delegate that can be used to execute the 
' finished method.
'
Delegate Function D(Of TIn, TOut)(ByVal input() As TIn) As TOut

Class GenericMethodBuilder

    ' This method shows how to declare, in Visual Basic, the generic
    ' method this program emits. The method has two type parameters,
    ' TInput and TOutput, the second of which must be a reference type
    ' (Class), must have a parameterless constructor (New), and must
    ' implement ICollection(Of TInput). This interface constraint
    ' ensures that ICollection(Of TInput).Add can be used to add
    ' elements to the TOutput object the method creates. The method 
    ' has one formal parameter, input, which is an array of TInput. 
    ' The elements of this array are copied to the new TOutput.
    '
    Public Shared Function Factory(Of TInput, _
        TOutput As {ICollection(Of TInput), Class, New}) _
        (ByVal input() As TInput) As TOutput

        Dim retval As New TOutput()
        Dim ic As ICollection(Of TInput) = retval

        For Each t As TInput In input
            ic.Add(t)
        Next

        Return retval
    End Function


    Public Shared Sub Main()
        ' The following shows the usage syntax of the Visual Basic
        ' version of the generic method emitted by this program.
        ' Note that the generic parameters must be specified 
        ' explicitly, because the compiler does not have enough 
        ' context to infer the type of TOutput. In this case, TOutput
        ' is a generic List containing strings.
        ' 
        Dim arr() As String = {"a", "b", "c", "d", "e"}
        Dim list1 As List(Of String) = _
            GenericMethodBuilder.Factory(Of String, List(Of String))(arr)
        Console.WriteLine("The first element is: {0}", list1(0))


        ' Creating a dynamic assembly requires an AssemblyName
        ' object, and the current application domain.
        '
        Dim asmName As New AssemblyName("DemoMethodBuilder1")
        Dim domain As AppDomain = AppDomain.CurrentDomain
        Dim demoAssembly As AssemblyBuilder = _
            domain.DefineDynamicAssembly(asmName, _
                AssemblyBuilderAccess.RunAndSave)

        ' Define the module that contains the code. For an 
        ' assembly with one module, the module name is the 
        ' assembly name plus a file extension.
        Dim demoModule As ModuleBuilder = _
            demoAssembly.DefineDynamicModule( _
                asmName.Name, _
                asmName.Name & ".dll")

        ' Define a type to contain the method.
        Dim demoType As TypeBuilder = demoModule.DefineType( _
            "DemoType", _
            TypeAttributes.Public)

        ' Define a Shared, Public method with standard calling
        ' conventions. Do not specify the parameter types or the
        ' return type, because type parameters will be used for 
        ' those types, and the type parameters have not been
        ' defined yet.
        '
        Dim factory As MethodBuilder = _
            demoType.DefineMethod("Factory", _
                MethodAttributes.Public Or MethodAttributes.Static)

        ' Defining generic type parameters for the method makes it a
        ' generic method. To make the code easier to read, each
        ' type parameter is copied to a variable of the same name.
        '
        Dim typeParameterNames() As String = {"TInput", "TOutput"}
        Dim typeParameters() As GenericTypeParameterBuilder = _
            factory.DefineGenericParameters(typeParameterNames)

        Dim TInput As GenericTypeParameterBuilder = typeParameters(0)
        Dim TOutput As GenericTypeParameterBuilder = typeParameters(1)

        ' Add special constraints.
        ' The type parameter TOutput is constrained to be a reference
        ' type, and to have a parameterless constructor. This ensures
        ' that the Factory method can create the collection type.
        ' 
        TOutput.SetGenericParameterAttributes( _
            GenericParameterAttributes.ReferenceTypeConstraint Or _
            GenericParameterAttributes.DefaultConstructorConstraint)

        ' Add interface and base type constraints.
        ' The type parameter TOutput is constrained to types that
        ' implement the ICollection(Of T) interface, to ensure that
        ' they have an Add method that can be used to add elements.
        '
        ' To create the constraint, first use MakeGenericType to bind 
        ' the type parameter TInput to the ICollection(Of T) interface,
        ' returning the type ICollection(Of TInput), then pass
        ' the newly created type to the SetInterfaceConstraints
        ' method. The constraints must be passed as an array, even if
        ' there is only one interface.
        '
        Dim icoll As Type = GetType(ICollection(Of ))
        Dim icollOfTInput As Type = icoll.MakeGenericType(TInput)
        Dim constraints() As Type = {icollOfTInput}
        TOutput.SetInterfaceConstraints(constraints)

        ' Set parameter types for the method. The method takes
        ' one parameter, an array of type TInput.
        Dim params() As Type = {TInput.MakeArrayType()}
        factory.SetParameters(params)

        ' Set the return type for the method. The return type is
        ' the generic type parameter TOutput.
        factory.SetReturnType(TOutput)

        ' Generate a code body for the method. 
        ' -----------------------------------
        ' Get a code generator and declare local variables and
        ' labels. Save the input array to a local variable.
        '
        Dim ilgen As ILGenerator = factory.GetILGenerator()

        Dim retVal As LocalBuilder = ilgen.DeclareLocal(TOutput)
        Dim ic As LocalBuilder = ilgen.DeclareLocal(icollOfTInput)
        Dim input As LocalBuilder = _
            ilgen.DeclareLocal(TInput.MakeArrayType())
        Dim index As LocalBuilder = _
            ilgen.DeclareLocal(GetType(Integer))

        Dim enterLoop As Label = ilgen.DefineLabel()
        Dim loopAgain As Label = ilgen.DefineLabel()

        ilgen.Emit(OpCodes.Ldarg_0)
        ilgen.Emit(OpCodes.Stloc_S, input)

        ' Create an instance of TOutput, using the generic method 
        ' overload of the Activator.CreateInstance method. 
        ' Using this overload requires the specified type to have
        ' a parameterless constructor, which is the reason for adding 
        ' that constraint to TOutput. Create the constructed generic
        ' method by passing TOutput to MakeGenericMethod. After
        ' emitting code to call the method, emit code to store the
        ' new TOutput in a local variable. 
        '
        Dim createInst As MethodInfo = _
            GetType(Activator).GetMethod("CreateInstance", Type.EmptyTypes)
        Dim createInstOfTOutput As MethodInfo = _
            createInst.MakeGenericMethod(TOutput)

        ilgen.Emit(OpCodes.Call, createInstOfTOutput)
        ilgen.Emit(OpCodes.Stloc_S, retVal)

        ' Load the reference to the TOutput object, cast it to
        ' ICollection(Of TInput), and save it.
        ilgen.Emit(OpCodes.Ldloc_S, retVal)
        ilgen.Emit(OpCodes.Box, TOutput)
        ilgen.Emit(OpCodes.Castclass, icollOfTInput)
        ilgen.Emit(OpCodes.Stloc_S, ic)

        ' Loop through the array, adding each element to the new
        ' instance of TOutput. Note that in order to get a MethodInfo
        ' for ICollection(Of TInput).Add, it is necessary to first 
        ' get the Add method for the generic type defintion,
        ' ICollection(Of T).Add. This is because it is not possible
        ' to call GetMethod on icollOfTInput. The static overload of
        ' TypeBuilder.GetMethod produces the correct MethodInfo for
        ' the constructed type.
        '
        Dim mAddPrep As MethodInfo = icoll.GetMethod("Add")
        Dim mAdd As MethodInfo = _
            TypeBuilder.GetMethod(icollOfTInput, mAddPrep)

        ' Initialize the count and enter the loop.
        ilgen.Emit(OpCodes.Ldc_I4_0)
        ilgen.Emit(OpCodes.Stloc_S, index)
        ilgen.Emit(OpCodes.Br_S, enterLoop)

        ' Mark the beginning of the loop. Push the ICollection
        ' reference on the stack, so it will be in position for the
        ' call to Add. Then push the array and the index on the 
        ' stack, get the array element, and call Add (represented
        ' by the MethodInfo mAdd) to add it to the collection.
        ' 
        ' The other ten instructions just increment the index
        ' and test for the end of the loop. Note the MarkLabel
        ' method, which sets the point in the code where the 
        ' loop is entered. (See the earlier Br_S to enterLoop.)
        '
        ilgen.MarkLabel(loopAgain)

        ilgen.Emit(OpCodes.Ldloc_S, ic)
        ilgen.Emit(OpCodes.Ldloc_S, input)
        ilgen.Emit(OpCodes.Ldloc_S, index)
        ilgen.Emit(OpCodes.Ldelem, TInput)
        ilgen.Emit(OpCodes.Callvirt, mAdd)

        ilgen.Emit(OpCodes.Ldloc_S, index)
        ilgen.Emit(OpCodes.Ldc_I4_1)
        ilgen.Emit(OpCodes.Add)
        ilgen.Emit(OpCodes.Stloc_S, index)

        ilgen.MarkLabel(enterLoop)
        ilgen.Emit(OpCodes.Ldloc_S, index)
        ilgen.Emit(OpCodes.Ldloc_S, input)
        ilgen.Emit(OpCodes.Ldlen)
        ilgen.Emit(OpCodes.Conv_I4)
        ilgen.Emit(OpCodes.Clt)
        ilgen.Emit(OpCodes.Brtrue_S, loopAgain)

        ilgen.Emit(OpCodes.Ldloc_S, retVal)
        ilgen.Emit(OpCodes.Ret)

        ' Complete the type.
        Dim dt As Type = demoType.CreateType()
        ' Save the assembly, so it can be examined with Ildasm.exe.
        demoAssembly.Save(asmName.Name & ".dll")

        ' To create a constructed generic method that can be
        ' executed, first call the GetMethod method on the completed 
        ' type to get the generic method definition. Call MakeGenericType
        ' on the generic method definition to obtain the constructed
        ' method, passing in the type arguments. In this case, the
        ' constructed method has String for TInput and List(Of String)
        ' for TOutput. 
        '
        Dim m As MethodInfo = dt.GetMethod("Factory")
        Dim bound As MethodInfo = m.MakeGenericMethod( _
            GetType(String), GetType(List(Of String)))

        ' Display a string representing the bound method.
        Console.WriteLine(bound)


        ' Once the generic method is constructed, 
        ' you can invoke it and pass in an array of objects 
        ' representing the arguments. In this case, there is only
        ' one element in that array, the argument 'arr'.
        '
        Dim o As Object = bound.Invoke(Nothing, New Object() {arr})
        Dim list2 As List(Of String) = CType(o, List(Of String))

        Console.WriteLine("The first element is: {0}", list2(0))


        ' You can get better performance from multiple calls if
        ' you bind the constructed method to a delegate. The 
        ' following code uses the generic delegate D defined 
        ' earlier.
        '
        Dim dType As Type = GetType(D(Of String, List(Of String)))
        Dim test As D(Of String, List(Of String))
        test = CType( _
            [Delegate].CreateDelegate(dType, bound), _
            D(Of String, List(Of String)))

        Dim list3 As List(Of String) = test(arr)
        Console.WriteLine("The first element is: {0}", list3(0))

    End Sub
End Class

' This code example produces the following output:
'
'The first element is: a
'System.Collections.Generic.List`1[System.String] Factory[String,List`1](System.String[])
'The first element is: a
'The first element is: a

참고 항목