Condividi tramite


Procedura: definire un tipo generico con la reflection emit

Aggiornamento: novembre 2007

In questo argomento viene illustrato come creare un tipo generico semplice con due parametri di tipo, nonché come applicare vincoli speciali, di classe e di interfaccia ai parametri di tipo e creare membri che utilizzano i parametri di tipo della classe come tipi di parametro o tipi restituiti.

Nota importante:

Un metodo non può essere considerato generico solo perché appartiene a un tipo generico e ne utilizza i parametri di tipo. Per essere generico, un metodo deve disporre di uno specifico elenco di parametri di tipo. La maggior parte dei metodi appartenenti a tipi generici non sono generici, come nell'esempio che segue. Per un esempio di creazione di un metodo generico, vedere Procedura: definire un metodo generico con la reflection emit.

Per definire un tipo generico

  1. Definire un assembly dinamico denominato GenericEmitExample1. Nell'esempio l'assembly viene eseguito e salvato su disco, pertanto è specificato AssemblyBuilderAccess.RunAndSave.

    Dim myDomain As AppDomain = AppDomain.CurrentDomain
    Dim myAsmName As New AssemblyName("GenericEmitExample1")
    Dim myAssembly As AssemblyBuilder = myDomain.DefineDynamicAssembly( _
        myAsmName, _
        AssemblyBuilderAccess.RunAndSave)
    
    AppDomain myDomain = AppDomain.CurrentDomain;
    AssemblyName myAsmName = new AssemblyName("GenericEmitExample1");
    AssemblyBuilder myAssembly = 
        myDomain.DefineDynamicAssembly(myAsmName, 
            AssemblyBuilderAccess.RunAndSave);
    
    AppDomain^ myDomain = AppDomain::CurrentDomain;
    AssemblyName^ myAsmName = gcnew AssemblyName( L"GenericEmitExample1" );
    AssemblyBuilder^ myAssembly = myDomain->DefineDynamicAssembly( 
        myAsmName, AssemblyBuilderAccess::RunAndSave );
    
  2. Definire un modulo dinamico. Un assembly è composto da moduli eseguibili. Per un assembly a modulo singolo, il nome del modulo è lo stesso dell'assembly e il nome del file corrisponde a quello del modulo seguito da un'estensione.

    Dim myModule As ModuleBuilder = myAssembly.DefineDynamicModule( _
        myAsmName.Name, _
        myAsmName.Name & ".dll")
    
    ModuleBuilder myModule = 
        myAssembly.DefineDynamicModule(myAsmName.Name, 
           myAsmName.Name + ".dll");
    
    ModuleBuilder^ myModule = myAssembly->DefineDynamicModule( 
        myAsmName->Name, String::Concat( myAsmName->Name, L".dll" ) );
    
  3. Definire una classe. In questo esempio la classe è denominata Sample.

    Dim myType As TypeBuilder = myModule.DefineType( _
        "Sample", _
        TypeAttributes.Public)
    
    TypeBuilder myType = 
        myModule.DefineType("Sample", TypeAttributes.Public);
    
    TypeBuilder^ myType = myModule->DefineType( L"Sample", 
        TypeAttributes::Public );
    
  4. Definire i parametri di tipo generico di Sample passando una matrice di stringhe contenenti i nomi dei parametri al metodo TypeBuilder.DefineGenericParameters. In questo modo la classe diventa un tipo generico. Il valore restituito è una matrice di oggetti GenericTypeParameterBuilder che rappresentano i parametri di tipo, che possono essere utilizzati nel codice creato.

    Nel codice riportato di seguito la classe Sample diventa un tipo generico con parametri di tipo TFirst e TSecond. Per migliorare la leggibilità del codice, ogni oggetto GenericTypeParameterBuilder viene inserito in una variabile che ha lo stesso nome del parametro di tipo.

    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)
    
    string[] typeParamNames = {"TFirst", "TSecond"};
    GenericTypeParameterBuilder[] typeParams = 
        myType.DefineGenericParameters(typeParamNames);
    
    GenericTypeParameterBuilder TFirst = typeParams[0];
    GenericTypeParameterBuilder TSecond = typeParams[1];
    
    array<String^>^typeParamNames = {L"TFirst",L"TSecond"};
    array<GenericTypeParameterBuilder^>^typeParams = 
        myType->DefineGenericParameters( typeParamNames );
    
    GenericTypeParameterBuilder^ TFirst = typeParams[0];
    GenericTypeParameterBuilder^ TSecond = typeParams[1];
    
  5. Aggiungere vincoli speciali ai parametri di tipo. In questo esempio il parametro di tipo TFirst viene vincolato a tipi con costruttori senza parametri e a tipi di riferimento.

    TFirst.SetGenericParameterAttributes( _
        GenericParameterAttributes.DefaultConstructorConstraint _
        Or GenericParameterAttributes.ReferenceTypeConstraint)
    
    TFirst.SetGenericParameterAttributes(
        GenericParameterAttributes.DefaultConstructorConstraint |
        GenericParameterAttributes.ReferenceTypeConstraint);
    
    TFirst->SetGenericParameterAttributes( 
        GenericParameterAttributes::DefaultConstructorConstraint | 
        GenericParameterAttributes::ReferenceTypeConstraint 
    );
    
  6. Facoltativamente, aggiungere vincoli di interfaccia e di classe ai parametri di tipo. In questo esempio il parametro di tipo TFirst viene vincolato a tipi che derivano dalla classe base rappresentata dall'oggetto Type, contenuto nella variabile baseType, e implementano le interfacce i cui tipi sono contenuti nelle variabili interfaceA e interfaceB. Per la dichiarazione e l'assegnazione di queste variabili, vedere l'esempio di codice.

    TSecond.SetBaseTypeConstraint(baseType)
    Dim interfaceTypes() As Type = {interfaceA, interfaceB}
    TSecond.SetInterfaceConstraints(interfaceTypes)
    
    TSecond.SetBaseTypeConstraint(baseType);
    Type[] interfaceTypes = {interfaceA, interfaceB};
    TSecond.SetInterfaceConstraints(interfaceTypes);
    
    array<Type^>^interfaceTypes = { interfaceA, interfaceB };
    TSecond->SetInterfaceConstraints( interfaceTypes );
    TSecond->SetBaseTypeConstraint( baseType );
    
  7. Definire un campo. In questo esempio il tipo del campo è specificato dal parametro di tipo TFirst. L'oggetto GenericTypeParameterBuilder deriva da Type ed è pertanto possibile utilizzare parametri di tipo generico in qualsiasi punto sia consentito l'utilizzo di un tipo.

    Dim exField As FieldBuilder = _
        myType.DefineField("ExampleField", TFirst, _
            FieldAttributes.Private)
    
    FieldBuilder exField = 
        myType.DefineField("ExampleField", TFirst, 
            FieldAttributes.Private);
    
    FieldBuilder^ exField = 
        myType->DefineField("ExampleField", TFirst, 
            FieldAttributes::Private);
    
  8. Definire un metodo che utilizza i parametri di tipo del tipo generico. Si noti che tali metodi non sono generici, a meno che non dispongano di specifici elenchi di parametri di tipo. Nel codice riportato di seguito viene definito un metodo static (Shared in Visual Basic) che accetta una matrice di parametri TFirst e restituisce un tipo List<TFirst> (List(Of TFirst) in Visual Basic) contenente tutti gli elementi della matrice. Per definire questo metodo, è necessario creare il tipo List<TFirst> chiamando il metodo MakeGenericType sulla definizione di tipo generico List<T>. Quando si utilizza l'operatore typeof (GetType in Visual Basic), il parametro di tipo T viene omesso in modo da ottenere la definizione di tipo generico. Il tipo di parametro viene creato utilizzando il metodo MakeArrayType.

    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)
    
    Type listOf = typeof(List<>);
    Type listOfTFirst = listOf.MakeGenericType(TFirst);
    Type[] mParamTypes = {TFirst.MakeArrayType()};
    
    MethodBuilder exMethod = 
        myType.DefineMethod("ExampleMethod", 
            MethodAttributes.Public | MethodAttributes.Static, 
            listOfTFirst, 
            mParamTypes);
    
    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);
    
  9. Creare il corpo del metodo. Il corpo del metodo è costituito da tre codici operativi che caricano la matrice di input nello stack, chiamano il costruttore di List<TFirst> che accetta IEnumerable<TFirst>, con cui vengono eseguite tutte le operazioni necessarie per l'inserimento degli elementi di input nell'elenco, e terminano lasciando il nuovo oggetto List<T> nello stack. Nella creazione di questo codice la difficoltà principale è rappresentata dal recupero del costruttore.

    Poiché il metodo GetConstructor non è supportato da un oggetto GenericTypeParameterBuilder, non è possibile ottenere il costruttore di List<TFirst> in modo diretto. È innanzitutto necessario ottenere il costruttore della definizione di tipo generico List<T>, quindi chiamare un metodo che lo converta nel costruttore corrispondente di List<TFirst>.

    Il costruttore utilizzato per questo esempio di codice accetta una definizione IEnumerable<T>. Si noti tuttavia che, non trattandosi della definizione di tipo generico dell'interfaccia generica IEnumerable<T>, è necessario sostituire il parametro di tipo T di List<T> al parametro di tipo T di IEnumerable<T>. Per evitare che la presenza di parametri di tipo denominati T in entrambi i tipi generi confusione, in questo esempio di codice vengono utilizzati i nomi TFirst e TSecond. Per ottenere il tipo dell'argomento del costruttore, creare innanzitutto la definizione di tipo generico IEnumerable<T>, quindi chiamare il metodo MakeGenericType con il primo parametro di tipo generico di List<T>. L'elenco di argomenti del costruttore deve essere passato come matrice che, in questo caso, contiene un unico argomento.

    Nota:

    La definizione di tipo generico è espressa come IEnumerable<> quando si utilizza l'operatore typeof in C# o come IEnumerable(Of ) quando si utilizza l'operatore GetType in Visual Basic.

    A questo punto è possibile ottenere il costruttore di List<T> chiamando il metodo GetConstructor sulla definizione di tipo generico. Per convertire questo costruttore nel costruttore corrispondente di List<TFirst>, passare List<TFirst> e il costruttore da List<T> al metodo statico TypeBuilder.GetConstructor(Type, ConstructorInfo).

    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)
    
    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);
    
    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);
    
  10. Creare il tipo e salvare il file.

    Dim finished As Type = myType.CreateType()
    myAssembly.Save(myAsmName.Name & ".dll")
    
    Type finished = myType.CreateType();
    myAssembly.Save(myAsmName.Name+".dll");
    
    Type^ finished = myType->CreateType();
    myAssembly->Save( String::Concat( myAsmName->Name, L".dll" ) );
    
  11. Richiamare il metodo. ExampleMethod non è generico, ma appartiene a un tipo generico. Pertanto, per ottenere un oggetto MethodInfo che possa essere richiamato, è necessario creare un tipo costruito dalla definizione di tipo di Sample. Il tipo costruito utilizza la classe Example che, essendo un tipo di riferimento e disponendo di un costruttore senza parametri predefinito, soddisfa i vincoli previsti per TFirst, nonché la classe ExampleDerived sottostante ai vincoli definiti per TSecond. Il codice per ExampleDerived è disponibile nella sezione del codice di esempio. Questi due tipi vengono passati al metodo MakeGenericType per la creazione del tipo costruito. L'oggetto MethodInfo viene quindi ottenuto utilizzando il metodo GetMethod.

    Dim typeArgs() As Type = _
        { GetType(Example), GetType(ExampleDerived) }
    Dim constructed As Type = finished.MakeGenericType(typeArgs)
    Dim mi As MethodInfo = constructed.GetMethod("ExampleMethod")
    
    Type[] typeArgs = {typeof(Example), typeof(ExampleDerived)};
    Type constructed = finished.MakeGenericType(typeArgs);
    MethodInfo mi = constructed.GetMethod("ExampleMethod");
    
    array<Type^>^ typeArgs = 
        { Example::typeid, ExampleDerived::typeid };
    Type^ constructed = finished->MakeGenericType(typeArgs);
    MethodInfo^ mi = constructed->GetMethod("ExampleMethod");
    
  12. Nel codice riportato di seguito viene creata una matrice di oggetti Example, viene inserita tale matrice in una matrice di tipo Object che rappresenta gli argomenti del metodo da richiamare e vengono passati questi argomenti al metodo Invoke(Object, array<Object[]). Il primo argomento del metodo Invoke è un riferimento null perché il metodo è static.

    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 _ 
    )
    
    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);
    
    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);
    

