Escritura de atributos personalizados

Para diseñar atributos personalizados, no es necesario aprender muchos conceptos nuevos. Si conoce bien la programación orientada a objetos y sabe cómo diseñar clases, ya tiene la mayoría de los conocimientos necesarios. Los atributos personalizados son clases tradicionales que se derivan directa o indirectamente de la clase System.Attribute. Como sucede con las clases tradicionales, los atributos personalizados contienen métodos que almacenan y recuperan datos.

Los pasos principales para diseñar correctamente clases de atributos personalizados son los siguientes:

En esta sección se describe cada uno de estos pasos y, para finalizar, se muestra un ejemplo de atributo personalizado.

Aplicar AttributeUsageAttribute

La declaración de un atributo personalizado empieza con el atributo System.AttributeUsageAttribute, que define algunas de las características clave de la clase de atributo. Por ejemplo, puede especificar si el atributo lo pueden heredar otras clases o los elementos a los que se puede aplicar el atributo. En el fragmento de código siguiente se muestra cómo usar AttributeUsageAttribute:

[AttributeUsage(AttributeTargets::All, Inherited = false, AllowMultiple = true)]
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
<AttributeUsage(AttributeTargets.All, Inherited:=False, AllowMultiple:=True)>
Public Class SomeClass
    Inherits Attribute
    '...
End Class

AttributeUsageAttribute tiene tres miembros que son importantes para la creación de atributos personalizados: AttributeTargets, Inherited y AllowMultiple.

Miembro AttributeTargets

En el ejemplo anterior, se especifica AttributeTargets.All, lo que indica que este atributo se puede aplicar a todos los elementos de programa. Como alternativa, puede especificar AttributeTargets.Class, que indica que el atributo solo se puede aplicar a una clase, o AttributeTargets.Method, que indica que el atributo solo se puede aplicar a un método. De esta manera, un atributo personalizado puede marcar para descripción todos los elementos del programa.

También puede pasar varios valores AttributeTargets. El fragmento de código siguiente especifica que un atributo personalizado se puede aplicar a cualquier clase o método:

[AttributeUsage(AttributeTargets::Class | AttributeTargets::Method)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method)>
Public Class SomeOtherClass
    Inherits Attribute
    '...
End Class

Propiedad Inherited

La propiedad AttributeUsageAttribute.Inherited indica si el atributo lo pueden heredar clases derivadas de las clases a las que se aplica el atributo. Esta propiedad acepta una marca true (valor predeterminado) o false. En el ejemplo siguiente, MyAttribute tiene un valor Inherited predeterminado de true, mientras que YourAttribute tiene un valor Inherited de false:

// This defaults to Inherited = true.
public ref class MyAttribute : Attribute
{
    //...
};

[AttributeUsage(AttributeTargets::Method, Inherited = false)]
public ref class YourAttribute : Attribute
{
    //...
};
// This defaults to Inherited = true.
public class MyAttribute : Attribute
{
    //...
}

