Udostępnij za pośrednictwem


Jak definiować typ ogólny przy użyciu emitowania odbicia

W tym artykule pokazano, jak utworzyć prosty typ ogólny z dwoma parametrami typu, jak zastosować ograniczenia klas, ograniczenia interfejsu i specjalne ograniczenia do parametrów typu oraz jak utworzyć składowe, które używają parametrów typu klasy jako typów parametrów i zwracanych typów.

Ważne

Metoda nie jest ogólna, ponieważ należy do typu ogólnego i używa parametrów typu tego typu. Metoda jest ogólna tylko wtedy, gdy ma własną listę parametrów typu. Większość metod w typach ogólnych nie jest ogólna, jak w tym przykładzie. Aby zapoznać się z przykładem emitowania metody ogólnej, zobacz How to: Define a Generic Method with Reflection Emit.

Definiowanie typu ogólnego

  1. Zdefiniuj zestaw dynamiczny o nazwie GenericEmitExample1. W tym przykładzie zestaw jest wykonywany i zapisywany na dysku, więc AssemblyBuilderAccess.RunAndSave jest określony.

    AppDomain^ myDomain = AppDomain::CurrentDomain;
    AssemblyName^ myAsmName = gcnew AssemblyName( L"GenericEmitExample1" );
    AssemblyBuilder^ myAssembly = myDomain->DefineDynamicAssembly( 
        myAsmName, AssemblyBuilderAccess::RunAndSave );
    
    AppDomain myDomain = AppDomain.CurrentDomain;
    AssemblyName myAsmName = new AssemblyName("GenericEmitExample1");
    AssemblyBuilder myAssembly =
        myDomain.DefineDynamicAssembly(myAsmName,
            AssemblyBuilderAccess.RunAndSave);
    
    Dim myDomain As AppDomain = AppDomain.CurrentDomain
    Dim myAsmName As New AssemblyName("GenericEmitExample1")
    Dim myAssembly As AssemblyBuilder = myDomain.DefineDynamicAssembly( _
        myAsmName, _
        AssemblyBuilderAccess.RunAndSave)
    
  2. Zdefiniuj moduł dynamiczny. Zestaw składa się z modułów wykonywalnych. W przypadku zestawu z jednym modułem nazwa modułu jest taka sama jak nazwa zestawu, a nazwa pliku to nazwa modułu oraz rozszerzenie.

    ModuleBuilder^ myModule = myAssembly->DefineDynamicModule( 
        myAsmName->Name, String::Concat( myAsmName->Name, L".dll" ) );
    
    ModuleBuilder myModule =
        myAssembly.DefineDynamicModule(myAsmName.Name,
           myAsmName.Name + ".dll");
    
    Dim myModule As ModuleBuilder = myAssembly.DefineDynamicModule( _
        myAsmName.Name, _
        myAsmName.Name & ".dll")
    
  3. Zdefiniuj klasę. W tym przykładzie klasa nosi nazwę Sample.

    TypeBuilder^ myType = myModule->DefineType( L"Sample", 
        TypeAttributes::Public );
    
    TypeBuilder myType =
        myModule.DefineType("Sample", TypeAttributes.Public);
    
    Dim myType As TypeBuilder = myModule.DefineType( _
        "Sample", _
        TypeAttributes.Public)
    
  4. Zdefiniuj ogólne parametry typu Sample, przekazując tablicę ciągów zawierających nazwy parametrów do metody TypeBuilder.DefineGenericParameters. Dzięki temu klasa jest typem ogólnym. Wartość zwracana to tablica obiektów GenericTypeParameterBuilder reprezentujących parametry typu, które mogą być używane w emitowanym kodzie.

    W poniższym kodzie Sample staje się typem ogólnym z parametrami typu TFirst i TSecond. Aby ułatwić odczytywanie kodu, każdy GenericTypeParameterBuilder jest umieszczany w zmiennej o tej samej nazwie co parametr typu.

    array<String^>^typeParamNames = {L"TFirst",L"TSecond"};
    array<GenericTypeParameterBuilder^>^typeParams = 
        myType->DefineGenericParameters( typeParamNames );
    
    GenericTypeParameterBuilder^ TFirst = typeParams[0];
    GenericTypeParameterBuilder^ TSecond = typeParams[1];
    
    string[] typeParamNames = {"TFirst", "TSecond"};
    GenericTypeParameterBuilder[] typeParams =
        myType.DefineGenericParameters(typeParamNames);
    
    GenericTypeParameterBuilder TFirst = typeParams[0];
    GenericTypeParameterBuilder TSecond = typeParams[1];
    
    Dim typeParamNames() As String = {"TFirst", "TSecond"}
    Dim typeParams() As GenericTypeParameterBuilder = _
        myType.DefineGenericParameters(typeParamNames)
    
    Dim TFirst As GenericTypeParameterBuilder = typeParams(0)
    Dim TSecond As GenericTypeParameterBuilder = typeParams(1)
    
  5. Dodaj specjalne ograniczenia do parametrów typu. W tym przykładzie typ parametru TFirst jest ograniczony do typów, które mają konstruktory bez parametrów i do typów odwołań.

    TFirst->SetGenericParameterAttributes( 
        GenericParameterAttributes::DefaultConstructorConstraint | 
        GenericParameterAttributes::ReferenceTypeConstraint 
    );
    
    TFirst.SetGenericParameterAttributes(
        GenericParameterAttributes.DefaultConstructorConstraint |
        GenericParameterAttributes.ReferenceTypeConstraint);
    
    TFirst.SetGenericParameterAttributes( _
        GenericParameterAttributes.DefaultConstructorConstraint _
        Or GenericParameterAttributes.ReferenceTypeConstraint)
    
  6. Opcjonalnie dodaj ograniczenia klasy i interfejsu do parametrów typu. W tym przykładzie typ parametru TFirst jest ograniczony do typów, które pochodzą z klasy bazowej reprezentowanej przez obiekt Type zawarty w zmiennej baseType, i które implementują interfejsy, których typy znajdują się w zmiennych interfaceA i interfaceB. Zobacz przykładowy kod deklaracji i przypisania tych zmiennych.

    array<Type^>^interfaceTypes = { interfaceA, interfaceB };
    TSecond->SetInterfaceConstraints( interfaceTypes );
    TSecond->SetBaseTypeConstraint( baseType );
    
    TSecond.SetBaseTypeConstraint(baseType);
    Type[] interfaceTypes = {interfaceA, interfaceB};
    TSecond.SetInterfaceConstraints(interfaceTypes);
    
    TSecond.SetBaseTypeConstraint(baseType)
    Dim interfaceTypes() As Type = {interfaceA, interfaceB}
    TSecond.SetInterfaceConstraints(interfaceTypes)
    
  7. Zdefiniuj pole. W tym przykładzie typ pola jest określany przez parametr typu TFirst. GenericTypeParameterBuilder pochodzi z Type, dzięki czemu można używać parametrów typu ogólnego w dowolnym miejscu, w którym można użyć typu.

    FieldBuilder^ exField = 
        myType->DefineField("ExampleField", TFirst, 
            FieldAttributes::Private);
    
    FieldBuilder exField =
        myType.DefineField("ExampleField", TFirst,
            FieldAttributes.Private);
    
    Dim exField As FieldBuilder = _
        myType.DefineField("ExampleField", TFirst, _
            FieldAttributes.Private)
    
  8. Zdefiniuj metodę, która używa parametrów typu ogólnego. Należy pamiętać, że takie metody nie są ogólne, chyba że mają własne listy parametrów typu. Poniższy kod definiuje metodę static (Shared w języku Visual Basic), która przyjmuje tablicę TFirst i zwraca List<TFirst> (List(Of TFirst) w Visual Basic) zawierające wszystkie elementy tablicy. Aby zdefiniować tę metodę, należy utworzyć typ List<TFirst>, wywołując MakeGenericType dla definicji typu ogólnego, List<T>. (T zostanie pominięty podczas używania operatora typeof (GetType w Visual Basic), aby uzyskać definicję typu ogólnego. Typ parametru jest tworzony przy użyciu metody MakeArrayType.

    Type^ listOf = List::typeid;
    Type^ listOfTFirst = listOf->MakeGenericType(TFirst);
    array<Type^>^ mParamTypes = { TFirst->MakeArrayType() };
    
    MethodBuilder^ exMethod = 
        myType->DefineMethod("ExampleMethod", 
            MethodAttributes::Public | MethodAttributes::Static, 
            listOfTFirst, 
            mParamTypes);
    
    Type listOf = typeof(List<>);
    Type listOfTFirst = listOf.MakeGenericType(TFirst);
    Type[] mParamTypes = {TFirst.MakeArrayType()};
    
    MethodBuilder exMethod =
        myType.DefineMethod("ExampleMethod",
            MethodAttributes.Public | MethodAttributes.Static,
            listOfTFirst,
            mParamTypes);
    
    Dim listOf As Type = GetType(List(Of ))
    Dim listOfTFirst As Type = listOf.MakeGenericType(TFirst)
    Dim mParamTypes() As Type = {TFirst.MakeArrayType()}
    
    Dim exMethod As MethodBuilder = _
        myType.DefineMethod("ExampleMethod", _
            MethodAttributes.Public Or MethodAttributes.Static, _
            listOfTFirst, _
            mParamTypes)
    
  9. Wygeneruj treść metody. Treść metody składa się z trzech kodów operacyjnych, które ładują tablicę wejściową na stos, wywołują konstruktor List<TFirst>, który przyjmuje IEnumerable<TFirst> (odpowiedzialny za umieszczanie elementów wejściowych na liście), i zwracają (pozostawiając nowy obiekt List<T> na stosie). Najtrudniejszą częścią generowania tego kodu jest uzyskanie konstruktora.

    Metoda GetConstructor nie jest obsługiwana w GenericTypeParameterBuilder, więc nie można uzyskać konstruktora List<TFirst> bezpośrednio. Najpierw należy uzyskać konstruktor ogólnej definicji typu List<T>, a następnie wywołać metodę, która konwertuje go na odpowiedni konstruktor List<TFirst>.

    Konstruktor używany w tym przykładzie kodu przyjmuje IEnumerable<T>. Należy jednak pamiętać, że nie jest to ogólna definicja typu interfejsu ogólnego IEnumerable<T>; Zamiast tego parametr typu T z List<T> musi zostać zastąpiony parametrem typu TIEnumerable<T>. (Wydaje się to mylące tylko dlatego, że oba typy mają parametry typu o nazwie T. Dlatego w tym przykładzie kodu używane są nazwy TFirst i TSecond). Aby uzyskać typ argumentu konstruktora, zacznij od ogólnej definicji typu IEnumerable<T> i wywołaj MakeGenericType z pierwszym parametrem typu ogólnego List<T>. Lista argumentów konstruktora musi być przekazywana jako tablica z tylko jednym argumentem w tym przypadku.

    Uwaga

    Definicja typu ogólnego jest wyrażana jako IEnumerable<> w przypadku używania operatora typeof w języku C#lub IEnumerable(Of ) podczas korzystania z operatora GetType w języku Visual Basic.

    Teraz można uzyskać konstruktor List<T> przez wywołanie GetConstructor w definicji typu ogólnego. Aby przekonwertować ten konstruktor na odpowiadający konstruktor List<TFirst>, przekaż List<TFirst> oraz konstruktor z List<T> do statycznej metody TypeBuilder.GetConstructor(Type, ConstructorInfo).

    ILGenerator^ ilgen = exMethod->GetILGenerator();
         
    Type^ ienumOf = IEnumerable::typeid;
    Type^ TfromListOf = listOf->GetGenericArguments()[0];
    Type^ ienumOfT = ienumOf->MakeGenericType(TfromListOf);
    array<Type^>^ ctorArgs = {ienumOfT};
    
    ConstructorInfo^ ctorPrep = listOf->GetConstructor(ctorArgs);
    ConstructorInfo^ ctor = 
        TypeBuilder::GetConstructor(listOfTFirst, ctorPrep);
    
    ilgen->Emit(OpCodes::Ldarg_0);
    ilgen->Emit(OpCodes::Newobj, ctor);
    ilgen->Emit(OpCodes::Ret);
    
    ILGenerator ilgen = exMethod.GetILGenerator();
    
    Type ienumOf = typeof(IEnumerable<>);
    Type TfromListOf = listOf.GetGenericArguments()[0];
    Type ienumOfT = ienumOf.MakeGenericType(TfromListOf);
    Type[] ctorArgs = {ienumOfT};
    
    ConstructorInfo ctorPrep = listOf.GetConstructor(ctorArgs);
    ConstructorInfo ctor =
        TypeBuilder.GetConstructor(listOfTFirst, ctorPrep);
    
    ilgen.Emit(OpCodes.Ldarg_0);
    ilgen.Emit(OpCodes.Newobj, ctor);
    ilgen.Emit(OpCodes.Ret);
    
    Dim ilgen As ILGenerator = exMethod.GetILGenerator()
    
    Dim ienumOf As Type = GetType(IEnumerable(Of ))
    Dim listOfTParams() As Type = listOf.GetGenericArguments()
    Dim TfromListOf As Type = listOfTParams(0)
    Dim ienumOfT As Type = ienumOf.MakeGenericType(TfromListOf)
    Dim ctorArgs() As Type = {ienumOfT}
    
    Dim ctorPrep As ConstructorInfo = _
        listOf.GetConstructor(ctorArgs)
    Dim ctor As ConstructorInfo = _
        TypeBuilder.GetConstructor(listOfTFirst, ctorPrep)
    
    ilgen.Emit(OpCodes.Ldarg_0)
    ilgen.Emit(OpCodes.Newobj, ctor)
    ilgen.Emit(OpCodes.Ret)
    
  10. Utwórz typ i zapisz plik.

    Type^ finished = myType->CreateType();
    myAssembly->Save( String::Concat( myAsmName->Name, L".dll" ) );
    
    Type finished = myType.CreateType();
    myAssembly.Save(myAsmName.Name+".dll");
    
    Dim finished As Type = myType.CreateType()
    myAssembly.Save(myAsmName.Name & ".dll")
    
  11. Wywołaj metodę . ExampleMethod nie jest ogólny, ale typ, do którego należy, jest ogólny, więc aby uzyskać MethodInfo, które można wywołać, należy utworzyć skonstruowany typ z definicji typu dla Sample. Skonstruowany typ używa klasy Example, która spełnia ograniczenia dotyczące TFirst, ponieważ jest typem odwołania i ma domyślny konstruktor bez parametrów, a klasa ExampleDerived spełnia ograniczenia dotyczące TSecond. (Kod ExampleDerived można znaleźć w sekcji przykładowego kodu). Te dwa typy są przekazywane do MakeGenericType w celu utworzenia skonstruowanego typu. Następnie uzyskuje się MethodInfo przy użyciu metody GetMethod.

    array<Type^>^ typeArgs = 
        { Example::typeid, ExampleDerived::typeid };
    Type^ constructed = finished->MakeGenericType(typeArgs);
    MethodInfo^ mi = constructed->GetMethod("ExampleMethod");
    
    Type[] typeArgs = {typeof(Example), typeof(ExampleDerived)};
    Type constructed = finished.MakeGenericType(typeArgs);
    MethodInfo mi = constructed.GetMethod("ExampleMethod");
    
    Dim typeArgs() As Type = _
        {GetType(Example), GetType(ExampleDerived)}
    Dim constructed As Type = finished.MakeGenericType(typeArgs)
    Dim mi As MethodInfo = constructed.GetMethod("ExampleMethod")
    
  12. Poniższy kod tworzy tablicę obiektów Example, umieszcza tę tablicę w tablicy typu Object reprezentującą argumenty metody do wywołania i przekazuje je do metody Invoke(Object, Object[]). Pierwszym argumentem metody Invoke jest odwołanie o wartości null, ponieważ metoda jest static.

    array<Example^>^ input = { gcnew Example(), gcnew Example() };
    array<Object^>^ arguments = { input };
    
    List<Example^>^ listX = 
        (List<Example^>^) mi->Invoke(nullptr, arguments);
    
    Console::WriteLine(
        "\nThere are {0} elements in the List<Example>.", 
        listX->Count);
    
    Example[] input = {new Example(), new Example()};
    object[] arguments = {input};
    
    List<Example> listX =
        (List<Example>) mi.Invoke(null, arguments);
    
    Console.WriteLine($"\nThere are {listX.Count} elements in the List<Example>.");
    
    Dim input() As Example = {New Example(), New Example()}
    Dim arguments() As Object = {input}
    
    Dim listX As List(Of Example) = mi.Invoke(Nothing, arguments)
    
    Console.WriteLine(vbLf & _
        "There are {0} elements in the List(Of Example).", _
        listX.Count _
    )
    

Przykład

Poniższy przykład kodu definiuje klasę o nazwie Sample, wraz z klasą bazową i dwoma interfejsami. Program definiuje dwa ogólne parametry typu dla Sample, zamieniając go w typ ogólny. Parametry typu są tym, co czyni typ ogólnym. Program pokazuje to, wyświetlając komunikat testowy przed i po definicji parametrów typu.

Parametr typu TSecond służy do demonstrowania ograniczeń klasy i interfejsu, przy użyciu klasy bazowej i interfejsów, a parametr typu TFirst jest używany do zademonstrowania specjalnych ograniczeń.

Przykład kodu definiuje pole i metodę przy użyciu parametrów typu klasy dla typu pola oraz parametru i zwracanego typu metody.

Po utworzeniu klasy Sample wywoływana jest metoda .

Program zawiera metodę zawierającą informacje o typie ogólnym oraz metodę, która zawiera listę specjalnych ograniczeń dla parametru typu. Te metody służą do wyświetlania informacji o zakończonej klasie Sample.

Program zapisuje gotowy moduł na dysku jako GenericEmitExample1.dll, aby można było go otworzyć przy użyciu Ildasm.exe (IL Dezasembler) i zbadać CIL dla klasy Sample.

using namespace System;
using namespace System::Reflection;
using namespace System::Reflection::Emit;
using namespace System::Collections::Generic;

// Dummy class to satisfy TFirst constraints.
//
public ref class Example {};

// Define a trivial base class and two trivial interfaces 
// to use when demonstrating constraints.
//
public ref class ExampleBase {};
public interface class IExampleA {};
public interface class IExampleB {};

// Define a trivial type that can substitute for type parameter 
// TSecond.
//
public ref class ExampleDerived : ExampleBase, IExampleA, IExampleB {};

// List the constraint flags. The GenericParameterAttributes
// enumeration contains two sets of attributes, variance and
// constraints. For this example, only constraints are used.
//
static void ListConstraintAttributes( Type^ t )
{
   // Mask off the constraint flags. 
   GenericParameterAttributes constraints = 
       t->GenericParameterAttributes & 
       GenericParameterAttributes::SpecialConstraintMask;

   if ((constraints & GenericParameterAttributes::ReferenceTypeConstraint)
           != GenericParameterAttributes::None)
       Console::WriteLine( L"    ReferenceTypeConstraint");

   if ((constraints & GenericParameterAttributes::NotNullableValueTypeConstraint)
           != GenericParameterAttributes::None)
       Console::WriteLine( L"    NotNullableValueTypeConstraint");

   if ((constraints & GenericParameterAttributes::DefaultConstructorConstraint)
           != GenericParameterAttributes::None)
       Console::WriteLine( L"    DefaultConstructorConstraint");
}

static void DisplayGenericParameters( Type^ t )
{
   if (!t->IsGenericType)
   {
       Console::WriteLine( L"Type '{0}' is not generic." );
       return;
   }
   if (!t->IsGenericTypeDefinition)
       t = t->GetGenericTypeDefinition();

   array<Type^>^ typeParameters = t->GetGenericArguments();
   Console::WriteLine( L"\r\nListing {0} type parameters for type '{1}'.", 
       typeParameters->Length, t );

   for each ( Type^ tParam in typeParameters )
   {
       Console::WriteLine( L"\r\nType parameter {0}:", 
           tParam->ToString() );

       for each (Type^ c in tParam->GetGenericParameterConstraints())
       {
           if (c->IsInterface)
               Console::WriteLine( L"    Interface constraint: {0}", c);
           else
               Console::WriteLine( L"    Base type constraint: {0}", c);
       }
       ListConstraintAttributes(tParam);
   }
}

void main()
{
   // Define a dynamic assembly to contain the sample type. The
   // assembly will be run and also saved to disk, so
   // AssemblyBuilderAccess.RunAndSave is specified.
   //
   AppDomain^ myDomain = AppDomain::CurrentDomain;
   AssemblyName^ myAsmName = gcnew AssemblyName( L"GenericEmitExample1" );
   AssemblyBuilder^ myAssembly = myDomain->DefineDynamicAssembly( 
       myAsmName, AssemblyBuilderAccess::RunAndSave );

   // An assembly is made up of executable modules. For a single-
   // module assembly, the module name and file name are the same 
   // as the assembly name. 
   //
   ModuleBuilder^ myModule = myAssembly->DefineDynamicModule( 
       myAsmName->Name, String::Concat( myAsmName->Name, L".dll" ) );

   // Get type objects for the base class trivial interfaces to
   // be used as constraints.
   //
   Type^ baseType = ExampleBase::typeid; 
   Type^ interfaceA = IExampleA::typeid; 
   Type^ interfaceB = IExampleB::typeid;
   
   // Define the sample type.
   //
   TypeBuilder^ myType = myModule->DefineType( L"Sample", 
       TypeAttributes::Public );
   
   Console::WriteLine( L"Type 'Sample' is generic: {0}", 
       myType->IsGenericType );
   
   // Define type parameters for the type. Until you do this, 
   // the type is not generic, as the preceding and following 
   // WriteLine statements show. The type parameter names are
   // specified as an array of strings. To make the code
   // easier to read, each GenericTypeParameterBuilder is placed
   // in a variable with the same name as the type parameter.
   // 
   array<String^>^typeParamNames = {L"TFirst",L"TSecond"};
   array<GenericTypeParameterBuilder^>^typeParams = 
       myType->DefineGenericParameters( typeParamNames );

   GenericTypeParameterBuilder^ TFirst = typeParams[0];
   GenericTypeParameterBuilder^ TSecond = typeParams[1];

   Console::WriteLine( L"Type 'Sample' is generic: {0}", 
       myType->IsGenericType );
   
   // Apply constraints to the type parameters.
   //
   // A type that is substituted for the first parameter, TFirst,
   // must be a reference type and must have a parameterless
   // constructor.
   TFirst->SetGenericParameterAttributes( 
       GenericParameterAttributes::DefaultConstructorConstraint | 
       GenericParameterAttributes::ReferenceTypeConstraint 
   );

   // A type that is substituted for the second type
   // parameter must implement IExampleA and IExampleB, and
   // inherit from the trivial test class ExampleBase. The
   // interface constraints are specified as an array
   // containing the interface types. 
   array<Type^>^interfaceTypes = { interfaceA, interfaceB };
   TSecond->SetInterfaceConstraints( interfaceTypes );
   TSecond->SetBaseTypeConstraint( baseType );

   // The following code adds a private field named ExampleField,
   // of type TFirst.
   FieldBuilder^ exField = 
       myType->DefineField("ExampleField", TFirst, 
           FieldAttributes::Private);

   // Define a static method that takes an array of TFirst and 
   // returns a List<TFirst> containing all the elements of 
   // the array. To define this method it is necessary to create
   // the type List<TFirst> by calling MakeGenericType on the
   // generic type definition, generic<T> List. 
   // The parameter type is created by using the
   // MakeArrayType method. 
   //
   Type^ listOf = List::typeid;
   Type^ listOfTFirst = listOf->MakeGenericType(TFirst);
   array<Type^>^ mParamTypes = { TFirst->MakeArrayType() };

   MethodBuilder^ exMethod = 
       myType->DefineMethod("ExampleMethod", 
           MethodAttributes::Public | MethodAttributes::Static, 
           listOfTFirst, 
           mParamTypes);

   // Emit the method body. 
   // The method body consists of just three opcodes, to load 
   // the input array onto the execution stack, to call the 
   // List<TFirst> constructor that takes IEnumerable<TFirst>,
   // which does all the work of putting the input elements into
   // the list, and to return, leaving the list on the stack. The
   // hard work is getting the constructor.
   // 
   // The GetConstructor method is not supported on a 
   // GenericTypeParameterBuilder, so it is not possible to get 
   // the constructor of List<TFirst> directly. There are two
   // steps, first getting the constructor of generic<T> List and then
   // calling a method that converts it to the corresponding 
   // constructor of List<TFirst>.
   //
   // The constructor needed here is the one that takes an
   // IEnumerable<T>. Note, however, that this is not the 
   // generic type definition of generic<T> IEnumerable; instead, the
   // T from generic<T> List must be substituted for the T of 
   // generic<T> IEnumerable. (This seems confusing only because both
   // types have type parameters named T. That is why this example
   // uses the somewhat silly names TFirst and TSecond.) To get
   // the type of the constructor argument, take the generic
   // type definition generic<T> IEnumerable and 
   // call MakeGenericType with the first generic type parameter
   // of generic<T> List. The constructor argument list must be passed
   // as an array, with just one argument in this case.
   // 
   // Now it is possible to get the constructor of generic<T> List,
   // using GetConstructor on the generic type definition. To get
   // the constructor of List<TFirst>, pass List<TFirst> and
   // the constructor from generic<T> List to the static
   // TypeBuilder.GetConstructor method.
   //
   ILGenerator^ ilgen = exMethod->GetILGenerator();
        
   Type^ ienumOf = IEnumerable::typeid;
   Type^ TfromListOf = listOf->GetGenericArguments()[0];
   Type^ ienumOfT = ienumOf->MakeGenericType(TfromListOf);
   array<Type^>^ ctorArgs = {ienumOfT};

   ConstructorInfo^ ctorPrep = listOf->GetConstructor(ctorArgs);
   ConstructorInfo^ ctor = 
       TypeBuilder::GetConstructor(listOfTFirst, ctorPrep);

   ilgen->Emit(OpCodes::Ldarg_0);
   ilgen->Emit(OpCodes::Newobj, ctor);
   ilgen->Emit(OpCodes::Ret);

   // Create the type and save the assembly. 
   Type^ finished = myType->CreateType();
   myAssembly->Save( String::Concat( myAsmName->Name, L".dll" ) );

   // Invoke the method.
   // ExampleMethod is not generic, but the type it belongs to is
   // generic, so in order to get a MethodInfo that can be invoked
   // it is necessary to create a constructed type. The Example 
   // class satisfies the constraints on TFirst, because it is a 
   // reference type and has a default constructor. In order to
   // have a class that satisfies the constraints on TSecond, 
   // this code example defines the ExampleDerived type. These
   // two types are passed to MakeGenericMethod to create the
   // constructed type.
   //
   array<Type^>^ typeArgs = 
       { Example::typeid, ExampleDerived::typeid };
   Type^ constructed = finished->MakeGenericType(typeArgs);
   MethodInfo^ mi = constructed->GetMethod("ExampleMethod");

   // Create an array of Example objects, as input to the generic
   // method. This array must be passed as the only element of an 
   // array of arguments. The first argument of Invoke is 
   // null, because ExampleMethod is static. Display the count
   // on the resulting List<Example>.
   // 
   array<Example^>^ input = { gcnew Example(), gcnew Example() };
   array<Object^>^ arguments = { input };

   List<Example^>^ listX = 
       (List<Example^>^) mi->Invoke(nullptr, arguments);

   Console::WriteLine(
       "\nThere are {0} elements in the List<Example>.", 
       listX->Count);

   DisplayGenericParameters(finished);
}

/* This code example produces the following output:

Type 'Sample' is generic: False
Type 'Sample' is generic: True

There are 2 elements in the List<Example>.

Listing 2 type parameters for type 'Sample[TFirst,TSecond]'.

Type parameter TFirst:
    ReferenceTypeConstraint
    DefaultConstructorConstraint

Type parameter TSecond:
    Interface constraint: IExampleA
    Interface constraint: IExampleB
    Base type constraint: ExampleBase
 */
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;

// Define a trivial base class and two trivial interfaces
// to use when demonstrating constraints.
//
public class ExampleBase {}

public interface IExampleA {}

public interface IExampleB {}

// Define a trivial type that can substitute for type parameter
// TSecond.
//
public class ExampleDerived : ExampleBase, IExampleA, IExampleB {}

public class Example
{
    public static void Main()
    {
        // Define a dynamic assembly to contain the sample type. The
        // assembly will not be run, but only saved to disk, so
        // AssemblyBuilderAccess.Save is specified.
        //
        AppDomain myDomain = AppDomain.CurrentDomain;
        AssemblyName myAsmName = new AssemblyName("GenericEmitExample1");
        AssemblyBuilder myAssembly =
            myDomain.DefineDynamicAssembly(myAsmName,
                AssemblyBuilderAccess.RunAndSave);

        // An assembly is made up of executable modules. For a single-
        // module assembly, the module name and file name are the same
        // as the assembly name.
        //
        ModuleBuilder myModule =
            myAssembly.DefineDynamicModule(myAsmName.Name,
               myAsmName.Name + ".dll");

        // Get type objects for the base class trivial interfaces to
        // be used as constraints.
        //
        Type baseType = typeof(ExampleBase);
        Type interfaceA = typeof(IExampleA);
        Type interfaceB = typeof(IExampleB);

        // Define the sample type.
        //
        TypeBuilder myType =
            myModule.DefineType("Sample", TypeAttributes.Public);

        Console.WriteLine($"Type 'Sample' is generic: {myType.IsGenericType}");

        // Define type parameters for the type. Until you do this,
        // the type is not generic, as the preceding and following
        // WriteLine statements show. The type parameter names are
        // specified as an array of strings. To make the code
        // easier to read, each GenericTypeParameterBuilder is placed
        // in a variable with the same name as the type parameter.
        //
        string[] typeParamNames = {"TFirst", "TSecond"};
        GenericTypeParameterBuilder[] typeParams =
            myType.DefineGenericParameters(typeParamNames);

        GenericTypeParameterBuilder TFirst = typeParams[0];
        GenericTypeParameterBuilder TSecond = typeParams[1];

        Console.WriteLine($"Type 'Sample' is generic: {myType.IsGenericType}");

        // Apply constraints to the type parameters.
        //
        // A type that is substituted for the first parameter, TFirst,
        // must be a reference type and must have a parameterless
        // constructor.
        TFirst.SetGenericParameterAttributes(
            GenericParameterAttributes.DefaultConstructorConstraint |
            GenericParameterAttributes.ReferenceTypeConstraint);

        // A type that is substituted for the second type
        // parameter must implement IExampleA and IExampleB, and
        // inherit from the trivial test class ExampleBase. The
        // interface constraints are specified as an array
        // containing the interface types.
        TSecond.SetBaseTypeConstraint(baseType);
        Type[] interfaceTypes = {interfaceA, interfaceB};
        TSecond.SetInterfaceConstraints(interfaceTypes);

        // The following code adds a private field named ExampleField,
        // of type TFirst.
        FieldBuilder exField =
            myType.DefineField("ExampleField", TFirst,
                FieldAttributes.Private);

        // Define a static method that takes an array of TFirst and
        // returns a List<TFirst> containing all the elements of
        // the array. To define this method it is necessary to create
        // the type List<TFirst> by calling MakeGenericType on the
        // generic type definition, List<T>. (The T is omitted with
        // the typeof operator when you get the generic type
        // definition.) The parameter type is created by using the
        // MakeArrayType method.
        //
        Type listOf = typeof(List<>);
        Type listOfTFirst = listOf.MakeGenericType(TFirst);
        Type[] mParamTypes = {TFirst.MakeArrayType()};

        MethodBuilder exMethod =
            myType.DefineMethod("ExampleMethod",
                MethodAttributes.Public | MethodAttributes.Static,
                listOfTFirst,
                mParamTypes);

        // Emit the method body.
        // The method body consists of just three opcodes, to load
        // the input array onto the execution stack, to call the
        // List<TFirst> constructor that takes IEnumerable<TFirst>,
        // which does all the work of putting the input elements into
        // the list, and to return, leaving the list on the stack. The
        // hard work is getting the constructor.
        //
        // The GetConstructor method is not supported on a
        // GenericTypeParameterBuilder, so it is not possible to get
        // the constructor of List<TFirst> directly. There are two
        // steps, first getting the constructor of List<T> and then
        // calling a method that converts it to the corresponding
        // constructor of List<TFirst>.
        //
        // The constructor needed here is the one that takes an
        // IEnumerable<T>. Note, however, that this is not the
        // generic type definition of IEnumerable<T>; instead, the
        // T from List<T> must be substituted for the T of
        // IEnumerable<T>. (This seems confusing only because both
        // types have type parameters named T. That is why this example
        // uses the somewhat silly names TFirst and TSecond.) To get
        // the type of the constructor argument, take the generic
        // type definition IEnumerable<T> (expressed as
        // IEnumerable<> when you use the typeof operator) and
        // call MakeGenericType with the first generic type parameter
        // of List<T>. The constructor argument list must be passed
        // as an array, with just one argument in this case.
        //
        // Now it is possible to get the constructor of List<T>,
        // using GetConstructor on the generic type definition. To get
        // the constructor of List<TFirst>, pass List<TFirst> and
        // the constructor from List<T> to the static
        // TypeBuilder.GetConstructor method.
        //
        ILGenerator ilgen = exMethod.GetILGenerator();

        Type ienumOf = typeof(IEnumerable<>);
        Type TfromListOf = listOf.GetGenericArguments()[0];
        Type ienumOfT = ienumOf.MakeGenericType(TfromListOf);
        Type[] ctorArgs = {ienumOfT};

        ConstructorInfo ctorPrep = listOf.GetConstructor(ctorArgs);
        ConstructorInfo ctor =
            TypeBuilder.GetConstructor(listOfTFirst, ctorPrep);

        ilgen.Emit(OpCodes.Ldarg_0);
        ilgen.Emit(OpCodes.Newobj, ctor);
        ilgen.Emit(OpCodes.Ret);

        // Create the type and save the assembly.
        Type finished = myType.CreateType();
        myAssembly.Save(myAsmName.Name+".dll");

        // Invoke the method.
        // ExampleMethod is not generic, but the type it belongs to is
        // generic, so in order to get a MethodInfo that can be invoked
        // it is necessary to create a constructed type. The Example
        // class satisfies the constraints on TFirst, because it is a
        // reference type and has a default constructor. In order to
        // have a class that satisfies the constraints on TSecond,
        // this code example defines the ExampleDerived type. These
        // two types are passed to MakeGenericMethod to create the
        // constructed type.
        //
        Type[] typeArgs = {typeof(Example), typeof(ExampleDerived)};
        Type constructed = finished.MakeGenericType(typeArgs);
        MethodInfo mi = constructed.GetMethod("ExampleMethod");

        // Create an array of Example objects, as input to the generic
        // method. This array must be passed as the only element of an
        // array of arguments. The first argument of Invoke is
        // null, because ExampleMethod is static. Display the count
        // on the resulting List<Example>.
        //
        Example[] input = {new Example(), new Example()};
        object[] arguments = {input};

        List<Example> listX =
            (List<Example>) mi.Invoke(null, arguments);

        Console.WriteLine($"\nThere are {listX.Count} elements in the List<Example>.");

        DisplayGenericParameters(finished);
    }

    private static void DisplayGenericParameters(Type t)
    {
        if (!t.IsGenericType)
        {
            Console.WriteLine("Type '{0}' is not generic.");
            return;
        }
        if (!t.IsGenericTypeDefinition)
        {
            t = t.GetGenericTypeDefinition();
        }

        Type[] typeParameters = t.GetGenericArguments();
        Console.WriteLine($"\nListing {typeParameters.Length} type parameters for type '{t}'.");

        foreach( Type tParam in typeParameters )
        {
            Console.WriteLine($"""
            
            Type parameter {tParam.ToString()}:
            """);

            foreach( Type c in tParam.GetGenericParameterConstraints() )
            {
                if (c.IsInterface)
                {
                    Console.WriteLine($"    Interface constraint: {c}");
                }
                else
                {
                    Console.WriteLine($"    Base type constraint: {c}");
                }
            }

            ListConstraintAttributes(tParam);
        }
    }

    // List the constraint flags. The GenericParameterAttributes
    // enumeration contains two sets of attributes, variance and
    // constraints. For this example, only constraints are used.
    //
    private static void ListConstraintAttributes(Type t)
    {
        // Mask off the constraint flags.
        GenericParameterAttributes constraints =
            t.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;

        if ((constraints & GenericParameterAttributes.ReferenceTypeConstraint)
            != GenericParameterAttributes.None)
        {
            Console.WriteLine("    ReferenceTypeConstraint");
        }

        if ((constraints & GenericParameterAttributes.NotNullableValueTypeConstraint)
            != GenericParameterAttributes.None)
        {
            Console.WriteLine("    NotNullableValueTypeConstraint");
        }

        if ((constraints & GenericParameterAttributes.DefaultConstructorConstraint)
            !=GenericParameterAttributes.None)
        {
            Console.WriteLine("    DefaultConstructorConstraint");
        }
    }
}

/* This code example produces the following output:

Type 'Sample' is generic: False
Type 'Sample' is generic: True

There are 2 elements in the List<Example>.

Listing 2 type parameters for type 'Sample[TFirst,TSecond]'.

Type parameter TFirst:
    ReferenceTypeConstraint
    DefaultConstructorConstraint

Type parameter TSecond:
    Interface constraint: IExampleA
    Interface constraint: IExampleB
    Base type constraint: ExampleBase
 */
Imports System.Reflection
Imports System.Reflection.Emit
Imports System.Collections.Generic

' Define a trivial base class and two trivial interfaces 
' to use when demonstrating constraints.
'
Public Class ExampleBase
End Class

Public Interface IExampleA
End Interface

Public Interface IExampleB
End Interface

' Define a trivial type that can substitute for type parameter 
' TSecond.
'
Public Class ExampleDerived
    Inherits ExampleBase
    Implements IExampleA, IExampleB
End Class

Public Class Example
    Public Shared Sub Main()
        ' Define a dynamic assembly to contain the sample type. The
        ' assembly will not be run, but only saved to disk, so
        ' AssemblyBuilderAccess.Save is specified.
        '
        Dim myDomain As AppDomain = AppDomain.CurrentDomain
        Dim myAsmName As New AssemblyName("GenericEmitExample1")
        Dim myAssembly As AssemblyBuilder = myDomain.DefineDynamicAssembly( _
            myAsmName, _
            AssemblyBuilderAccess.RunAndSave)

        ' An assembly is made up of executable modules. For a single-
        ' module assembly, the module name and file name are the same 
        ' as the assembly name. 
        '
        Dim myModule As ModuleBuilder = myAssembly.DefineDynamicModule( _
            myAsmName.Name, _
            myAsmName.Name & ".dll")

        ' Get type objects for the base class trivial interfaces to
        ' be used as constraints.
        '
        Dim baseType As Type = GetType(ExampleBase)
        Dim interfaceA As Type = GetType(IExampleA)
        Dim interfaceB As Type = GetType(IExampleB)

        ' Define the sample type.
        '
        Dim myType As TypeBuilder = myModule.DefineType( _
            "Sample", _
            TypeAttributes.Public)

        Console.WriteLine("Type 'Sample' is generic: {0}", _
            myType.IsGenericType)

        ' Define type parameters for the type. Until you do this, 
        ' the type is not generic, as the preceding and following 
        ' WriteLine statements show. The type parameter names are
        ' specified as an array of strings. To make the code
        ' easier to read, each GenericTypeParameterBuilder is placed
        ' in a variable with the same name as the type parameter.
        ' 
        Dim typeParamNames() As String = {"TFirst", "TSecond"}
        Dim typeParams() As GenericTypeParameterBuilder = _
            myType.DefineGenericParameters(typeParamNames)

        Dim TFirst As GenericTypeParameterBuilder = typeParams(0)
        Dim TSecond As GenericTypeParameterBuilder = typeParams(1)

        Console.WriteLine("Type 'Sample' is generic: {0}", _
            myType.IsGenericType)

        ' Apply constraints to the type parameters.
        '
        ' A type that is substituted for the first parameter, TFirst,
        ' must be a reference type and must have a parameterless
        ' constructor.
        TFirst.SetGenericParameterAttributes( _
            GenericParameterAttributes.DefaultConstructorConstraint _
            Or GenericParameterAttributes.ReferenceTypeConstraint)

        ' A type that is substituted for the second type
        ' parameter must implement IExampleA and IExampleB, and
        ' inherit from the trivial test class ExampleBase. The
        ' interface constraints are specified as an array 
        ' containing the interface types.
        TSecond.SetBaseTypeConstraint(baseType)
        Dim interfaceTypes() As Type = {interfaceA, interfaceB}
        TSecond.SetInterfaceConstraints(interfaceTypes)

        ' The following code adds a private field named ExampleField,
        ' of type TFirst.
        Dim exField As FieldBuilder = _
            myType.DefineField("ExampleField", TFirst, _
                FieldAttributes.Private)

        ' Define a Shared method that takes an array of TFirst and 
        ' returns a List(Of TFirst) containing all the elements of 
        ' the array. To define this method it is necessary to create
        ' the type List(Of TFirst) by calling MakeGenericType on the
        ' generic type definition, List(Of T). (The T is omitted with
        ' the GetType operator when you get the generic type 
        ' definition.) The parameter type is created by using the
        ' MakeArrayType method. 
        '
        Dim listOf As Type = GetType(List(Of ))
        Dim listOfTFirst As Type = listOf.MakeGenericType(TFirst)
        Dim mParamTypes() As Type = {TFirst.MakeArrayType()}

        Dim exMethod As MethodBuilder = _
            myType.DefineMethod("ExampleMethod", _
                MethodAttributes.Public Or MethodAttributes.Static, _
                listOfTFirst, _
                mParamTypes)

        ' Emit the method body. 
        ' The method body consists of just three opcodes, to load 
        ' the input array onto the execution stack, to call the 
        ' List(Of TFirst) constructor that takes IEnumerable(Of TFirst),
        ' which does all the work of putting the input elements into
        ' the list, and to return, leaving the list on the stack. The
        ' hard work is getting the constructor.
        ' 
        ' The GetConstructor method is not supported on a 
        ' GenericTypeParameterBuilder, so it is not possible to get 
        ' the constructor of List(Of TFirst) directly. There are two
        ' steps, first getting the constructor of List(Of T) and then
        ' calling a method that converts it to the corresponding 
        ' constructor of List(Of TFirst).
        '
        ' The constructor needed here is the one that takes an
        ' IEnumerable(Of T). Note, however, that this is not the 
        ' generic type definition of IEnumerable(Of T); instead, the
        ' T from List(Of T) must be substituted for the T of 
        ' IEnumerable(Of T). (This seems confusing only because both
        ' types have type parameters named T. That is why this example
        ' uses the somewhat silly names TFirst and TSecond.) To get
        ' the type of the constructor argument, take the generic
        ' type definition IEnumerable(Of T) (expressed as 
        ' IEnumerable(Of ) when you use the GetType operator) and 
        ' call MakeGenericType with the first generic type parameter
        ' of List(Of T). The constructor argument list must be passed
        ' as an array, with just one argument in this case.
        ' 
        ' Now it is possible to get the constructor of List(Of T),
        ' using GetConstructor on the generic type definition. To get
        ' the constructor of List(Of TFirst), pass List(Of TFirst) and
        ' the constructor from List(Of T) to the static
        ' TypeBuilder.GetConstructor method.
        '
        Dim ilgen As ILGenerator = exMethod.GetILGenerator()

        Dim ienumOf As Type = GetType(IEnumerable(Of ))
        Dim listOfTParams() As Type = listOf.GetGenericArguments()
        Dim TfromListOf As Type = listOfTParams(0)
        Dim ienumOfT As Type = ienumOf.MakeGenericType(TfromListOf)
        Dim ctorArgs() As Type = {ienumOfT}

        Dim ctorPrep As ConstructorInfo = _
            listOf.GetConstructor(ctorArgs)
        Dim ctor As ConstructorInfo = _
            TypeBuilder.GetConstructor(listOfTFirst, ctorPrep)

        ilgen.Emit(OpCodes.Ldarg_0)
        ilgen.Emit(OpCodes.Newobj, ctor)
        ilgen.Emit(OpCodes.Ret)

        ' Create the type and save the assembly. 
        Dim finished As Type = myType.CreateType()
        myAssembly.Save(myAsmName.Name & ".dll")

        ' Invoke the method.
        ' ExampleMethod is not generic, but the type it belongs to is
        ' generic, so in order to get a MethodInfo that can be invoked
        ' it is necessary to create a constructed type. The Example 
        ' class satisfies the constraints on TFirst, because it is a 
        ' reference type and has a default constructor. In order to
        ' have a class that satisfies the constraints on TSecond, 
        ' this code example defines the ExampleDerived type. These
        ' two types are passed to MakeGenericMethod to create the
        ' constructed type.
        '
        Dim typeArgs() As Type = _
            {GetType(Example), GetType(ExampleDerived)}
        Dim constructed As Type = finished.MakeGenericType(typeArgs)
        Dim mi As MethodInfo = constructed.GetMethod("ExampleMethod")

        ' Create an array of Example objects, as input to the generic
        ' method. This array must be passed as the only element of an 
        ' array of arguments. The first argument of Invoke is 
        ' Nothing, because ExampleMethod is Shared. Display the count
        ' on the resulting List(Of Example).
        ' 
        Dim input() As Example = {New Example(), New Example()}
        Dim arguments() As Object = {input}

        Dim listX As List(Of Example) = mi.Invoke(Nothing, arguments)

        Console.WriteLine(vbLf & _
            "There are {0} elements in the List(Of Example).", _
            listX.Count _
        )

        DisplayGenericParameters(finished)
    End Sub

    Private Shared Sub DisplayGenericParameters(ByVal t As Type)

        If Not t.IsGenericType Then
            Console.WriteLine("Type '{0}' is not generic.")
            Return
        End If
        If Not t.IsGenericTypeDefinition Then _
            t = t.GetGenericTypeDefinition()

        Dim typeParameters() As Type = t.GetGenericArguments()
        Console.WriteLine(vbCrLf & _
            "Listing {0} type parameters for type '{1}'.", _
            typeParameters.Length, t)

        For Each tParam As Type In typeParameters

            Console.WriteLine(vbCrLf & "Type parameter {0}:", _
                tParam.ToString())

            For Each c As Type In tParam.GetGenericParameterConstraints()
                If c.IsInterface Then
                    Console.WriteLine("    Interface constraint: {0}", c)
                Else
                    Console.WriteLine("    Base type constraint: {0}", c)
                End If
            Next

            ListConstraintAttributes(tParam)
        Next tParam
    End Sub

    ' List the constraint flags. The GenericParameterAttributes
    ' enumeration contains two sets of attributes, variance and
    ' constraints. For this example, only constraints are used.
    '
    Private Shared Sub ListConstraintAttributes(ByVal t As Type)

        ' Mask off the constraint flags. 
        Dim constraints As GenericParameterAttributes = _
            t.GenericParameterAttributes And _
            GenericParameterAttributes.SpecialConstraintMask

        If (constraints And GenericParameterAttributes.ReferenceTypeConstraint) _
                <> GenericParameterAttributes.None Then _
            Console.WriteLine("    ReferenceTypeConstraint")

        If (constraints And GenericParameterAttributes.NotNullableValueTypeConstraint) _
                <> GenericParameterAttributes.None Then _
            Console.WriteLine("    NotNullableValueTypeConstraint")

        If (constraints And GenericParameterAttributes.DefaultConstructorConstraint) _
                <> GenericParameterAttributes.None Then _
            Console.WriteLine("    DefaultConstructorConstraint")

    End Sub

End Class

' This code example produces the following output:
'
'Type 'Sample' is generic: False
'Type 'Sample' is generic: True
'
'There are 2 elements in the List(Of Example).
'
'Listing 2 type parameters for type 'Sample[TFirst,TSecond]'.
'
'Type parameter TFirst:
'    ReferenceTypeConstraint
'    DefaultConstructorConstraint
'
'Type parameter TSecond:
'    Interface constraint: IExampleA
'    Interface constraint: IExampleB
'    Base type constraint: ExampleBase

Zobacz też