Esempio

Nell'esempio di codice riportato di seguito viene definita una classe denominata Sample, insieme a una classe base e a due interfacce. Nel programma vengono definiti due parametri di tipo generico per la classe Sample, che viene così trasformata in un tipo generico. I parametri di tipo sono gli unici elementi in grado di rendere generico un tipo. Nel programma questa trasformazione viene indicata tramite la visualizzazione di un messaggio di testo sia prima che dopo la definizione dei parametri di tipo.

Il parametro di tipo TSecond viene utilizzato per illustrare i vincoli di interfaccia e di classe tramite le interfacce e la classe base, mentre il parametro di tipo TFirst viene utilizzato per illustrare i vincoli speciali.

Nell'esempio di codice vengono definiti un campo e un metodo utilizzando i parametri di tipo della classe relativi al tipo di campo e al tipo restituito e di parametro del metodo.

Una volta creata la classe Sample, viene richiamato il metodo.

Nel programma sono inclusi due metodi, uno che fornisce informazioni su un tipo generico e un altro che illustra i vincoli speciali in un parametro di tipo. Questi metodi vengono utilizzati per visualizzare informazioni sulla classe Sample completata.

Nel programma il modulo completato viene salvato su disco come GenericEmitExample1.dll, in modo che sia possibile aprirlo con lo strumento Disassembler MSIL (Ildasm.exe) ed esaminare il codice MSIL relativo alla classe Sample.

Imports System
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
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
 */
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
 */

Compilazione del codice

  • Nel codice sono incluse le istruzioni using di C# (Imports in Visual Basic) necessarie per la compilazione.

  • Non sono necessari altri riferimenti ad assembly.

  • Compilare il codice dalla riga di comando utilizzando csc.exe, vbc.exe o cl.exe. Per compilare il codice in Visual Studio, inserirlo in un modello di progetto di applicazione console.

Vedere anche

Concetti

Scenari relativi ad assembly dinamici della Reflection Emit

Riferimenti

GenericTypeParameterBuilder

Altre risorse

Utilizzo della reflection emit