Atributos en .NET. Definición de atributos

Introducción

Una de las posibilidades que incluye .NET es el uso de atributos para proporcionar metainformación añadida a las distintas partes del código que creamos.

Los atributos de .NET nos proporcionan una poderosa herramienta para añadir información a nuestras clases, métodos, propiedades etc.

Un atributo puede aplicarse puede aplicarse sobre cualquier elemento básico de .NET es decir, clases, eventos, propiedades, métodos, etc y añadirá información a dicho elemento que posteriormente podremos consultar en tiempo de diseño, compilación o ejecución.

Aspect Oriented Programming (Programación orientada a aspectos)

El uso de atributos nos permite acercar la programación en .NET al concepto de AOP, que son las siglas inglesas de Aspect Oriented Programing o Programación arientada a aspectos.

Para comprender correctamente en que consiste el paradigma de programación orientada a aspectos debemos partir de la base de las limitaciones de la programación orientada a objetos. En el paradigma orientado a objetos, el concepto de herencia describe las relaciones jerárquicas entre los distintos objetos que a su vez modelan el mundo real (o conceptos de este).

Sin embargo, en el mundo real, existen determinados aspectos de la realidad que pueden ser comunes a distintos objetos, conocimiento asociado que permite deducir otras cosas. Por ejemplo, en el mundo real sabemos que los objetos de metal se oxidan mientras que los objetos de madera no lo hacen. Sin embargo, el modelo orientado a objetos tan solo contempla la herencia entre unas clases y otras. Aunque podemos definir una jerarquía tal que el hierro derive de metal (por ejemplo), ¿dónde situaríamos una mesa de hierro?, ¿heredaría de "mueble" o de "hierro"? Por otro lado no todos los metales son oxidables (por ejemplo el acero "inoxidable" no lo es) asi que no podemos asumir que TODOS los metales lo son.

Resulta extremadamente difícil incluir el concepto de oxidación, de que un objeto es oxidable, dentro de la jerarquía de clases, la herencia múltiple podría resolver el problema, manteniendo la jerarquía de herencia y añadiendo una herencia múltiple hacía una clase oxidable. No obstante la herencia múltiple está soportada por pocos lenguajes de programación, entre otras cosas debido a las posibles ambigüedades que se obtienen.

El uso de atributos nos permite separar entre la jerarquía de clases, que representa las relaciones de parentazgo entre distintos objetos de los aspectos o características de esos objetos por pertenecer a un determinado grupo de objetos.

¿Para que sirven?

El ejemplo anterior era poco práctico pero existen usos mucho más útiles para los atributos... A nivel de software podemos caracterizar con aspectos muy distintos.

Un ejemplo más relacionado con el software podemos encontrarlo en el propio framework .NET. En el framework existe un atributo llamado "obsolete" que podemos utilizar para marcar aquellos fragmentos de código (funciones, clases, librerías completas) que, aunque se mantienen por compatibilidad hacia atras, han quedado obsoletas.

En el caso de que dicho atributo esté presente el compilador emitirá un aviso (warning) al compilar para informarnos de ello.

En .NET podemos acceder a los atributos de un objeto en tiempo de ejecución y realizar acciones (código condicional) en función de dichos atributos. Por ejemplo, para un motor gráfico podríamos definir el atributo "inamovible" de forma que cualquier objeto que contenga dicho atributo no se vea afectado por las fuerzas físicas del motor. Lo interesante de este asunto es que los atributos funcionan de una forma "paralela" a la del código base. Explico esto último: una de las opciones para el caso anterior sería crear una propiedad o implementar un interfaz, sin embargo eso implica introducir una modificación de código. El uso de un atributo nos permite modificar el comportamiento sin necesidad de tocar el código o de crear "estructuras especiales", si queremos convertir un objeto en inamovible bastará con marcarlo con el atributo adecuado (y lo mismo vale para otros atributos según lo necesitemos).

Atributos intrinsecos y atributos de usuario

Podemos distinguir entre dos tipos distintos de atributos en función de si están integrados en el framework de .NET o si son creados por el usuario.

En realidad ambos tipos de atributos revierten en lo mismo pero el framework de .NET hace uso de algunos de dichos atributos e incorpora cierta funcionalidad predefinida. De esta forma, por ejemplo el uso del atributo "Serializable" nos permite marcar una clase para ser mandada de forma serial por un canal de transmisión (por ejemplo por TCP) o el ya mencionado "Obsolete" que es utilizada por el compilador.

Aplicando atributos

Para aplicar un atributo a un elemento de código en .NET bastará con indicar el atributo entre corchetes justo delante del elemento de código al que deseamos aplicarlo. Podemos indicar varios atributos para el mismo elemento, poniéndolos uno detrás de otro.

Como veremos más adelante un atributo es en el fondo nada más que una clase, con su constructor y sus propiedades por lo que la "invocación" del atributo se realizará de una forma muy similar a como se realiza una llamada a un constructor. Por ejemplo:

