Cómo: Definir un tipo genérico con emisión de reflexión
En este artículo se muestra cómo crear un tipo genérico simple con dos parámetros de tipo, cómo aplicar restricciones de clase, restricciones de interfaz y restricciones especiales a los parámetros de tipo, y cómo crear miembros que usen los parámetros de tipo de la clase como tipos de parámetro y tipos de valor devuelto.
Importante
Un método no es genérico sólo porque pertenece a un tipo genérico y utiliza los parámetros de tipo de ese tipo genérico. Un método sólo es genérico si tiene su propia lista de parámetros de tipo. La mayoría de los métodos de los tipos genéricos no son genéricos, como en este ejemplo. Para obtener un ejemplo de emisión de un método genérico, vea Procedimiento para definir un tipo genérico con emisión de reflexión.
Definir un tipo genérico
Defina un ensamblado dinámico denominado
GenericEmitExample1
. En este ejemplo, el ensamblado se ha ejecutado y guardado en el disco, por lo que se especifica AssemblyBuilderAccess.RunAndSave.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)
Defina un módulo dinámico. Un ensamblado se compone de módulos ejecutables. En el caso de un ensamblado de un solo módulo, el nombre del módulo es el mismo que el nombre del ensamblado y el nombre del archivo es el nombre del módulo más una extensión.
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")
Defina una clase. En este ejemplo, la clase se denomina
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)
Defina los parámetros de tipo genérico de
Sample
pasando una matriz de cadenas que contienen los nombres de los parámetros al método TypeBuilder.DefineGenericParameters. Esto convierte a la clase en un tipo genérico. El valor devuelto es una matriz de objetos GenericTypeParameterBuilder que representan los parámetros de tipo y que se pueden usar en el código emitido.En el código siguiente,
Sample
se convierte en un tipo genérico con parámetros de tipoTFirst
yTSecond
. Para que el código sea más fácil de leer, cada GenericTypeParameterBuilder se coloca en una variable con el mismo nombre que el parámetro de tipo.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)
Agregue restricciones especiales a los parámetros de tipo. En este ejemplo, el parámetro de tipo
TFirst
está restringido a tipos que tienen constructores sin parámetros y a tipos de referencia.TFirst->SetGenericParameterAttributes( GenericParameterAttributes::DefaultConstructorConstraint | GenericParameterAttributes::ReferenceTypeConstraint );
TFirst.SetGenericParameterAttributes( GenericParameterAttributes.DefaultConstructorConstraint | GenericParameterAttributes.ReferenceTypeConstraint);
TFirst.SetGenericParameterAttributes( _ GenericParameterAttributes.DefaultConstructorConstraint _ Or GenericParameterAttributes.ReferenceTypeConstraint)
También tiene la opción de agregar restricciones de clase y de interfaz a los parámetros de tipo. En este ejemplo, el parámetro de tipo
TFirst
está restringido a tipos que derivan de la clase base representada por el objeto Type incluido en la variablebaseType
y que implementan las interfaces cuyos tipos están incluidos en las variablesinterfaceA
yinterfaceB
. Vea en el ejemplo de código la declaración y la asignación de estas variables.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)
Defina un campo. En este ejemplo, el tipo del campo es especificado por el parámetro de tipo
TFirst
. GenericTypeParameterBuilder deriva de Type, por lo que puede usar parámetros de tipo genérico en cualquier lugar en que se pueda usar un tipo.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)
Defina un método que use los parámetros del tipo genérico. Tenga en cuenta que estos métodos no son genéricos a menos que tengan sus propias listas de parámetros de tipo. El código siguiente define un método
static
(Shared
en Visual Basic) que toma una matriz deTFirst
y devuelve un tipoList<TFirst>
(List(Of TFirst)
en Visual Basic) que contiene todos los elementos de la matriz. Para definir este método, es necesario crear el tipoList<TFirst>
. Para ello, se llama a MakeGenericType en la definición de tipo genérico,List<T>
. (T
se omite cuando se usa el operadortypeof
[GetType
en Visual Basic] para obtener la definición de tipo genérico). El tipo de parámetro se crea con el método 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)
Emita el cuerpo del método. El cuerpo del método consta de tres códigos de operación que cargan la matriz de entrada en la pila, llaman al constructor
List<TFirst>
que tomaIEnumerable<TFirst>
(que hace todo el trabajo de colocar los elementos de entrada en la lista) y devuelven (dejando el nuevo objeto List<T> en la pila). La parte difícil a la hora de emitir este código es la obtención del constructor.El método GetConstructor no se admite en un GenericTypeParameterBuilder, por lo que no es posible obtener el constructor de
List<TFirst>
directamente. En primer lugar, es necesario obtener el constructor de la definición de tipo genéricoList<T>
y luego llamar a un método que lo convierta en el constructor correspondiente deList<TFirst>
.El constructor usado para este ejemplo de código toma un tipo
IEnumerable<T>
. Pero tenga en cuenta que no es la definición de tipo genérico de la interfaz genérica IEnumerable<T>; en su lugar, debe sustituirse el parámetro de tipoT
deList<T>
por el parámetro de tipoT
deIEnumerable<T>
. (Esto parece confuso solo porque ambos tipos tienen parámetros de tipo con el nombreT
. Es por esto que este ejemplo de código usa los nombresTFirst
yTSecond
). Para obtener el tipo del argumento del constructor, comience con la definición de tipo genéricoIEnumerable<T>
y llame a MakeGenericType con el primer parámetro de tipo genérico deList<T>
. La lista de argumentos del constructor se debe pasar como matriz, con un único argumento en este caso.Nota
La definición de tipo genérico se expresa como
IEnumerable<>
cuando se usa el operadortypeof
de C#, o comoIEnumerable(Of )
cuando se usa el operadorGetType
de Visual Basic.Ahora es posible obtener el constructor de
List<T>
con una llamada a GetConstructor en la definición de tipo genérico. Para convertir este constructor en el constructor correspondiente deList<TFirst>
, paseList<TFirst>
y el constructor deList<T>
al método estático 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)
Cree el tipo y guarde el archivo.
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")
Invoque al método.
ExampleMethod
no es genérico, pero el tipo al que pertenece sí lo es, así que para obtener un elemento MethodInfo que pueda invocarse es necesario crear un tipo construido de la definición de tipo paraSample
. El tipo construido usa la claseExample
, que satisface las restricciones deTFirst
porque es un tipo de referencia y tiene un constructor sin parámetros predeterminado, y la claseExampleDerived
, que satisface las restricciones deTSecond
. (El código deExampleDerived
puede encontrarse en la sección de código de ejemplo). Estos dos tipos se pasan a MakeGenericType para crear el tipo construido. Luego se obtiene MethodInfo mediante el método 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")
El código siguiente crea una matriz de objetos
Example
, coloca esa matriz en una matriz de tipo Object que representa los argumentos del método que se va a invocar y los pasa al método Invoke(Object, Object[]). El primer argumento del método Invoke es una referencia nula porque el método esstatic
.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 {0} elements in the List<Example>.", listX.Count);
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 _ )
Ejemplo
En el ejemplo de código siguiente se define una clase denominada Sample
junto con una clase base y dos interfaces. El programa define dos parámetros de tipo genérico para Sample
, con lo que lo convierte en un tipo genérico. Los parámetros de tipo son lo único que convierte un tipo genérico. El programa lo indica al mostrar un mensaje de prueba antes y después de la definición de los parámetros de tipo.
El parámetro de tipo TSecond
se usa para mostrar las restricciones de interfaz y de clase, mediante la clase base y las interfaces, y el parámetro de tipo TFirst
se emplea para mostrar restricciones especiales.
El ejemplo de código define un campo y un método con los parámetros de tipo de la clase para el tipo de campo y para el parámetro y el tipo devuelto del método.
Una vez creada la clase Sample
, se invoca al método.
El programa incluye un método que enumera información sobre un tipo genérico y un método que enumera las restricciones especiales de un parámetro de tipo. Estos métodos se usan para mostrar información sobre la clase finalizada Sample
.
El programa guarda el módulo finalizado en el disco como GenericEmitExample1.dll
, por lo que se puede abrir con Ildasm.exe (Desensamblador de IL) y examinar el CIL de la clase 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: {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
*/
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