Escrever atributos personalizados

Para criar atributos personalizados, não é preciso aprender muitos conceitos novos. Se estiver familiarizado com a programação orientada a objeto e souber como criar classes, você já domina a maior parte do conhecimento necessário. Os atributos personalizados são classes tradicionais que derivam direta ou indiretamente da classe System.Attribute. Como acontece nas classes tradicionais, os atributos personalizados contêm métodos que armazenam e recuperam dados.

As principais etapas para criar corretamente classes de atributos personalizados são as seguintes:

Esta seção descreve cada uma dessas etapas e oferece um exemplo de atributo personalizado no fim.

Aplicar o AttributeUsageAttribute

A declaração de um atributo personalizado começa com o atributo System.AttributeUsageAttribute, que define algumas das principais características da classe de atributos. Por exemplo, é possível especificar se o atributo pode ser herdado por outras classes ou a quais elementos o atributo pode ser aplicado. O fragmento de código a seguir demonstra como usar o 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

O AttributeUsageAttribute possui três membros que são importantes para a criação de atributos personalizados: AttributeTargets, Inherited e AllowMultiple.

Membro de AttributeTargets

No exemplo anterior, AttributeTargets.All é especificado, indicando que esse atributo pode ser aplicado a todos os elementos do programa. Como alternativa, você pode especificar AttributeTargets.Class, que indica que o atributo pode ser aplicado somente a uma classe, ou AttributeTargets.Method, que indica que o atributo pode ser aplicado a um único método. Todos os elementos do programa podem ser marcados para serem descritos dessa maneira por um atributo personalizado.

Você também pode passar vários valores de AttributeTargets. O fragmento de código a seguir especifica que um atributo personalizado pode ser aplicado a qualquer classe ou 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

Propriedade Inherited

A propriedade AttributeUsageAttribute.Inherited indica se o atributo pode ser herdado pelas classes derivadas das classes às quais o atributo é aplicado. Essa propriedade aceita um sinalizador true (o padrão) ou false. No exemplo a seguir, MyAttribute tem um valor padrão Inherited de true, enquanto YourAttribute tem um 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

Em seguida, os dois atributos são aplicados a um método na classe 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 fim, a classe YourClass é herdada da classe base MyClass. O método MyMethod mostra MyAttribute, mas não mostra 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

Propriedade AllowMultiple

A propriedade AttributeUsageAttribute.AllowMultiple indica se várias instâncias do atributo podem existir em um elemento. Se definido como true, várias instâncias serão permitidas. Se definido como false (o padrão), apenas uma instância será permitida.

No exemplo a seguir, MyAttribute tem um valor padrão AllowMultiple de false, enquanto YourAttribute tem um 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

Quando várias instâncias desses atributos são aplicadas, MyAttribute produz um erro do compilador. O exemplo de código a seguir mostra o uso válido de YourAttribute e o uso invá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

Se as propriedades AllowMultiple e Inherited estiverem definidas como true, uma classe herdada de outra classe poderá herdar um atributo e ter outra instância do mesmo atributo aplicada à mesma classe filho. Se AllowMultiple estiver definida como false, os valores de todos os atributos na classe pai serão substituídos pelas novas instâncias do mesmo atributo na classe filho.

Declarar a classe de atributos

Depois de aplicar o AttributeUsageAttribute, comece a definir as especificações do seu atributo. A declaração de uma classe de atributos é semelhante à declaração de uma classe tradicional, como demonstrado pelo código a seguir:

[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

Esta definição de atributo demonstra os seguintes pontos:

  • As classes de atributos devem ser declaradas como classes públicas.

  • Por convenção, o nome da classe de atributos termina com a palavra Attribute. Embora não seja necessária, essa convenção é recomendada para facilitar a leitura. Quando o atributo é aplicado, a inclusão da palavra Attribute torna-se opcional.

  • Todas as classes de atributos devem ser herdadas diretamente ou indiretamente da classe System.Attribute.

  • No Microsoft Visual Basic, todas as classes de atributos personalizados devem ter o atributo System.AttributeUsageAttribute.

Declarar constructos

Assim como as classes tradicionais, os atributos são inicializados com construtores. O fragmento de código a seguir ilustra um constructo de atributo típico. Esse construtor público aceita um parâmetro e define uma variável de membro igual ao seu 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

Você pode sobrecarregar o constructo para acomodar diferentes combinações de valores. Se também definir uma propriedade para sua classe de atributos personalizados, você poderá usar uma combinação de parâmetros nomeados e posicionais ao inicializar o atributo. Normalmente, você define todos os parâmetros necessários como posicionais e todos os parâmetros opcionais como nomeados. Nesse caso, o atributo não pode ser inicializado sem o parâmetro necessário. Todos os outros parâmetros são opcionais.

Observação

No Visual Basic, os construtores de uma classe de atributos não devem usar um argumento ParamArray.

O exemplo de código a seguir mostra como um atributo que usa o constructo anterior pode ser aplicado usando parâmetros necessários e opcionais. Ele pressupõe que o atributo tem um valor booliano necessário e uma cadeia de caracteres 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 propriedades

Se quiser definir um parâmetro nomeado ou disponibilizar uma maneira fácil de retornar os valores armazenados pelo seu atributo, declare uma propriedade. As propriedades de atributos devem ser declaradas como entidades públicas com uma descrição do tipo de dados que será retornado. Defina a variável que conterá o valor de sua propriedade e irá associá-la aos métodos get e set. O exemplo de código a seguir demonstra como implementar uma propriedade no seu 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

Exemplo de Atributo personalizado

Esta seção incorpora as informações anteriores e mostra como criar um atributo que documenta informações sobre o autor de uma seção de código. O atributo neste exemplo armazena o nome e o nível do programador, e se o código foi revisado. Ele usa três variáveis privadas para armazenar os valores reais que serão salvos. Cada variável é representada por uma propriedade pública que obtém e define os valores. Por fim, o constructo é definido com dois parâmetros necessários:

[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

Você pode aplicar esse atributo usando o nome completo, DeveloperAttribute, ou usando o nome abreviado, Developer, em uma das seguintes maneiras:

[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)>

O primeiro exemplo mostra o atributo aplicado apenas com os parâmetros nomeados necessários. O segundo exemplo mostra o atributo aplicado com os parâmetros obrigatórios e opcionais.

Confira também