[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class YourAttribute : Attribute
{
    //...
}
' This defaults to Inherited = true.
Public Class MyAttribute
    Inherits Attribute
    '...
End Class

<AttributeUsage(AttributeTargets.Method, Inherited:=False)>
Public Class YourAttribute
    Inherits Attribute
    '...
End Class

Los dos atributos se aplican entonces a un método de la clase base MyClass:

public ref class MyClass
{
public:
    [MyAttribute]
    [YourAttribute]
    virtual void MyMethod()
    {
        //...
    }
};
public class MyClass
{
    [MyAttribute]
    [YourAttribute]
    public virtual void MyMethod()
    {
        //...
    }
}
Public Class MeClass
    <MyAttribute>
    <YourAttribute>
    Public Overridable Sub MyMethod()
        '...
    End Sub
End Class

Por último, la clase YourClass se hereda de la clase base MyClass. El método MyMethod muestra MyAttribute, pero no YourAttribute:

public ref class YourClass : MyClass
{
public:
    // MyMethod will have MyAttribute but not YourAttribute.
    virtual void MyMethod() override
    {
        //...
    }

};
public class YourClass : MyClass
{
    // MyMethod will have MyAttribute but not YourAttribute.
    public override void MyMethod()
    {
        //...
    }
}
Public Class YourClass
    Inherits MeClass
    ' MyMethod will have MyAttribute but not YourAttribute.
    Public Overrides Sub MyMethod()
        '...
    End Sub

End Class

Propiedad AllowMultiple

La propiedad AttributeUsageAttribute.AllowMultiple indica si pueden existir varias instancias del atributo en un elemento. Si se establece en true, se permiten varias instancias. Si se establece en false (valor predeterminado), solo se permite una instancia.

En el ejemplo siguiente, MyAttribute tiene un valor AllowMultiple predeterminado de false, mientras que YourAttribute tiene un valor de true:

//This defaults to AllowMultiple = false.
public ref class MyAttribute : Attribute
{
};

[AttributeUsage(AttributeTargets::Method, AllowMultiple = true)]
public ref class YourAttribute : Attribute
{
};
//This defaults to AllowMultiple = false.
public class MyAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class YourAttribute : Attribute
{
}
' This defaults to AllowMultiple = false.
Public Class MyAttribute
    Inherits Attribute
End Class

<AttributeUsage(AttributeTargets.Method, AllowMultiple:=true)>
Public Class YourAttribute
    Inherits Attribute
End Class

Si se aplican varias instancias de estos atributos, MyAttribute produce un error del compilador. En el ejemplo de código siguiente se muestra el uso válido de YourAttribute y el uso no válido de MyAttribute:

public ref class MyClass
{
public:
    // This produces an error.
    // Duplicates are not allowed.
    [MyAttribute]
    [MyAttribute]
    void MyMethod()
    {
        //...
    }

    // This is valid.
    [YourAttribute]
    [YourAttribute]
    void YourMethod()
    {
        //...
    }
};
public class MyClass
{
    // This produces an error.
    // Duplicates are not allowed.
    [MyAttribute]
    [MyAttribute]
    public void MyMethod()
    {
        //...
    }

    // This is valid.
    [YourAttribute]
    [YourAttribute]
    public void YourMethod()
    {
        //...
    }
}
Public Class MyClass
    ' This produces an error.
    ' Duplicates are not allowed.
    <MyAttribute>
    <MyAttribute>
    Public Sub MyMethod()
        '...
    End Sub

    ' This is valid.
    <YourAttribute>
    <YourAttribute>
    Public Sub YourMethod()
        '...
    End Sub
End Class

Si tanto la propiedad AllowMultiple como la propiedad Inherited se establecen en true, una clase que se hereda de otra clase puede heredar un atributo y tener otra instancia del mismo atributo aplicada en la misma clase secundaria. Si se establece AllowMultiple en false, las instancias nuevas del mismo atributo de la clase secundaria sobrescribirán los valores de los atributos de la clase primaria.

Declarar la clase de atributo

Después de aplicar AttributeUsageAttribute, empiece a definir los detalles del atributo. La declaración de una clase de atributo es similar a la declaración de una clase tradicional, tal como se muestra en el código siguiente:

[AttributeUsage(AttributeTargets::Method)]
public ref class MyAttribute : Attribute
{
    // . . .
};
[AttributeUsage(AttributeTargets.Method)]
public class MyAttribute : Attribute
{
    // . . .
}
<AttributeUsage(AttributeTargets.Method)>
Public Class MyAttribute
    Inherits Attribute
    ' . . .
End Class

En esta definición de atributo se muestra lo siguiente:

  • Las clases de atributos se deben declarar como clases públicas.

  • Por convención, el nombre de la clase de atributo termina con la palabra Attribute. Aunque no es obligatoria, se recomienda seguir esta convención para mejorar la legibilidad. Cuando se aplica el atributo, la inclusión de la palabra Attribute es opcional.

  • Todas las clases de atributo deben heredar directa o indirectamente de la clase System.Attribute.

  • En Microsoft Visual Basic, todas las clases de atributos personalizados deben tener el atributo System.AttributeUsageAttribute.

Declarar constructores

Al igual que las clases tradicionales, los atributos se inicializan con constructores. En el siguiente fragmento de código se muestra un constructor de atributos típico. Este constructor público acepta un parámetro y establece una variable de miembro igual a su valor.

MyAttribute(bool myvalue)
{
    this->myvalue = myvalue;
}
public MyAttribute(bool myvalue)
{
    this.myvalue = myvalue;
}
Public Sub New(myvalue As Boolean)
    Me.myvalue = myvalue
End Sub

Puede sobrecargar el constructor para adaptarse a distintas combinaciones de valores. Si también define una propiedad para la clase de atributo personalizado, puede utilizar una combinación de parámetros con nombre y de posición al inicializar el atributo. Normalmente, se definen todos los parámetros necesarios como de posición y todos parámetros opcionales como con nombre. En este caso, el atributo no se puede inicializar sin el parámetro obligatorio. Todos los demás parámetros son opcionales.

Nota

En Visual Basic, los constructores de una clase de atributo no deben usar un argumento ParamArray.

En el ejemplo de código siguiente, se muestra cómo se puede aplicar un atributo que utiliza el constructor anterior mediante parámetros obligatorios y opcionales. Se supone que el atributo tiene un valor booleano obligatorio y una propiedad de cadena opcional.

// One required (positional) and one optional (named) parameter are applied.
[MyAttribute(false, OptionalParameter = "optional data")]
public ref class SomeClass
{
    //...
};
// One required (positional) parameter is applied.
[MyAttribute(false)]
public ref class SomeOtherClass
{
    //...
};
// One required (positional) and one optional (named) parameter are applied.
[MyAttribute(false, OptionalParameter = "optional data")]
public class SomeClass
{
    //...
}
// One required (positional) parameter is applied.
[MyAttribute(false)]
public class SomeOtherClass
{
    //...
}
' One required (positional) and one optional (named) parameter are applied.
<MyAttribute(false, OptionalParameter:="optional data")>
Public Class SomeClass
    '...
End Class

' One required (positional) parameter is applied.
<MyAttribute(false)>
Public Class SomeOtherClass
    '...
End Class

Declarar propiedades

Si quiere definir un parámetro con nombre o proporcionar un método sencillo para devolver los valores almacenados por el atributo, declare una propiedad. Las propiedades de atributo deben declararse como entidades públicas con una descripción del tipo de datos que se devolverá. Defina la variable que contendrá el valor de la propiedad y asóciela con los métodos get y set . En el ejemplo de código siguiente se muestra cómo implementar una propiedad en el atributo:

property bool MyProperty
{
    bool get() {return this->myvalue;}
    void set(bool value) {this->myvalue = value;}
}
public bool MyProperty
{
    get {return this.myvalue;}
    set {this.myvalue = value;}
}
Public Property MyProperty As Boolean
    Get
        Return Me.myvalue
    End Get
    Set
        Me.myvalue = Value
    End Set
End Property

ejemplo de atributo personalizado

En esta sección se incorpora la información anterior y se muestra cómo se diseña un atributo que documente información sobre el autor de una sección del código. El atributo de este ejemplo almacena el nombre y el nivel del programador y si se ha revisado el código. Utiliza tres variables privadas para almacenar los valores reales que se deben guardar. Cada variable se representa mediante una propiedad pública que obtiene y establece los valores. Por último, el constructor se define con dos parámetros obligatorios:

[AttributeUsage(AttributeTargets::All)]
public ref class DeveloperAttribute : Attribute
{
    // Private fields.
private:
    String^ name;
    String^ level;
    bool reviewed;

public:
    // This constructor defines two required parameters: name and level.

    DeveloperAttribute(String^ name, String^ level)
    {
        this->name = name;
        this->level = level;
        this->reviewed = false;
    }

    // Define Name property.
    // This is a read-only attribute.

    virtual property String^ Name
    {
        String^ get() {return name;}
    }

    // Define Level property.
    // This is a read-only attribute.

    virtual property String^ Level
    {
        String^ get() {return level;}
    }

    // Define Reviewed property.
    // This is a read/write attribute.

    virtual property bool Reviewed
    {
        bool get() {return reviewed;}
        void set(bool value) {reviewed = value;}
    }
};
[AttributeUsage(AttributeTargets.All)]
public class DeveloperAttribute : Attribute
{
    // Private fields.
    private string name;
    private string level;
    private bool reviewed;

    // This constructor defines two required parameters: name and level.

    public DeveloperAttribute(string name, string level)
    {
        this.name = name;
        this.level = level;
        this.reviewed = false;
    }

    // Define Name property.
    // This is a read-only attribute.

    public virtual string Name
    {
        get {return name;}
    }

    // Define Level property.
    // This is a read-only attribute.

    public virtual string Level
    {
        get {return level;}
    }

    // Define Reviewed property.
    // This is a read/write attribute.

    public virtual bool Reviewed
    {
        get {return reviewed;}
        set {reviewed = value;}
    }
}
<AttributeUsage(AttributeTargets.All)>
Public Class DeveloperAttribute
    Inherits Attribute
    ' Private fields.
    Private myname As String
    Private mylevel As String
    Private myreviewed As Boolean

    ' This constructor defines two required parameters: name and level.

    Public Sub New(name As String, level As String)
        Me.myname = name
        Me.mylevel = level
        Me.myreviewed = False
    End Sub

    ' Define Name property.
    ' This is a read-only attribute.

    Public Overridable ReadOnly Property Name() As String
        Get
            Return myname
        End Get
    End Property

    ' Define Level property.
    ' This is a read-only attribute.

    Public Overridable ReadOnly Property Level() As String
        Get
            Return mylevel
        End Get
    End Property

    ' Define Reviewed property.
    ' This is a read/write attribute.

    Public Overridable Property Reviewed() As Boolean
        Get
            Return myreviewed
        End Get
        Set
            myreviewed = value
        End Set
    End Property
End Class

Puede aplicar este atributo con el nombre completo, DeveloperAttribute, o el nombre abreviado, Developer, de una de las maneras siguientes:

[Developer("Joan Smith", "1")]

-or-

[Developer("Joan Smith", "1", Reviewed = true)]
[Developer("Joan Smith", "1")]

-or-

[Developer("Joan Smith", "1", Reviewed = true)]
<Developer("Joan Smith", "1")>

-or-

<Developer("Joan Smith", "1", Reviewed := true)>

En el primer ejemplo se muestra el atributo aplicado solo con los parámetros con nombre obligatorios. En el segundo ejemplo se muestra el atributo aplicado con los parámetros obligatorios y opcionales.

Vea también