Atributos

Anuncio
Marco Besteiro y Miguel Rodríguez
Atributos
Attributes.
El lenguaje C# permite al programador especificar información declarativa (atributos)
de entidades definidas en el código. Un ejemplo de esto son los modificadores de
accesibilidad (public, protected, internal y private) que permiten indicar desde
dónde puede utilizarse un método (visibilidad del método).
C# permite también definir nuevos tipos de información declarativa (atributos definidos
por el usuario) para varias entidades de programación y también permite obtener esa
información en tiempo de ejecución. Por ejemplo, podría definirse el atributo
InfoAttribute, el cual puede ser utilizado en clases y métodos para que los elementos
del programa puedan acceder a la información sobre esas clases y métodos.
Se puede decir que un atributo es un tipo de datos -una clase- que se puede utilizar para
“decorar” a otras clases o elementos -métodos, variables...-. Los atributos se guardan
con los datos y puesto que ofrecen información sobre éstos se les llama metadatos. Tal
información se guarda en tiempo de compilación, en el assembly, junto a los datos y
puede ser consultada durante la ejecución.
La clase System.Attribute
Todos los atributos derivan directa o indirectamente de esta clase heredando ciertas
características. En realidad podría utilizarse como atributo cualquier tipo definido por el
usuario, pero es mejor utilizar tipos derivados de System.Attribute.
-
Los atributos pueden ser asociados a distintos elementos:
Nombre de elemento Descripción
El atributo puede ser aplicado a cualquier elemento.
Assembly
El atributo puede ser aplicado a un assembly.
Class
El atributo puede ser aplicado a una clase.
ClassMembers
El atributo puede ser aplicado a clases, structuras,
enumeraciones, constructores, métodos, propiedades,
campos, eventos, delegates e interfaces.
Constructor
El atributo puede ser aplicado a un constructor.
Delegate
El atributo puede ser aplicado a un delegate.
Enum
El atributo puede ser aplicado a una enumeration.
Event
El atributo puede ser aplicado a un evento.
Field
El atributo puede ser aplicado a un campo.
Interface
El atributo puede ser aplicado a un interface.
Method
El atributo puede ser aplicado a un método.
Module
El atributo puede ser aplicado a un module.
Parameter
El atributo puede ser aplicado a un parámetro.
Property
El atributo puede ser aplicado a una propiedad.
ReturnValue
El atributo puede ser aplicado a un valor de retorno.
Struct
El atributo puede ser aplicado a un tipo valor.
All
1/14
Marco Besteiro y Miguel Rodríguez
Atributos
Tabla 12.1
-
Los atributos pueden ser heredados o no por un elemento derivado.
Se puede decidir si se permite o no múltiples instancias de un atributo en el
mismo elemento.
Los compiladores y demás herramientas de desarrollo utilizan esta información para
identificar de qué tipo son los atributos personalizados que encuentran en el código.
Los atributos personalizados pueden almacenarse con cualquier elemento de los
metadatos, de este modo es posible almacenar información sobre la aplicación en
tiempo de compilación y acceder a ésta en tiempo de ejecución o cuando otra
herramienta lee los metadatos.
A continuación se muestra un ejemplo de utilización de una clase atributo
(InfoAttribute) y la propia clase.
...
...
//El atributo Info corresponde a la clase InfoAttribute
//se está aplicando a la clase CSaludar
[Info("20/09/2001", Informacion = "Esta clase posee dos métodos, el
constructor y Saludar")]
public class CSaludar
{
private string nombre;
public CSaludar(string nombre)
{
this.nombre = nombre;
}
public void Saludar()
{
System.Console.WriteLine("Hola " + nombre);
}
}
...
...
La clase correspondiente es InfoAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method
| AttributeTargets.Field,
AllowMultiple = true, Inherited = false)]
public class InfoAttribute : Attribute
{
private DateTime fecha;
public string informacion;
public InfoAttribute(string fecha)
{
this.fecha = DateTime.Parse(fecha);
}
public string Informacion
{
get
{
return informacion;
}
2/14
Marco Besteiro y Miguel Rodríguez
Atributos
set
{
informacion = value;
}
}
public DateTime Fecha
{
get
{
return fecha;
}
}
}
Se puede observar que InfoAttribute tiene dos campos, fecha e información
Parámetros posicionales y con nombre.
Un parámetro posicional de un tipo Attribute se define mediante un constructor
público de tal tipo.
Un parámetro con nombre se define mediante una propiedad de lectura-escritura no
static.
La diferencia entre ambos tipos de parámetros esque el primero sólo tiene método get,
de modo que la única manera de establecerlo es mediante el constructor de la clase
atributo.
En el ejemplo anterior, fecha es un parámetro posicional e Información es un
parámetro con nombre.
Es muy sencillo apreciar la diferencia cuando se utiliza el atributo, los parámetros
posicionales se pasan directamente al constructor del atributo y los parámetros con
nombre se pasan indicando el nombre.
[Info("20/09/2001", Informacion = "Esta clase posee dos métodos, el
constructor y Saludar")]
public class CSaludar
{
…
…
}
Atributos reservados.
En el ejemplo estudiado, la clase InfoAttribute ha sido creada por el desarrollador.
Además de la posibilidad de que el desarrollador cree sus propias clases Attribute
existe la posibilidad de que utilice clases Attribute ya definidas en el Framework
.NET. A los atributos de tales clases se les llama atributos reservados.
ConditionalAttribute.
Permite la definición de métodos condicionales, es decir, métodos a los que sólo se
compilarán llamadas en función de una condición.
3/14
Marco Besteiro y Miguel Rodríguez
Atributos
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class ConditionalAttribute: System.Attribute
{
public ConditionalAttribute(string conditionalSymbol) {…}
public string ConditionalSymbol { get {…} }
}
Un método condicional está sujeto a las siguientes restricciones:
-
Debe ser un método en una declaración de clase. Si fuese de un interface se daría
un error en tiempo de compilación.
El tipo devuelto por el método condicional ha de ser void.
El método condicional no debe estar marcado con el modificador override (sí
con virtual).
El método condicional no debe ser una implementación de un método de un
interface. Si lo es se dará un error de compilación.
El método condicional no debe ser utilizado en una expresión de creación de un
delegate. Si se utiliza se dará un error en tiempo de compilación.
Ejemplo: En el siguiente ejemplo su muestran tres clases (Clase1, Clase2 y Clase3).
Clase1 define un método condicional, Clase2 invoca tal método teniendo la condición
definida y Clase3 lo invoca con la condición sin definir.
//Fichero Clase1.cs
using System;
//Para utilizar Conditional es necesario
//el namespace System.Runtime.InteropServices
using System.Diagnostics;
namespace Clases_NameSpace
{
public class Clase1
{
//Sólo se compilara una llamada a FunDebug
//si está definido DEPURACION
[Conditional("DEPURACION")]
public static void FunDebug()
{
System.Console.WriteLine("Clase1.FunDebug ha
sido llamada");
}
}
}
//Fichero Clase2.cs
#define DEPURACION
using System;
namespace Clases_NameSpace
{
public class Clase2
{
public static void FunClase2 ()
{
4/14
Marco Besteiro y Miguel Rodríguez
Atributos
System.Console.WriteLine("Se va a intentar llamar a
Clase1.FunDebug desde Clase2.FunClase2");
// se llama a FunDebug
Clase1.FunDebug();
}
}
}
//Fichero Clase3.cs
#undef DEPURACION
using System;
namespace Clases_NameSpace
{
class Clase3
{
public static void FunClase3 ()
{
System.Console.WriteLine("Se va a intentar
Clase1.FunDebug desde Clase3.FunClase3");
// no se llama a FunDebug
Clase1.FunDebug();
}
}
}
//Fichero PruebaAtributosReservados.cs
using System;
using System.Reflection;
using Clases_NameSpace;
class PruebaAtributosReservados
{
public static void Main()
{
Clase2.FunClase2 ();
Clase3.FunClase3 ();
System.Console.WriteLine(" ");
}
}
El resultado de ejecutar el ejemplo será:
5/14
llamar
a
Marco Besteiro y Miguel Rodríguez
Atributos
Figura 12.1
ObsoleteAttribute.
Se utiliza para marcar elementos que no debieran ser utilizados en adelante.
[AttributeUsage(AttributeTargets.All)]
public class ObsoleteAttribute: System.Attribute
{
public ObsoleteAttribute(string message) {…}
public string Message { get {…} }
public bool IsError{ get {…} set {…} }
}
Si en el ejemplo anterior se modifica el método FunDebug de Clase1 añadiendo el
atributo Obsolete, se tendrá:
using System;
//Para utilizar Conditional y Obsolete es necesario
//el namespace System.Runtime.InteropServices
using System.Diagnostics;
namespace Clases_NameSpace
{
public class Clase1
{
//Sólo se compilara una llamada a FunDebug
//si está definido DEPURACION
[Conditional("DEPURACION")]
[Obsolete("FunDebug va a ser sustituida por otra función,
no la utilice")]
public static void FunDebug()
{
System.Console.WriteLine("Clase1.FunDebug ha sido
llamada");
}
}
}
6/14
Marco Besteiro y Miguel Rodríguez
Atributos
Este ejemplo afecta a la compilación, el resultado será que el compilador mostrará un
warning por cada intento de utilizar el método FunDebug . Se puede observar en la
Clase1.FunDebug
añadida
al
fichero
figura
12.2
la
llamada
a
PruebaAtributosReservados.cs.
Figura 12.2
DllImportAttribute.
No siempre se puede conseguir lo que se desea con la librería de clases base .NET.
En tales ocasiones puede ser necesario acceder al API de Windows, es decir, a
funciones contenidas las librerías de enlace dinámico (dll) de tipo Win32.
En el siguiente ejemplo se añadirá una llamada a la función MessageBox desde la clase
PruebaAtributosReservados.
using System;
using System.Reflection;
using Clases_NameSpace;
//Para utilizar DllImport es necesario el namespace
//System.Runtime.InteropServices
using System.Runtime.InteropServices;
class PruebaAtributosReservados
{
[DllImport("User32.dll")]
public static extern int MessageBox(int hParent, string Message,
string Caption, int Type);
7/14
Marco Besteiro y Miguel Rodríguez
Atributos
public static void Main()
{
Clase2.FunClase2 ();
Clase3.FunClase3 ();
System.Console.WriteLine(" ");
System.Console.WriteLine("Para llamar a MessageBox pulse
Enter: ");
System.Console.ReadLine();
MessageBox(0, "Hola desde el API de Windows", "Mensaje
invocado desde .NET", 0);
}
}
El resultado de ejecutar este ejemplo será:
Figura 12.3
Tras pulsar Enter se mostrará el cuadro de mensaje:
Figura 12.4
StructLayoutAttribute.
Este atributo permite indicar el alineamiento (struct layout) de los miembros de una
clase o estructura.
El alineamiento de los miembros de una clase o estructura determina el tamaño que
ocupa cada miembro (por ejemplo, si se la línea a 4 bytes, todos los miembros de una
estructura o clase han de ocupar múltiplos de 4 bytes) y en consecuencia, también
8/14
Marco Besteiro y Miguel Rodríguez
Atributos
determina el desplazamiento u offset de cada miembro de la clase o estructura
respecto del comienzo de la clase o estructura.
CLSCompliantAttribute.
Indica si un elemento de un programa cumple la CLS (Common Languaje
Specification), es decir, si es compatible con el lenguaje común de la plataforma .NET.
Por defecto, un elemento al que no se haya aplicado el atributo CLSCompliant:
-
Se considera que el assembly no cumple la especificación de lenguaje común
(CLS).
Se considera que el tipo cumple la CLS si el tipo que lo contiene o el assembly
cumple la CLS.
Se considera que un miembro de un tipo cumple la CLS sólo si la cumple el tipo
al que pertenece.
Si un assembly se marca como CLSCompliant, cualquier tipo público del assembly que
no sea CLSCompliant debe ser marcado con el atributo CLSCompliant pasándole False
como argumento. Del mismo modo, en una clase marcada como CLSCompliant han de
marcarse como no CLSCompliant todos los miembros que no sean CLSCompliant.
Todos los miembros que no sean CLSCompliant debieran tener alternativas
CLSCompliant.
SerializableAttribute y NonSerializableAttribute.
Estos atributos se utilizan para indicar que una clase y/o los miembros de una clase son
o no serializables.
La serialización permite programar cómodamente qué datos de una clase se envían a un
flujo. Un flujo es un concepto que puede asociarse a un fichero, a la red, etc...
No debe utilizarse el atributo Serializable con clases que contengan punteros,
handles u otras estructuras de datos que no puedan ser reconstruidos al deserializarlos
en un entorno diferente (por ejemplo, los punteros tienen sentido en el proceso al que
pertenecen pero fuera de los límites del proceso al que pertenecen, la dirección a la que
apuntan no es válida).
AttributeUssageAttribute.
Este atributo se utiliza para describir el modo en que puede ser utilizado un atributo.
Una clase decorada con este atributo debe derivar de System.Attribute directa o
indirectamente. En caso de no ser así se dará un error de compilación.
El atributo AttributeUsage se utiliza para crear nuevas clases de atributos, como ya se
ha visto.
La clase de tipo Attribute AttributeUsageAttribute es:
[AttributeUsage(AttributeTargets.Class)]
public class AttributeUsageAttribute: System.Attribute
{
public AttributeUsageAttribute(AttributeTargets validOn) {…}
public AttributeUsageAttribute(AttributeTargets validOn,
bool allowMultiple,
9/14
Marco Besteiro y Miguel Rodríguez
Atributos
bool inherited) {…}
public virtual bool AllowMultiple { get {…} set {…} }
public virtual bool Inherited { get {…} set {…} }
public virtual AttributeTargets ValidOn { get {…} }
}
public enum AttributeTargets
{
Assembly
= 0x0001,
Module
= 0x0002,
Class
= 0x0004,
Struct
= 0x0008,
Enum
= 0x0010,
Constructor = 0x0020,
Method
= 0x0040,
Property
= 0x0080,
Field
= 0x0100,
Event
= 0x0200,
Interface
= 0x0400,
Parameter
= 0x0800,
Delegate
= 0x1000,
All = Assembly | Module | Class
Method | Property | Field
Delegate,
ClassMembers = Class | Struct
Property | Field
}
| Struct | Enum | Constructor |
| Event | Interface | Parameter |
| Enum | Constructor | Method |
| Event | Delegate | Interface,
Atributos definidos por el usuario.
Los atributos definidos por el desarrollador son, físicamente, clases derivadas de
System.Attribute. Realmente no es obligatorio que deriven de System.Attribute
pero sí recomendable.
Utilización de AttributeUsageAttribute.
La utilización del atributo AttributeUsage es relativamente sencilla e implica los
siguientes pasos:
-
-
Crear la clase atributo. En esta fase se deberá crear la clase que luego servirá
para decorar otras clases y se deberán configurar las propiedades
AllowMultiple, Inherited y ValidOn de tal clase.
Aplicar la clase atributo creada a otras clases (decorar).
Obtener mediante reflexión (GetCustomAttributes...) los atributos de la clase
decorada -durante la ejecución- y utilizarlos.
AllowMultiple.
Es un parámetro con nombre. Indica si puede aplicarse más de una vez el mismo
atributo a un elemento.
Inherited.
Es un parámetro con nombre. Especifica si el atributo puede ser heredado y sobrescritos
sus métodos por clases derivadas.
10/14
Marco Besteiro y Miguel Rodríguez
Atributos
ValidOn.
Es un parámetro posicional y sólo puede ser pasado al constructor de AttributeUsage.
Especifica los elementos de un programa a los que puede aplicarse el atributo. En
realidad, el conjunto de los posibles elementos a los que puede afectar el atributo es
indicado por la enumeración AttributeTargets. Se pueden combinar varios valores de
AttributeTargets con |.
Los posibles valores de AttributeTargets son:
Nombre de elemento Descripción
All
El atributo puede ser aplicado a cualquier elemento.
Assembly
El atributo puede ser aplicado a un assembly.
Class
El atributo puede ser aplicado a una clase.
ClassMembers
El atributo puede ser aplicado a clases, structuras,
enumeraciones, constructores, métodos, propiedades,
campos, eventos, delegates e interfaces.
Constructor
El atributo puede ser aplicado a un constructor.
Delegate
El atributo puede ser aplicado a un delegate.
Enum
El atributo puede ser aplicado a una enumeration.
Event
El atributo puede ser aplicado a un evento.
Field
El atributo puede ser aplicado a un campo.
Interface
El atributo puede ser aplicado a un interface.
Method
El atributo puede ser aplicado a un método.
Module
El atributo puede ser aplicado a un module.
Parameter
El atributo puede ser aplicado a un parámetro.
Property
El atributo puede ser aplicado a una propiedad.
ReturnValue
El atributo puede ser aplicado a un valor de retorno.
Struct
El atributo puede ser aplicado a un tipo valor.
Tabla 12.2
Ejemplo: Creación y utilización de una clase atributo.
Paso 1: Creación de la clase atributo InfoAttribute: La clase InfoAttribute estará
contenida el la librería LibAtributos, en el fichero InfoAttribute.cs
using System;
namespace LibAtributos
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method
| AttributeTargets.Field,
AllowMultiple = true, Inherited = false)]
public class InfoAttribute : Attribute
{
private DateTime fecha;
public string informacion;
public InfoAttribute(string fecha)
{
11/14
Marco Besteiro y Miguel Rodríguez
Atributos
this.fecha = DateTime.Parse(fecha);
}
public string Informacion
{
get
{
return informacion;
}
set
{
informacion = value;
}
}
public DateTime Fecha
{
get
{
return fecha;
}
}
}
}
Paso 2: Aplicación de la clase atributo a otra u otras clases. En este caso se creará una
clase CSaludar dentro de una librería LibSaludar.dll. Se aplicará el atributo
InfoAttribute a la clase CSaludar. El fichero donde está la clase CSaludar es
CSaludar.cs.
using System;
using LibAtributos;
namespace LibSaludar
{
//El atributo Info corresponde a la clase InfoAttribute
//se está aplicando a la clase CSaludar
[Info("20/09/2001", Informacion = "Esta clase posee dos métodos,
el constructor y Saludar")]
public class CSaludar
{
private string nombre;
public CSaludar(string nombre)
{
this.nombre = nombre;
}
public void Saludar()
{
System.Console.WriteLine("Hola " + nombre);
}
}
}
Paso 3: Crear una aplicación que, mediante la reflexión, obtenga la información que el
atributo InfoAttribute asociada a la clase CSaludar. La aplicación será
PruebaAtributos.exe. La clase aplicación se llama PruebaAtributos y está en el
fichero PruebaAtributos.cs.
using System;
12/14
Marco Besteiro y Miguel Rodríguez
Atributos
using System.Reflection;
using LibAtributos;
using LibSaludar;
class PruebaAtributos
{
public static void Main()
{
Assembly as1 = Assembly.Load("LibSaludar");
//Obtener un array de referencias a todos los
//tipos del assembly “LibSaludar”.
//En este caso sólo hay uno: “CSaludar”
Type[] tipos = as1.GetTypes();
foreach(Type tipo in tipos)
{
//Obtener un array de referencias a todos los
//atributos de cada tipo del assembly “LibSaludar”,
//es decir, de “CSaludar”
//En este caso sólo hay un atributo: “InfoAttribute”
Attribute[]
atributos
=
Attribute.GetCustomAttributes(tipo);
foreach(Attribute atributo in atributos)
{
//Obtener la información de “InfoAttribute”
//y mostrarla
//”InfoAttribute” tiene dos campos:
//”Fecha” e “Información”.
InfoAttribute info = atributo as InfoAttribute;
System.Console.WriteLine("Clase:
"
+
tipo.Name);
System.Console.WriteLine("-----");
System.Console.WriteLine("Fecha:
"
+
info.Fecha);
System.Console.WriteLine("Información:
"
+
info.Informacion);
System.Console.WriteLine("-----");
System.Console.WriteLine(" ");
}
}
}
}
El resultado de ejecutar este ejemplo será:
13/14
Marco Besteiro y Miguel Rodríguez
Atributos
Figura 12.5
14/14
Descargar