[Obsolete("Esta función es obsoleta. Utilice la función bar en su lugar")]
[Loggable]
void Foo()

No todas las propiedades de un atributo tienen porque ser inicializadas en el constructor (todas tendrán un valor por defecto de acuerdo a los estándares de .NET) sino que algunas pueden estar declaradas tan solo por si queremos utilizarlas. Para dichas inicializaciones debemos, a la vez que aplicamos el atributo, indicar el nombre de la propiedad seguido del valor que queremos asignar. Otro ejemplo:

[AttributeUssage(AttributeTargets.Class, Inherited = False, AllowMultiple = True)]
plubic class MiAtributo

Declaración de atributos de usuario

Hasta ahora hemos estado viendo tan solo el uso de los atributos de .NET y unos hipotéticos atributos definidos por nosotros mismo, ahora vamos a ver los pasos que debemos realizar para definir esos atributos.

Definir un atributo de usuario es extremadamente sencillo en .NET. Básicamente un atributo es una clase que hereda de la clase Attribute. Y prácticamente eso es todo, nada más. Los siguientes pasos consiste en definir las propiedades que queremos que estén disponibles para su lectura en rutime. Vamos a ver un ejemplo:

Nota: Aunque no es obligatorio, al crear atributos suele añadirse la palabra Attribute como sufijo al final del nombre de la clase. El compilador interpreta dicha palabra y permite referirse al mismo de cualquiera de las dos formas, de forma que si tenemos LogAttribute podemos referirnos a él como LogAttribute o como Log a secas

[AttributeUssage(AttributeTargets.Class)]
public class InamovibleAttribute : Attribute
{
}

[AttributeUssage(AttributeTargets.Class)]
public class BoundingObjectAttribute : Attribute
{
  private int boundRate;

  public BoundingObjectAttribute(int boundRate)
  {
    this.boundRate = boundRate;
  }
}

[Inamovible]
[BoundingObject(3)]
public class MyWall
{
  // Clase que implementa una pared que va a ser no movible y que va
  // a hacer rebotar objetos (bound)
}

AttributeUssage y Ámbito de aplicación de atributos

Cuando definimos un atributo en .NET tenemos la posibilidad de especificar el ámbito de aplicación que queremos que se aplique en dicho atributo, es decir, a que elementos del código vamos a permitir que sea aplicable.

Para definir dicho ámbito de aplicación utilizaremos el atributo AttributeUssage que viene predefinido en el propio .NET, es decir, vamos a utilizar un atributo para definir las posibilidades de otro atributo. Si examinamos la definición el constructor del atributo AttributeUssage:

public AttributeUsageAttribute(AttributeTargets validOn);

que nos permite definir en el propio constructor a que elementos deseamos que se aplique el atributo.

Para ello la clase de enumeración AttibuteTargets define diversos valores que nos permitirán especificar si deseamos aplicar el atributo a clases, métodos, eventos, etc. En la siguiente tabla tenemos los valores que podemos definir, dichos valores se pueden además combinar de forma que definamos un conjunto de valores permitidos.

All Permite aplicar el atributo a cualquier elemento
Assembly Permite aplicar el atributo a nivel de ensamblado
Class Permite aplicar el atributo a nivel de clase
Constructor Permite aplicar el atributo a un constructor de una clase
Delegate Permite aplicar el atributo a una definición de delegado
Enum Permite aplicar el atributo a una definición de enumerado
Event Permite aplicar el atributo a la definición de un evento
Field Permite aplicar el atributo a un campo de una clase
Interface Permite aplicar el atributo a la definición de un interfaz
Method Permite aplicar el atributo a un método de una clase
Module Permite aplicar el atributo a un módulo. Un módulo se refiere a un archivo PE (.exe o .dll)
Parameter Permite aplicar el atributo a un parámetro de una función
Property Permite aplicar el atributo a una propiedad de una clase
Struct Permite aplicar el atributo a una estructura

Por ejemplo, si queremos definir que nuestro atributo tan solo pueda aplicarse a métodos y eventos:

[AttributeUssage(AttributeTargets.Event | AttributeTargets.Method)]
public class MiAtributoAttribute
{
  // Definición del atributo.
}

Por último el atributo AttributeUssage nos permite definir otras dos propiedades adicionales inherited y AllowMultiple.

  • Inherited nos permite definir que el atributo sea heredable ,es decir, que si una clase aplica el atributo, todos sus clases derivadas también lo aplicarán.
  • AllowMultiple permite especificar si el atributo se podrá especificar varias veces sobre el mismo elemento. Por ejemplo, si tenemos un atributo Exportable cuyo argumento es el tipo de archivo al que se puede exportar, querremos que se pueda aplicar varias veces:

[Exportable("pdf")]
[Exportable("doc")]
public class TestClass{
  // La clase
}

10
Average: 10 (1 vote)
Your rating: None