Atributos en .NET. Leyendo los atributos con Reflection

¿Qué es .NET reflection?

La librería reflection de .NET nos va a permitir acceder a la información de los atributos que hemos definido en el artículo anterior.

Reflection es el nombre dado por microsoft al conjunto de utilidades que permite leer la información y metainformación de las dll y ejecutables de .NET en tiempo de ejecución. Su utilidad fundamental está contenida en la biblioteca System.Reflection y contiene diversas funciones para cargar ensamblados, crear objetos, invocar métodos o leer información, todo ello en tiempo de ejecución.

Este artículo se centrará unicamente en el uso de .NET para leer la información de atributos, aunque se hará uso de otras funciones para acceder a las clases o los métodos involucrados. Para más información os recomiendo que leáis el artículo sobre .NET+Reflection

La clase Type

La clase Type constituye la base de la librería de reflexión, encapsulando la información de las clases de un ensamblado así como acciones tales como la creación de objetos en runtime, etc.

Todos los objetos de .NET derivan en última instancia de la clase Object. Dicha clase define un método virtual llamado GetType que nos permite obtener la instancia de tipo Type que representa dicha clase. Cada Type define única y exclusivamente una clase del código y se garantiza que si y solo si dos objetos pertenencen a la misma clase entonces su Type es el mismo.

GetCustomAttributes

Para nuestro caso sin embargo vamos a centrarnos en una función llamada GetCustomAttributes de la clase Type, o su correpondiente estático de la clase Attribute. Dicha función nos permite elaborar los atributos asociados a cualquier objeto definido en el código. Veamos su definición:

public abstract object[] GetCustomAttributes(
   bool inherit
);
public abstract object[] GetCustomAttributes(
   Type attributeType,
   bool inherit
);

La función nos permite recuperar los atributos asignados a un elemento de código, en este caso un objeto, especificando además si deseamos recorrer toda la cadena de herencia hacía los padres (inherit) y si queremos que nos devuelva tan solo aquellos atributos de un determinado tipo.

Por ejemplo, supongamos que tenemos una función que recibe cualquier tipo de objeto, comprueba si implementa un interface y en caso de que lo implemente realiza alguna acción. Sin embargo, para aumentar la seguridad, deseamos comprobar antes de realizar ninguna acción si el objeto recibido está obsoleto:

void MyFunc(object obj)
{
  Type t = obj.GetType();
  // Obtener el array de atributos Obsolete del objeto mirando en
  // la cadena de herencia
  object[] attr = t.GetCustomAttributes(typeof(ObsoleteAttribute), true));
  if (attr.Length > 0)
  {
    // Obsolete esta definido ... obtenemos el mensaje
    ObsoleteAttribute obs = (ObsoleteAttribute)attr[0];
    MessageBox.Show("El objeto suministrado es obsoleto: " + obs.Message);
  }
  else
     // Hacer lo necesario
}

Otro ejemplo relacionado con aquel del motor gráfico que vimos en el artículo anterior. Supongamos que tenemos una función que procesa una colisión contra un objeto. Para ello se le pasa a dicha función el objeto que recibe la colisión y un objeto de tipo Collision que recoge la información sobre la fuerza y dirección del impacto. Ahora bien, solo queremos tener en cuenta dicha colisión si el objeto que la recibe no es inamovible, o, por ejemplo, podríamos comprobar otras condiciones del objeto:

void HacerColision(CObjeto3D obj, CColision col)
{
  // Comprobamos si el objeto es inamovible
  Type t = obj.GetType();
  if (t.GetCustomAttributes(typeof(InamovibleAttribute), true)).Length > 0)
    return; // El objeto no se puedem mover

  object[] attr = t.GetCustomAttributes(typeof(RompibleAttribute), true));
  if (attr.Length > 0)
  {
     // El objeto se puede romper, obtenemos la información
     RompibleAttribute rAttr = (RompibleAttribute)rAttr[0];
     int FuerzaSoportada = rAttr.Fuerza;
     // Comprobamos si la fuerza de la colision supera la soportada...
  }
}

Además de la clase Type podemos acceder a los atributos de cualquier elemento en el que, de hecho, se pueda definir un atributo. Eso quiere decir que podemos acceder a los atributos de un ensamblado mediante la función Assembly.GetCustomAttributes, a los de un metodo mediante MethodInfo.GetCustomAttributes, etc. Otro método para acceder a ellos es utilizar la función estática Attribute.GetCustomAttributes que admite varias sobrecargas en función del tipo que queramos pasar, es decir, en función de si pasamos un Type, un MemberInfo, un AssemblyInfo, etc

public static Attribute[] GetCustomAttributes(
   Assembly element
);

Para acabar

Hemos visto que podemos acceder a la información de los atributos de, casi, cualquier elemento de código. Por otro lado, la posibilidad de .NET de cargar cualquier ensamblado en tiempo de ejecución mediante el uso de Reflection nos permite no solamente acceder a los atributos de los objetos físicos que nos pasan sino examinar cualquier ensamblado, objeto por objeto y método por método en busca de un determinado atributo lo cual nos abre un enorme abanico de posibilidades.

Para ver un ejémplo práctico del uso de atributos en una aplicación real, y aunque es un ejemplo razonablemente complejo, os recomiendo que le echéis un vistazo al artículo Sistema de Plugins con C# y .NET en el que se hace un uso bastante extenso de diversos atributos para contemplar toda la información de los diversos plugins.

10
Average: 10 (6 votes)
Your rating: None