Reflection (Reflexión)

Anuncio
Marco Besteiro y Miguel Rodríguez
Reflexión
Reflection (Reflexión)
La reflexión permite obtener información sobre el contenido de un assembly a partir de
sus metadatos, en concreto permite:
Los
Obtener información dinámicamente de todos los tipos de un assembly.
Determinar los atributos de los elementos de un assembly y obtener información
de éstos.
Cargar dinámicamente y utilizar un assembly (invocar métodos en el assembly,
cargar y enlazar dinámicamente tipos para poder utilizarlos).
Generar dinámicamente y ejecutar código intermedio.
tipos
utilizados
System.Reflection.
para
la
reflexión
se
encuentran
en
el
namespace
Application Domains
La reflexión permite obtener, en tiempo de ejecución, la información sobre el dominio
de una aplicación (application domain). Desde el punto de vista de la reflexión, un
application domain es la raíz de la jerarquía de tipos y es el contenedor de los
assemblies y tipos cuando son cargados en memoria en tiempo de ejecución.
En un sentido más físico un application domain es similar a un proceso de Win32,
provee un espacio independiente y seguro para el manage code similar al límite de un
proceso Win32. El cargador del CLR maneja application domains.
La jerarquía de una aplicación en tiempo de ejecución puede verse del siguiente modo
(figura 11.1):
1/13
Marco Besteiro y Miguel Rodríguez
Reflexión
Figura 11.1
La reflexión ofrece objetos que encapsulan el concepto de assembly, el de módulo y el
de tipo.
Los Assemblies y módulos.
Los assemblies son los bloques de construcción de las aplicaciones .NET. Son similares
a las dll pero en su sentido más amplio (contienen código, recursos, metadatos...): de
hecho, se les llama también “managed dll”. Se podría pensar en un assembly como en
un paquete formado por uno o varios ficheros o módulos.
Los assemblies son la unidad fundamental de desarrollo, control de versiones,
reutilización, control de permisos de seguridad y control de visibilidad de los datos y
métodos.
Los módulos son los ficheros físicos que componen los assemblies (dll, EXEs, ficheros
de recursos...).
La clase System.Reflection.Assembly
La clase Assembly contiene un gran número de métodos y propiedades que permiten
acceder a los metadatos de un assembly, así como cargarlo e incluso ejecutarlo, en caso
de que sea ejecutable.
Algunos de sus métodos más importantes son:
-
Load: permite cargar un assembly. Antes de utilizar una instancia de un
assembly es necesario cargarlo. Load toma como parámetro el nombre del
assembly, lo cual implica que el assembly en ejecución ha de conocer al que se
desea cargar, es decir, en tiempo de compilación debe haberse incluido una
referencia al assembly a cargar.
Por ejemplo:
Assembly miassembly = Assembly.Load(“AssemblyX”);
-
LoadFrom: es similar a Load pero toma como parámetro la ruta física del
assembly en lugar de su nombre (con lo cual no es necesario que el assembly en
ejecución posea una referencia de compilación al assembly a cargar.
Por ejemplo:
Assembly
miassembly
Assembly.LoadFrom(“C:\MisProyectos\Reflection\AssemblyX”);
-
=
GetModule: Devuelve una instancia del módulo cuyo nombre se le ha de pasar
como parámetro.
Por ejemplo:
Module mimodulo = Assembly.GetModule(“ModuloX”);
2/13
Marco Besteiro y Miguel Rodríguez
-
Reflexión
GetModules: Devuelve un array con todos los módulos de un assembly, no
recibe parámetros. El siguiente ejemplo muestra como obtener, en ejecución, el
modulo principal del assembly, que es el que contiene el manifiesto del
assembly:
Assembly assemblyPpal = Assembly.GetExecutingAssembly();
Module moduloPpal = assemblyPpal.GetModules()[0];
-
GetType: devuelve el objeto Type correspondiente al parámetro pasado. El
siguiente ejemplo muestra como crear dinámicamente una instancia de un tipo
cargado también dinámicamente:
Assembly miassembly = Assembly.Load(“AssemblyX”);
// obtener un instancia u objeto tipo, el tipo es TipoX
Type tipox = miassembly.GetType(“TipoX”);
//crear una instancia del tipo tipox,
//que equivale a crear una instancia de TipoX
object o = Activator.CreateInstance(tipox);
Un modo más rápido de hacer lo mismo es:
Assembly miassembly = Assembly.Load(“AssemblyX”);
//crear directamente un objeto del tipo TipoX
object o = miassembly.CreateInstance(“TipoX”);
Es posible cargar un assembly dinámicamente y crear y utilizar instancias de sus
tipos de datos (a esto se le llama enlace tardío de tipos y se explicará más
adelante).
-
GetTypes: devuelve un array con todos los tipos de un assembly. Un ejemplo
típico de su utilización es:
Assembly miassembly = Assembly.Load(“AssemblyX”);
foreach (Type tipo in miassembly.GetTypes())
if (tipo.IsSubclassOf(typeof(TipoX)) {
object o = Activator.CreateInstance(tipo)
…
En este ejemplo, Reflexion_1 carga las dll del Framework .NET mscorlib y
System.XML.dll y muestra información básica sobre las mismas.
using System;
using System.Reflection;
class PruebaReflexion1
{
public static void Main()
{
Assembly as1 = Assembly.Load("mscorlib.dll");
System.Console.WriteLine("Localización: " + as1.CodeBase);
System.Console.WriteLine(as1.FullName);
3/13
Marco Besteiro y Miguel Rodríguez
Reflexión
//es importante notar el uso de @ para indicar que le sigue
//un string de modo que no se interprete \ como carácter
//especial.
as1
=
Assembly.LoadFrom(@"C:\winnt\Microsoft.NET\Framework\v1.0.2
914\System.XML.dll");
System.Console.WriteLine("");
System.Console.WriteLine("Localización: " + as1.CodeBase);
System.Console.WriteLine(as1.FullName);
}
}
El resultado de ejecutar este assembly es:
Figura 11.2
Ejemplo: Es posible mostrar información de cualquier assembly independientemente de
que pertenezca al framework .NET o no. A continuación se muestra un assembly
llamado dll_reflexion y se modifica el anterior ejemplo para que cargue y muestre
información sobre dll_reflexion.
dll_reflexion consta de dos ficheros fuente: CNombre.cs y CNumero.cs.
//Fichero CNombre.cs
using System;
namespace dll_reflexion
{
public class CNombre
{
private string nombre;
public CNombre()
{
//desde aquí es posible utilizar nombre.
nombre = "";
4/13
Marco Besteiro y Miguel Rodríguez
Reflexión
}
public string Nombre
{
get
{
return nombre;
}
set
{
nombre = value;
}
}
public void mostrarNombreyNumero (int inum)
{
//desde aquí es posible utilizar nombre.
System.Console.WriteLine("El valor del nombre es: " +
nombre);
System.Console.WriteLine("El valor del parámetro es:"
+ inum);
}
}
}
//Fichero CNumero.cs
using System;
namespace dll_reflexion
{
public class CNumero
{
private int numero;
public CNumero()
{
//desde aquí es posible utilizar numero.
numero = 0;
}
public int Numero
{
get
{
return numero;
}
set
{
numero = value;
}
}
public void mostrarNumeroyNombre (string snombre)
{
//desde aquí es posible utilizar numero.
System.Console.WriteLine("El valor del numero es: " +
numero);
System.Console.WriteLine("El valor del parámetro es:
" + snombre);
}
}
}
5/13
Marco Besteiro y Miguel Rodríguez
Reflexión
El proyecto Reflexion_1 modificado es:
using System;
using System.Reflection;
class PruebaReflexion1
{
public static void Main()
{
Assembly as1 = Assembly.Load("mscorlib.dll");
System.Console.WriteLine("Localización: " + as1.CodeBase);
System.Console.WriteLine(as1.FullName);
//es importante notar el uso de @ para indicar que le sigue
//un string de modo que no se interprete \ como carácter
//especial.
as1
=
Assembly.LoadFrom(@"C:\winnt\Microsoft.NET\Framework\v1.0.2
914\System.XML.dll");
System.Console.WriteLine("");
System.Console.WriteLine("Localización: " + as1.CodeBase);
System.Console.WriteLine(as1.FullName);
as1 =
Assembly.LoadFrom(@"C:\progs\dll_reflexion\obj\debug\dll_re
flexion.dll");
System.Console.WriteLine("");
System.Console.WriteLine("Localización: " + as1.CodeBase);
System.Console.WriteLine(as1.FullName);
}
}
El resultado de ejecutarlo es:
Figura 11.3
6/13
Marco Besteiro y Miguel Rodríguez
Reflexión
La clase System.Reflection.Module
Al igual que la clase Assembly, la clase Module ofrece un gran número de métodos que
permiten obtener información sobre los módulos de un assembly. Un módulo, del
mismo modo que un assembly, contiene tipos. Por tanto, la clase Module ofrece los
métodos GetType, GetTypes, cuyo funcionamiento es el mismo que en la clase
Assembly.
Además de estos métodos ofrece otros como el método Assembly, que devuelve el
assembly para la instancia del módulo a través de la cual se invoca.
Para obtener un módulo a partir de un assembly se pueden utilizar los métodos:
-
GetModule
GetModules
GetLoadedModules
En el siguiente ejemplo se carga el asembly dll_reflexion.dll y, además de mostrar
información básica sobre el mismo, se obtiene y se muestra el nombre de todos sus
módulos.
using System;
using System.Reflection;
class PruebaReflexion2
{
public static void Main()
{
Assembly as1 =
Assembly.LoadFrom(@"C:\progs\dll_reflexion\obj\debug\dll_re
flexion.dll");
System.Console.WriteLine("");
System.Console.WriteLine("Localización: " + as1.CodeBase);
System.Console.WriteLine(as1.FullName);
Module[] m1 = as1.GetModules();
foreach (Module m in m1)
{
System.Console.WriteLine("");
System.Console.WriteLine("Nombre
m.Name);
}
System.Console.WriteLine("");
}
}
El resultado de ejecutar este ejemplo es:
7/13
del
módulo:
"
+
Marco Besteiro y Miguel Rodríguez
Reflexión
Figura 11.4
Los Tipos.
Los tipos son la base de la reflexión, corresponden a la clase System.Type, que
representa los metadatos para la declaración de tipo en una aplicación.
La clase System.Reflection.Type
Type es una clase base abstracta. Los miembros de esta clase permiten obtener
información sobre una declaración de tipo, como por ejemplo los constructores, los
métodos, campos, propiedades y eventos de una clase, así como el módulo y el
assembly al que pertenece la clase.
Existen varios modos de obtener una referencia a un tipo, los más significativos son:
-
Utilizar el operador typeof.
Por ejemplo:
Type tipo = typeof (int);
-
Utilizar el método GetType, que toda clase hereda de System.Object.
Ejemplo:
int mientero = 5;
Tipo tipo = mientero.GetType();
-
Utilizar el método static GetType, de la clase Type:
Type tipo = Type.GetType(“System.Int32”);
8/13
Marco Besteiro y Miguel Rodríguez
Reflexión
Una vez se dispone de una referencia a un tipo de datos pueden utilizarse los métodos y
propiedades de la clase Type para obtener información sobre el tipo referenciado.
Los métodos más relevantes son:
-
GetConstructor
y
GetConstructors:
devuelven respectivamente una
referencia o un array de referencias de tipo ConstructorInfo.
ConstructorInfo es un tipo de datos que contiene información sobre el
constructor de un Tipo e incluso permite invocarlo (mediante el método
Invoke).
-
GetMethod y GetMethods: devuelven respectivamente una referencia o un array
de referencias de tipo MethodInfo (similar a ConstructorInfo pero para
métodos).
-
GetMember y GetMembers: Devuelven respectivamente una referencia o un array
de referencias de tipo MemberInfo. Se considera un miembro de un Tipo a las
propiedades, métodos, eventos, campos...
-
Los siguientes métodos se comportan de modo análogo a los anteriores:
o
o
o
o
GetField y GetFields
GetEvent y GetEvents
GetInterface y GetInterfaces
GetProperty y GetProperties
Las propiedades más relevantes son:
-
Name: devuelve un string que contiene el nombre del tipo de datos.
FullName: devuelve el nombre completo del tipo, incluido el namespace.
Namespace: devuelve el namespace al que pertenece el tipo de datos.
IsClass: devuelve True si el tipo es una clase.
IsValueType: devuelve True si el tipo es valor (recuérdese que los tipos
-
posibles son “referencia” y “valor”, existiendo un tercer tipo inseguro que es
“puntero”)
BaseType: devuelve el tipo base del tipo dado.
UnderlyingSystemType: devuelve el tipo IL del CLR correspondiente al tipo.
Attributes: devuelve los atributos asociados al tipo.
Assembly: devuelve una referencia al assembly al que pertenece el tipo.
En el siguiente ejemplo Reflexion3 se carga el assembly dll_reflexion.dll y se
muestra información sobre sus tipos.
using System;
using System.Reflection;
class PruebaReflexion3
{
public static void Main()
{
9/13
Marco Besteiro y Miguel Rodríguez
Reflexión
Assembly as1 =
Assembly.LoadFrom(@"C:\progs\dll_reflexion\obj\debug\dll_re
flexion.dll");
System.Console.WriteLine("");
System.Console.WriteLine("Localización: " + as1.CodeBase);
System.Console.WriteLine(as1.FullName);
Type[] t1 = as1.GetTypes();
foreach (Type t in t1)
{
System.Console.WriteLine("");
System.Console.WriteLine("Nombre
t.Name);
}
del
tipo:
"
+
System.Console.WriteLine("");
}
}
El resultado de ejecutar este ejemplo es:
Figura 11.5
Enlace tardío de tipos a una aplicación.
El enlace tardío de tipos consiste en que una aplicación cargue, instancie y utilice un
tipo en tiempo de ejecución. Una vez obtenida una referencia a un tipo de datos, puede
crearse una instancia de tal tipo de datos mediante el método CreateInstance de la
clase System.Activator.
A continuación se pueden utilizar los métodos de la clase Type (GetMethods,
GetMembers...) para obtener los métodos, miembros, etc. del tipo y utilizarlos.
Un caso interesante es la clase MethodInfo. GetMethods devuelve un objeto de tal
clase, el cual contiene información sobre un método. La clase MethodInfo posee un
10/13
Marco Besteiro y Miguel Rodríguez
Reflexión
método llamado Invoke, a través del cual se puede invocar el método cuya información
posee el objeto MethodInfo devuelto por GetMethods.
Ejemplo:
using System;
using System.Reflection;
class PruebaEnlaceTardio
{
public static void Main()
{
Assembly
as1
=
Assembly.LoadFrom(@"C:\progs\dll_reflexion\obj\debug\dll_re
flexion.dll");
Type[] t1 = as1.GetTypes();
foreach (Type t in t1)
{
System.Console.WriteLine("");
System.Console.WriteLine("Nombre
t.Name);
del
tipo:
"
+
if (t.Name.Equals("CNumero"))
{
//Se crea un objeto de la clase CNumero, que es
//el tipo que referencia t
object obj = Activator.CreateInstance(t);
//Se obtiene el método mostrarNumeroyNombre
MethodInfo
metinf
t.GetMethod("mostrarNumeroyNombre");
=
System.Console.WriteLine("Invocación del método
mostrarNumeroyNombre:");
//MethodInfo posee un método llamado 'Invoke'
//mediante 'Invoke' puede invocarse al método
//obtenido 'Invoke' recibe 2 parámetros:
//1) El objeto al que pertenece el método a
//invocar
//2) Un array de objetos con los parámetros del
//método a invocar
object[] arObjs = {"Hola"};
metinf.Invoke(obj, arObjs);
System.Console.WriteLine("");
}
}
System.Console.WriteLine("");
}
}
El resultado de ejecutar este ejemplo es:
11/13
Marco Besteiro y Miguel Rodríguez
Reflexión
Figura 11.6
Creación de nuevos tipos en tiempo de ejecución.
Es posible definir dinámicamente un assembly, módulos dentro del assembly, tipos en
los módulos, con sus miembros, e incluso emitir código IL que implemente la
funcionalidad de tales métodos.
Para realizar esta labor existen clases como AppDomain, AssemblyBuilder,
ModuleBuilder, TypeBuilder, MethodBuilder, ILGenerator…
Todas estas clases pertenecen al namespace System.Reflection.Emit.
AssemblyName,
En el siguiente ejemplo se muestra cómo generar dinámicamente un assembly y un
módulo, así como un tipo de datos CSaludar con un método Saludar e invocarlo.
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace EmisionCodigo
{
public class Emision
{
static void Main()
{
//Obtención del AppDomain actual.
AppDomain appD = AppDomain.CurrentDomain;
//Creación de un assembly de modo dinámico
//en el AppDomain actual.
AssemblyName asN = new AssemblyName();
asN.Name = "AssemblyDinamico";
AssemblyBuilder asB = appD.DefineDynamicAssembly(asN,
AssemblyBuilderAccess.Run);
//Creación de un módulo en el Assembly dinámico
//Assembly assem = (Assembly) asB;
ModuleBuilder
modB
asB.DefineDynamicModule("ModuloDinamico");
12/13
=
Marco Besteiro y Miguel Rodríguez
Reflexión
//Creación de un Tipo (CSaludo) y de un método
//(Saludar) para tal tipo
TypeBuilder
typB
=
modB.DefineType("CSaludar",
TypeAttributes.Public);
MethodBuilder metB = typB.DefineMethod("Saludar",
MethodAttributes.Public, null, null);
//Generación del código IL (MSIL) para el método
//Saludar el método GetILGenerator devuelve una
//referencia al generador de código asociado a un
//método
ILGenerator ilG = metB.GetILGenerator();
ilG.EmitWriteLine("Hola desde el codigo generado
dinámicamente");
ilG.EmitWriteLine(" ");
ilG.Emit(OpCodes.Ret);
//Creaación del Tipo
typB.CreateType();
//Utilización del Tipo
object ob = Activator.CreateInstance(typB);
typB.GetMethod("Saludar").Invoke(ob, null);
}
}
}
El resultado de ejecutar este ejemplo es:
Figura 11.7
13/13
Descargar