Przeczytaj w języku angielskim

Udostępnij za pośrednictwem


Instrukcje: definiowanie typu ogólnego z emitem 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. Przykład emitowania metody ogólnej można znaleźć w temacie How to: Define a Generic Method with Emocje ion Emit (Instrukcje: Definiowanie metody ogólnej za pomocą emisji Emocje ion).

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 = new AssemblyName("GenericEmitExample1");
    AssemblyBuilder myAssembly =
        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,
           myAsmName.Name + ".dll");
    
  3. Zdefiniuj klasę. W tym przykładzie klasa nosi nazwę Sample.

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

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

    string[] typeParamNames = {"TFirst", "TSecond"};
    GenericTypeParameterBuilder[] typeParams =
        myType.DefineGenericParameters(typeParamNames);
    
    GenericTypeParameterBuilder TFirst = typeParams[0];
    GenericTypeParameterBuilder TSecond = typeParams[1];
    
  5. Dodaj specjalne ograniczenia do parametrów typu. W tym przykładzie parametr TFirst typu jest ograniczony do typów, które mają konstruktory bez parametrów i do typów referencyjnych.

    TFirst.SetGenericParameterAttributes(
        GenericParameterAttributes.DefaultConstructorConstraint |
        GenericParameterAttributes.ReferenceTypeConstraint);
    
  6. Opcjonalnie dodaj ograniczenia klasy i interfejsu do parametrów typu. W tym przykładzie parametr TFirst typu jest ograniczony do typów, które pochodzą z klasy bazowej reprezentowanej przez Type obiekt 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.

    TSecond.SetBaseTypeConstraint(baseType);
    Type[] interfaceTypes = {interfaceA, interfaceB};
    TSecond.SetInterfaceConstraints(interfaceTypes);
    
  7. Zdefiniuj pole. W tym przykładzie typ pola jest określany przez parametr TFirsttypu . GenericTypeParameterBuilder pochodzi z Typeklasy , aby można było 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);
    
  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 Visual Basic), która przyjmuje tablicę TFirst i zwraca List<TFirst> element (List(Of TFirst) w Visual Basic) zawierający wszystkie elementy tablicy. Aby zdefiniować tę metodę, należy utworzyć typ List<TFirst> , wywołując MakeGenericType definicję typu ogólnego, List<T>. (Parametr T jest pomijany podczas używania typeof operatora (GetType w Visual Basic) w celu uzyskania definicji typu ogólnego. Typ parametru MakeArrayType jest tworzony przy użyciu metody .

    Type listOf = typeof(List<>);
    Type listOfTFirst = listOf.MakeGenericType(TFirst);
    Type[] mParamTypes = {TFirst.MakeArrayType()};
    
    MethodBuilder exMethod =
        myType.DefineMethod("ExampleMethod",
            MethodAttributes.Public | MethodAttributes.Static,
            listOfTFirst,
            mParamTypes);
    
  9. Emituj treść metody. Treść metody składa się z trzech kodów opcode, które ładują tablicę wejściową do stosu, wywołując List<TFirst> konstruktor, który IEnumerable<TFirst> wykonuje całą pracę polegającą na umieszczeniu elementów wejściowych na liście i zwracają (pozostawiając nowy List<T> obiekt na stosie). Trudną częścią emitowania tego kodu jest uzyskanie konstruktora.

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

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

    Uwaga

    Definicja typu ogólnego jest wyrażona tak, jak IEnumerable<> w przypadku używania typeof operatora w języku C#lub IEnumerable(Of ) operatora GetType w języku Visual Basic.

    Teraz można uzyskać konstruktor klasy List<T> , wywołując GetConstructor definicję typu ogólnego. Aby przekonwertować ten konstruktor na odpowiedni konstruktor List<TFirst>klasy , przekaż List<TFirst> i konstruktor z List<T> do metody statycznej TypeBuilder.GetConstructor(Type, ConstructorInfo) .

    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);
    
  10. Utwórz typ i zapisz plik.

    Type finished = 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 element, który można wywołać, należy utworzyć skonstruowany typ z definicji typu dla Sample. Skonstruowany typ używa Example klasy, która spełnia ograniczenia TFirst , ponieważ jest typem odwołania i ma domyślny konstruktor bez parametrów, a ExampleDerived klasa, która spełnia ograniczenia dotyczące TSecond. (Kod dla ExampleDerived elementu można znaleźć w sekcji przykładowego kodu). Te dwa typy są przekazywane do MakeGenericType , aby utworzyć skonstruowany typ. Metoda MethodInfo jest następnie uzyskiwana przy użyciu GetMethod metody .

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

    Example[] input = {new Example(), new Example()};
    object[] arguments = {input};
    
    List<Example> listX =
        (List<Example>) mi.Invoke(null, arguments);
    
    Console.WriteLine(
        "\nThere are {0} elements in the List<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 je w typ ogólny. Parametry typu są jedyną rzeczą, która sprawia, że typ ogólny. Program pokazuje to, wyświetlając komunikat testowy przed i po definicji parametrów typu.

Parametr TSecond typu służy do demonstrowania ograniczeń klasy i interfejsu, przy użyciu klasy bazowej i interfejsów, a parametr TFirst typu służy do demonstrowania 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 Sample klasy 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 Sample klasie.

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

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: {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.
        //
        string[] typeParamNames = {"TFirst", "TSecond"};
        GenericTypeParameterBuilder[] typeParams =
            myType.DefineGenericParameters(typeParamNames);

        GenericTypeParameterBuilder TFirst = typeParams[0];
        GenericTypeParameterBuilder TSecond = 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 |
            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 {0} elements in the List<Example>.",
            listX.Count);

        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 {0} type parameters for type '{1}'.",
            typeParameters.Length, t);

        foreach( Type tParam in typeParameters )
        {
            Console.WriteLine("\r\nType parameter {0}:", tParam.ToString());

            foreach( Type c in tParam.GetGenericParameterConstraints() )
            {
                if (c.IsInterface)
                {
                    Console.WriteLine("    Interface constraint: {0}", c);
                }
                else
                {
                    Console.WriteLine("    Base type constraint: {0}", 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
 */

Zobacz też