Lectura de atributos en tiempo de ejecución

    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ámetro 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íficas 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() Ademá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);
     foreach (MethodInfo metodo in tipo.GetMethods())
      muestra("METODO", metodo);
     foreach (EventInfo evento in tipo.GetEvents())
      muestra("EVENTO", evento);
     foreach (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 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.

Lectura de atributos en tiempo de ejecución
José Antonio González Seco

José Antonio es experto en tecnologias Microsoft. Imparte cursos y conferencias en congresos sobre C# y .NET en Universidades de toda España (Sevilla, Barcelona, San Sebastián, Valencia, Oviedo, etc.) en representación de grandes empresas como Microsoft.
Fecha de alta:30/11/2006
Última actualizacion:30/11/2006
Visitas totales:15476
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com