Para acceder a los metadatos de
cualquier ensamblado se utilizan las clases del espacio de nombres System.Reflection.
Este espacio de nombres es inmenso y
explicar cómo utilizarlo queda fuera del alcance de este libro, aunque de todos
modos a continuación se darán unas ideas básicas sobre cómo acceder a través de
sus tipos a los atributos incluidos en los ensamblados.
La clave para acceder a los
atributos se encuentra en el método estático de la clase System.Attribute
llamado Attribute[]
GetCustomAttributes(<x> objetoReflexivo), donde <x> es el tipo de System.Reflection que
representa a los elementos cuyos atributos se desea obtener. Los posibles tipos
son: Assembly,
que representa ensamblados, Module que representa módulos, MemberInfo que
representa miembros (incluidos tipos, que al fin y al cabo son miembros de
espacios de nombres), y ParameterInfo que representa parámetros. El
parámetros tomado por este método será el objeto que represente al elemento en
concreto cuyos metadatos se quieren obtener.
Como se ve, GetCustomAttributes()
devuelve una tabla con los atributos en forma de objetos Attribute, que es la
clase base de todos los atributos, por lo que si a partir de ellos se desease
acceder a características específica de cada tipo de atributo habría que
aplicar downcasting como se comentó en el Tema
5: Clases (para asegurase de que
las conversiones se realicen con éxito recuérdese que se puede usar el operador
is
para determinar cuál es el verdadero tipo de cada atributo de esta
tabla)
Para obtener el objeto Assembly que
representa al ensamblado al que pertenezca el código que se esté ejecutando se
usa el método Assembly
GetExecutingAssembly() de la clase Assembly, que se usa
tal y como se muestra:
Assembly ensamblado =
Assembly.GetExecutingAssembly();
Otra posibilidad sería obtener
ese objeto Assembly
a partir del nombre del fichero donde se encuentre almacenado el
ensamblado. Para ello se usa el método Assembly LoadFrom(string rutaEnsamblado) de la
clase Assembly
como se muestra:
Assembly ensamblado =
Assembly.LoadFrom(“josan.dll”);
Una vez obtenido el objeto que
representa a un ensamblado pueden obtenerse los objetos Module que
representan a los módulos que lo forman a través de su método Module[]
GetModules().
A partir del objeto Module
que representa a un módulo puede obtenerse los objetos Type que representan a
sus tipos a través de su método Type[] GetTypes() Otra
posibilidad sería usar el operador typeof ya visto para obtener el Type que
representa a un tipo en concreto sin necesidad de crear objetos Module o
Assembly.
En cualquier caso, una vez
obtenido un objeto Type, a través de sus métodos FieldInfo[]
GetFields(), MethodInfo[] GetMethods(), ConstructorInfo[]
GetConstructors(), EventInfo[] GetEvents[] y PropertyInfo[] GetProperties() pueden
obtenerse los objetos reflexivos que representan, de manera respectiva, a sus
campos, métodos, constructores, eventos y
propiedades o indizadores. Tanto todos estos objetos como los objetos Type derivan
de MemberInfo,
por lo que pueden ser pasados como parámetros de GetCustomAttributes()
para obtener los atributos de los elementos que representan.
Por otro lado, a través de los
objetos MethodInfo
y
ConstructorInfo, es posible obtener los tipos reflexivos que
representan a los parámetros de métodos y constructores llamando a su método ParameterInfo[]
GetParameters() Admemás, en el caso de los objetos MethodInfo también
es posible obtener el objeto que representa al tipo de retorno del método que
representan mediante su propiedad Type ReturnType {get;}.
En lo referente a las
propiedades, es posible obtener los objetos MethodInfo que
representan a sus bloques get y set a través de los métodos MethodInfo GetSetMethod()
y MethodInfo
GetSetMethod() de los objetos PropertyInfo que las representan. Además, para
obtener los objetos reflexivos que representen a los índices de los indizadores
también se dispone de un método ParamterInfo[] GetIndexParameters()
Y en cuanto a los eventos, los
objetos EventInfo
disponen de métodos MethodInfo GetAddMethod() y MethodInfo GetRemoveMethod()
mediante los que es posible obtener los objetos reflexivos que representan a
sus bloques add
y remove.
A continuación se muestra un
programa de ejemplo que lo que hace es mostrar por pantalla el nombre de todos
los atributos que en él se hayan definido:
using System.Reflection;
using System;
[assembly: EjemploEnsamblado]
[module: EjemploModulo]
[AttributeUsage(AttributeTargets.Method)]
class EjemploMétodo:Attribute
{}
[AttributeUsage(AttributeTargets.Assembly)]
class EjemploEnsamblado:Attribute
{}
[AttributeUsage(AttributeTargets.Module)]
class EjemploModulo:Attribute
{}
[AttributeUsage(AttributeTargets.Class)]
class EjemploTipo:Attribute
{}
[AttributeUsage(AttributeTargets.Field)]
class EjemploCampo:Attribute
{}
[EjemploTipo]
class A
{
public static void Main()
{
Assembly ensamblado
= Assembly.GetExecutingAssembly();
foreach (Attribute
atributo in Attribute.GetCustomAttributes(ensamblado))
Console.WriteLine("ENSAMBLADO:
{0}",atributo);
foreach
(Module modulo in ensamblado.GetModules())
{
foreach(Attribute atributo in Attribute.GetCustomAttributes(modulo))
Console.WriteLine("MODULO:
{0}", atributo);
foreach
(Type tipo in modulo.GetTypes())
{
foreach(Attribute atributo in Attribute.GetCustomAttributes(tipo))
Console.WriteLine("TIPO:
{0}", atributo);
foreach (FieldInfo campo in tipo.GetFields())
muestra("CAMPO",
campo);
f oreach (MethodInfo
metodo in tipo.GetMethods())
muestra("METODO",
metodo);
foreach (EventInfo evento in
tipo.GetEvents())
muestra("EVENTO",
evento);
f oreach
(PropertyInfo propiedad in tipo.GetProperties())
muestra("PROPIEDAD", propiedad);
foreach (ConstructorInfo constructor in tipo.GetConstructors())
muestra("CONSTRUCTOR",constructor);
}
}
}
static private void
muestra(string nombre, MemberInfo miembro)
{
foreach (Attribute atributo in
Attribute.GetCustomAttributes(miembro))
Console.WriteLine("{0}:
{1}", nombre, atributo);
}
}
Lo único que hace el Main() de
este programa es obtener el Assembly que representa el ensamblado actual y
mostrar todos sus atributos de ensamblado. Luego obtiene todos los Modules
que representa a los módulos de dicho ensamblado y muestra todos los atributos de módulo de cada uno. Además, de
cada módulo se obtienen todos los Types que representan a los tipos en él definidos
y se muestran todos sus atributos; y de cada tipo se obtienen los objetos
reflexivos que representan a sus diferentes tipos de miembros y se muestran los
atributos atributos de cada miembro.
Aparte del método Main() en
el ejemplo se han incluido definiciones de numerosos atributos de ejemplo
aplicables a diferentes tipos de elemento y se han diseminado a lo largo del
fuente varios usos de estos atributos. Por ello, la salida del programa es:
ENSAMBLADO: EjemploEnsamblado
ENSAMBLADO:
System.Diagnostics.DebuggableAttribute
MODULO EjemploModulo
TIPO:
System.AttributeUsageAttribute
TIPO: System.AttributeUsageAttribute
TIPO: System.AttributeUsageAttribute
TIPO: System.AttributeUsageAttribute
TIPO: System.AttributeUsageAttribute
TIPO: EjemploTipo
METODO: EjemploMétodo
Nótese que aparte de los atributos utilizados en el código fuente, la
salida del programa muestra que el compilador ha asociado a nivel de ensamblado
un atributo extra llamado Debuggable. Este atributo incluye información
sobre si pueden aplicarse optimizaciones al compilar JIT el ensamblado o si se
ha de realizar una traza de su ejecución. Sin embargo, no conviene fiarse de su
implementación ya que no está documentado por Microsoft y puede cambiar en
futuras versiones de la plataforma .NET.