Aracılığıyla paylaş


Nasıl yapılır: Yansıma yayma ile genel bir yöntem tanımlama

İlk yordamda iki tür parametresiyle basit bir genel yöntemin nasıl oluşturulacağı ve tür parametrelerine sınıf kısıtlamaları, arabirim kısıtlamaları ve özel kısıtlamaların nasıl uygulanacağı gösterilir.

İkinci yordamda yöntem gövdesinin nasıl yayileceği ve genel türlerin örneklerini oluşturmak ve yöntemlerini çağırmak için genel yöntemin tür parametrelerinin nasıl kullanılacağı gösterilmektedir.

Üçüncü yordam, genel yöntemin nasıl çağrılılacağını gösterir.

Önemli

Bir yöntem yalnızca genel bir türe ait olduğundan ve bu türün tür parametrelerini kullandığından genel değildir. Bir yöntem yalnızca kendi tür parametre listesi varsa geneldir. Genel bir yöntem, bu örnekte olduğu gibi, genel olmayan bir türde görünebilir. Genel bir türdeki genel olmayan yöntem örneği için bkz . Nasıl yapılır: Yansıma Yayma ile Genel Tür Tanımlama.

Genel bir yöntem tanımlama

  1. Başlamadan önce, genel yöntemin üst düzey bir dil kullanılarak yazıldığında nasıl göründüğüne bakmak yararlıdır. Aşağıdaki kod, bu makalenin örnek koduna ve genel yöntemi çağırmaya yönelik koda eklenmiştir. Bu yöntem iki tür parametresi vardır, TInput ve TOutput, bunlardan ikincisi bir başvuru türü (class), bir parametresiz oluşturucuya (new) sahip olmalıdır ve ICollection<TInput> uygulaması gerekir. Bu arabirim kısıtlaması, ICollection<T>.Add tarafından oluşturulan koleksiyona öğe eklemek için TOutput yönteminin kullanılabilmesini sağlar. Yöntemin bir tane resmi parametresi vardır, input, bu bir TInput dizisidir. Yöntem, TOutput türünde bir koleksiyon oluşturur ve input öğelerini bu koleksiyona kopyalar.

    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. Genel yöntemin ait olduğu türü içerecek dinamik bir derleme ve dinamik modül tanımlayın. Bu durumda, derlemenin adlı DemoMethodBuilder1tek bir modülü vardır ve modül adı derleme adıyla ve bir uzantıyla aynıdır. Bu örnekte, derleme diske kaydedilir ve ayrıca yürütülür, bu nedenle AssemblyBuilderAccess.RunAndSave belirtilir. DemoMethodBuilder1.dll incelemek ve 1. adımda gösterilen yöntemin ortak ara diliyle (CIL) karşılaştırmak içinIldasm.exe (IL Disassembler) kullanabilirsiniz.

    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. Genel yöntemin ait olduğu türü tanımlayın. Türün genel olması gerekmez. Genel bir yöntem genel veya genel olmayan bir türe ait olabilir. Bu örnekte tür bir sınıftır, genel değildir ve olarak adlandırılır DemoType.

    TypeBuilder demoType =
        demoModule.DefineType("DemoType", TypeAttributes.Public);
    
    Dim demoType As TypeBuilder = demoModule.DefineType( _
        "DemoType", _
        TypeAttributes.Public)
    
  4. Genel yöntemi tanımlayın. Genel bir yöntemin resmi parametrelerinin türleri genel yöntemin genel tür parametreleri tarafından belirtiliyorsa, yöntemi tanımlamak için yöntem aşırı yüklemesini kullanın DefineMethod(String, MethodAttributes) . Yönteminin genel tür parametreleri henüz tanımlanmamıştır, bu nedenle çağrısında DefineMethodyöntemin resmi parametrelerinin türlerini belirtemezsiniz. Bu örnekte yönteminin adı Factoryverilmiştir. Yöntem Public ve static'dir (Shared ise Visual Basic'te).

    MethodBuilder factory =
        demoType.DefineMethod("Factory",
            MethodAttributes.Public | MethodAttributes.Static);
    
    Dim factory As MethodBuilder = _
        demoType.DefineMethod("Factory", _
            MethodAttributes.Public Or MethodAttributes.Static)
    
  5. DemoMethod genel tür parametrelerini, parametrelerin adlarını içeren bir dizi dizeyi MethodBuilder.DefineGenericParameters yöntemine geçirerek tanımlayın. Bu, yöntemini genel bir yöntem yapar. Aşağıdaki kod, Factory'ı, TInput ve TOutput tür parametreleriyle bir genel yöntem yapar. Kodun daha kolay okunmasını sağlamak için, iki tür parametresini temsil eden nesneleri tutmak GenericTypeParameterBuilder için bu adlara sahip değişkenler oluşturulur.

    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. İsteğe bağlı olarak tür parametrelerine özel kısıtlamalar ekleyin. Yöntemi kullanılarak SetGenericParameterAttributes özel kısıtlamalar eklenir. Bu örnekte, TOutput bir başvuru türü ve parametresiz oluşturucuya sahip olmak için kısıtlanır.

    TOutput.SetGenericParameterAttributes(
        GenericParameterAttributes.ReferenceTypeConstraint |
        GenericParameterAttributes.DefaultConstructorConstraint);
    
    TOutput.SetGenericParameterAttributes( _
        GenericParameterAttributes.ReferenceTypeConstraint Or _
        GenericParameterAttributes.DefaultConstructorConstraint)
    
  7. İsteğe bağlı olarak tür parametrelerine sınıf ve arabirim kısıtlamaları ekleyin. Bu örnekte tür parametresi TOutput , (ICollection(Of TInput) C#'de) arabirimini ICollection<TInput> uygulayan türlerde kısıtlanır. Bu, yöntemin Add öğeleri eklemek için kullanılabilmesini sağlar.

    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. yöntemini kullanarak SetParameters yönteminin resmi parametrelerini tanımlayın. Bu örnekte, Factory yönteminin bir parametresi vardır, bu da bir TInput dizisidir. Bu tür, MakeArrayType öğesini temsil eden GenericTypeParameterBuilder üzerindeki TInput yönteminin çağrılmasıyla oluşturulur. Bu SetParameters bağımsız değişkeni, Type nesnelerinin bir dizisidir.

    Type[] parms = {TInput.MakeArrayType()};
    factory.SetParameters(parms);
    
    Dim params() As Type = {TInput.MakeArrayType()}
    factory.SetParameters(params)
    
  9. yöntemini kullanarak SetReturnType yöntemin dönüş türünü tanımlayın. Bu örnekte TOutput döndürülür.

    factory.SetReturnType(TOutput);
    
    factory.SetReturnType(TOutput)
    
  10. Yöntem gövdesini ILGenerator kullanarak oluştur. Ayrıntılar için, yöntem gövdesini oluşturmaya yönelik eşlik eden prosedüre bakın.

    Önemli

    Genel türlerin metodlarına çağrılar gönderdiğinizde ve bu türlerin tip argümanları genel metodun tip parametreleri olduğunda, yöntemlerin oluşturulmuş formlarını elde etmek için staticGetConstructor(Type, ConstructorInfo), GetMethod(Type, MethodInfo), ve GetField(Type, FieldInfo) metod aşırı yüklemelerini içeren TypeBuilder sınıfını kullanmanız gerekir. Yöntem gövdesini yaymaya yönelik eşlik eden yordam bunu gösterir.

  11. Yöntemi içeren türü tamamlayın ve bütünleşik dosyayı kaydedin. Genel yöntemi çağırmak için eşlik eden yordam, tamamlanmış yöntemi çağırmanın iki yolunu gösterir.

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

Yöntem gövdesini oluştur.

  1. Bir kod oluşturucu alın ve yerel değişkenleri ve etiketleri bildirin. DeclareLocal yöntemi, yerel değişkenleri bildirmek için kullanılır. Factory yönteminin dört yerel değişkeni vardır: retVal tarafından döndürülen yeni TOutput'yi tutmak için, ic'ün TOutput olarak dönüştürüldüğünde ICollection<TInput>'ü tutmak için, input nesnelerinin giriş dizisini saklamak için ve TInput ile bu diziyi döngüyle gözden geçirmek için. Yöntemin, enterLoop yöntemi kullanılarak tanımlanan, biri döngüye (loopAgain) girmek için, diğeri de döngünün üst kısmı için (DefineLabel) olmak üzere iki etiketi vardır.

    Yöntemin yaptığı ilk şey, Ldarg_0 opcode kullanılarak bağımsız değişkenini yüklemek ve input opcode kullanılarak bu veriyi yerel değişkende Stloc_S depolamaktır.

    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. TOutput yönteminin genel yöntem aşırı yüklemesini kullanarak, Activator.CreateInstance örneği oluşturmak için kod oluşturun. Bu aşırı yüklemenin kullanılması, belirtilen türün parametresiz bir oluşturucuya sahip olmasını gerektirir. Bu, bu kısıtlamayı öğesine TOutputeklemenin nedenidir. TOutput'yi MakeGenericMethod'e geçirerek genel yöntemi oluşturun. yöntemi çağırmak için kod oluşturduktan sonra, retVal kullanarak onu yerel değişkende Stloc_S depolamak için kod oluştur

    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. Yeni TOutput nesnesini ICollection(Of TInput) türüne dönüştüren kodu yayımlayın ve yerel değişken ic'da saklayın.

    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. MethodInfo yöntemini temsil eden bir ICollection<T>.Add alın. Yöntem, ICollection<TInput> üzerinde etkide bulunur, bu nedenle bu yapı türüne özgü Add yöntemi almak gerekir. GetMethod yöntemini kullanarak bu MethodInfo öğesini icollOfTInput'den doğrudan alamazsınız, çünkü GetMethod, GenericTypeParameterBuilder ile oluşturulmuş bir tür üzerinde desteklenmez. Bunun yerine, GetMethod üzerinde icoll çağrısı yapın, bu ICollection<T> genel arabirimi için genel tür tanımını içerir. Ardından, oluşturulan tür için GetMethod(Type, MethodInfo) öğesini üretmek amacıyla staticMethodInfo yöntemini kullanın. Aşağıdaki kod bunu gösterir.

    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 bitlik bir tamsayı 0 yükleyip değişkende depolayarak index değişkenini başlatmak için kod oluşturun. Etiket enterLoop'ye dallanacak şekilde kod üret. Bu etiket, döngünün içinde olduğundan henüz işaretlenmedi. Döngünün kodu bir sonraki adımda gönderilir.

    // 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. Döngü için kod üret. İlk adım, MarkLabel etiketini loopAgain ile çağırarak döngünün üst kısmını işaretlemektir. Etiketi kullanan dal deyimleri artık kodda bu noktaya dallanır. Sonraki adım, TOutput nesnesini ICollection(Of TInput) türüne dönüştürerek yığına eklemektir. Hemen gerekli değildir, ancak Add yöntemini çağırmak için uygun konumda olması gerekir. Ardından giriş dizisi yığına gönderilir ve ardından index geçerli dizini içeren değişken diziye gönderilir. İşlem Ldelem kodu, dizini ve diziyi yığından çıkarır ve dizine alınmış dizi öğesini yığına ekler. Yığın, koleksiyonu ve yeni öğeyi yığından çıkararak öğeyi koleksiyona ekleyen ICollection<T>.Add metodunu çağırmaya hazırdır.

    Döngüdeki kodun geri kalanı, dizini artırır ve döngünün tamamlanıp tamamlanmadığını test eder: Dizin ve 32 bit tamsayı 1 yığına eklenir ve toplanır, sonuç yığında bırakılır; bu toplam index içinde depolanır. MarkLabel bu noktayı döngünün giriş noktası olarak ayarlamak için çağrılır. Dizin yeniden yüklenir. Giriş dizisi yığına itilir ve Ldlen uzunluğunu elde etmek için tetiklenir. Dizin ve uzunluk artık yığındadır ve Clt bunları karşılaştırmak için gönderilir. Dizinin uzunluğundan küçükse, Brtrue_S döngünün başına geri döner.

    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 nesneyi yığına itmek ve metottan dönmek için kod üretin. Yerel değişkenler retVal ve ic her ikisi de yeni TOutputöğesine başvurular içerir; ic yalnızca yöntemine ICollection<T>.Add erişmek için kullanılır.

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

Genel yöntemi çağırma

  1. Factory genel bir yöntem tanımıdır. Çağırmak için türleri genel tür parametrelerine atamanız gerekir. Bu işi yapmak için MakeGenericMethod yöntemini kullanın. Aşağıdaki kod, String ve TInput (List(Of String) C#'de) için List<string> ve TOutput belirterek oluşturulan bir genel yöntemi meydana getirir ve yöntemin dize temsilini görüntüler.

    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. Late-bound yöntemi çağırmak için Invoke yöntemini kullanın. Aşağıdaki kod, tek öğesi olarak dize dizisi içeren bir dizisi Objectoluşturur ve bunu genel yöntemin bağımsız değişken listesi olarak geçirir. İlk parametre bir null referanstır çünkü Invoke yöntemi static. Dönüş değeri List(Of String) türüne dönüştürülür ve ilk elemanı görüntülenir.

    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))
    
  3. Yöntemini bir temsilci kullanarak çağırmak için, oluşturduğunuz genel yöntemin imzası ile eşleşen bir temsilciniz olmalıdır. Bunu yapmak için genel bir temsilci oluşturmak kolay bir yoldur. Aşağıdaki kod, yöntem aşırı yüklemesini kullanarak örnek kodda tanımlanan genel temsilcinin DDelegate.CreateDelegate(Type, MethodInfo) bir örneğini oluşturur ve temsilciyi çağırır. Temsilciler geç bağlanan çağrılardan daha iyi performans gösterir.

    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))
    
  4. Emit edilen yöntem, kaydedilen derlemeye başvuran bir programdan da çağrılabilir.

Örnek

Aşağıdaki kod örneği, DemoType adlı jenerik bir yöntemle Factory adlı jenerik olmayan bir tür oluşturur. Bu yöntem, TInput bir giriş türü belirtmek ve TOutput bir çıkış türü belirtmek için iki genel tür parametresine sahiptir. TOutput tür parametresi, (ICollection<TInput> Visual Basic'te) ICollection(Of TInput) uygulamasını yapma, bir başvuru türü olma ve parametresiz bir oluşturucuya sahip olma konularında kısıtlanmıştır.

Yöntemin, TInput olan bir dizi şeklinde tek bir resmi parametresi vardır. yöntemi, giriş dizisinin TOutput tüm öğelerini içeren bir örneğini döndürür. TOutput genel arabirimi uygulayan ICollection<T> herhangi bir genel koleksiyon türü olabilir.

Kod çalıştırıldığında, dinamik derleme DemoGenericMethod1.dllolarak kaydedilir ve Ildasm.exe (IL Ayrıştırıcı) kullanılarak incelemeye uygundur.

Uyarı

Kod yaymayı öğrenmenin iyi bir yolu, yaymaya çalıştığınız görevi gerçekleştiren bir program yazmak ve derleyici tarafından üretilen CIL'yi incelemek için ayrıştırıcıyı kullanmaktır.

Kod örneği, yayılan yönteme eşdeğer kaynak kodu içerir. Yayılan yöntem, geç bağlı olarak ve ayrıca kod örneğinde tanımlanan genel bir delege kullanılarak çağrılır.

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

Ayrıca bkz.