Atributos en Visual Basic

El lenguaje Visual Basic permite al programador especificar modificadores en declaraciones, que representan información sobre las entidades que se declaran. Por ejemplo, al colocar un método de clase con los modificadores Public, , FriendProtected, Protected Friendo Private se especifica su accesibilidad.

Además de los modificadores definidos por el lenguaje, Visual Basic también permite a los programadores crear nuevos modificadores, denominados atributos, y usarlos al declarar nuevas entidades. Estos nuevos modificadores, que se definen a través de la declaración de clases de atributo, se asignan a las entidades a través de bloques de atributos.

Nota. Los atributos se pueden recuperar en tiempo de ejecución a través de las API de reflexión de .NET Framework. Estas API están fuera del ámbito de esta especificación.

Por ejemplo, un marco podría definir un Help atributo que se puede colocar en elementos de programa como clases y métodos para proporcionar una asignación de elementos de programa a documentación, como se muestra en el ejemplo siguiente:

<AttributeUsage(AttributeTargets.All)> _
Public Class HelpAttribute
    Inherits Attribute

    Public Sub New(urlValue As String)
        Me.UrlValue = urlValue
    End Sub

    Public Topic As String
    Private UrlValue As String

    Public ReadOnly Property Url() As String
        Get
            Return UrlValue
        End Get
    End Property
End Class

En el ejemplo se define una clase de atributo denominada HelpAttribute, o Help para short, que tiene un parámetro posicional (UrlValue) y un argumento con nombre (Topic).

En el ejemplo siguiente se muestran varios usos del atributo :

<Help("http://www.example.com/.../Class1.htm")> _
Public Class Class1
    <Help("http://www.example.com/.../Class1.htm", Topic:="F")> _
    Public Sub F()
    End Sub
End Class

En el ejemplo siguiente se comprueba si Class1 tiene un Help atributo y se escriben los valores y Url asociados Topic si el atributo está presente.

Module Test
    Sub Main()
        Dim type As Type = GetType(Class1)
        Dim arr() As Object = _
            type.GetCustomAttributes(GetType(HelpAttribute), True)

        If arr.Length = 0 Then
            Console.WriteLine("Class1 has no Help attribute.")
        Else
            Dim ha As HelpAttribute = CType(arr(0), HelpAttribute)
            Console.WriteLine("Url = " & ha.Url & ", Topic = " & ha.Topic)
        End If
    End Sub
End Module

Clases de atributos

Una clase de atributo es una clase no genérica que deriva de System.Attribute y no MustInherites . La clase de atributo puede tener un System.AttributeUsage atributo que declara en qué es válido el atributo, si se puede usar varias veces en una declaración y si se hereda. En el ejemplo siguiente se define una clase de atributo denominada SimpleAttribute que se puede colocar en declaraciones de clase y declaraciones de interfaz:

<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Interface)> _
Public Class SimpleAttribute
    Inherits System.Attribute
End Class

En el ejemplo siguiente se muestran algunos usos del Simple atributo . Aunque la clase de atributo se denomina SimpleAttribute, los usos de este atributo pueden omitir el Attribute sufijo, lo que reduce el nombre a Simple:

<Simple> Class Class1
End Class

<Simple> Interface Interface1
End Interface

Si el atributo carece de , System.AttributeUsageel atributo se puede colocar en cualquier destino (equivalente a AttributeTargets.All). El System.AttributeUsage atributo tiene un inicializador de variable, , AllowMultipleque especifica si el atributo indicado se puede especificar más de una vez para una declaración determinada. Si AllowMultiple para un atributo es True, es una clase de atributo de varios usos y se puede especificar más de una vez en una declaración. Si AllowMultiple para un atributo es o no se False especifica para un atributo, es una clase de atributo de uso único y se puede especificar como máximo una vez en una declaración.

En el ejemplo siguiente se define una clase de atributo de varios usos denominada AuthorAttribute:

<AttributeUsage(AttributeTargets.Class, AllowMultiple:=True)> _
Public Class AuthorAttribute
    Inherits System.Attribute

    Private _Value As String

    Public Sub New(value As String)
        Me._Value = value
    End Sub

    Public ReadOnly Property Value() As String
        Get
            Return _Value
        End Get
    End Property
End Class

En el ejemplo se muestra una declaración de clase con dos usos del Author atributo :

<Author("Maria Hammond"), Author("Ramesh Meyyappan")> _
Class Class1
End Class

El System.AttributeUsage atributo tiene una variable de instancia pública, Inherited, que especifica si el atributo, cuando se especifica en un tipo base, también se hereda por tipos que derivan de este tipo base. Si la Inherited variable de instancia pública no se inicializa, se usa un valor predeterminado de False . Las propiedades y los eventos no heredan atributos, aunque los métodos definidos por propiedades y eventos sí lo hacen. Las interfaces no heredan atributos.

Si un atributo de uso único se hereda y se especifica en un tipo derivado, el atributo especificado en el tipo derivado invalida el atributo heredado. Si un atributo de varios usos se hereda y se especifica en un tipo derivado, ambos atributos se especifican en el tipo derivado. Por ejemplo:

<AttributeUsage(AttributeTargets.Class, AllowMultiple:=True, _
                Inherited:=True) > _
Class MultiUseAttribute
    Inherits System.Attribute

    Public Sub New(value As Boolean)
    End Sub
End Class

<AttributeUsage(AttributeTargets.Class, Inherited:=True)> _
Class SingleUseAttribute
    Inherits Attribute

    Public Sub New(value As Boolean)
    End Sub
End Class

<SingleUse(True), MultiUse(True)> Class Base
End Class

' Derived has three attributes defined on it: SingleUse(False),
' MultiUse(True) and MultiUse(False)
<SingleUse(False), MultiUse(False)> _
Class Derived
    Inherits Base
End Class

Los parámetros posicionales del atributo se definen mediante los parámetros de los constructores públicos de la clase de atributo. Los parámetros posicionales deben ser ByVal y no especificar ByRef. Las variables y propiedades de instancia pública se definen mediante propiedades de lectura y escritura públicas o variables de instancia de la clase de atributo. Los tipos que se pueden usar en parámetros posicionales y variables y propiedades de instancia pública están restringidos a los tipos de atributo. Un tipo es un tipo de atributo si es uno de los siguientes:

  • Cualquier tipo primitivo excepto para Date y Decimal.

  • El tipo Object.

  • El tipo System.Type.

  • Tipo enumerado, siempre que y los tipos en los que esté anidado (si existe) tienen Public accesibilidad.

  • Matriz unidimensional de uno de los tipos anteriores de esta lista.

Bloques de atributos

Los atributos se especifican en bloques de atributos. Cada bloque de atributo se delimita por corchetes angulares ("<>") y se pueden especificar varios atributos en una lista separada por comas dentro de un bloque de atributos o en varios bloques de atributos. El orden en el que se especifican los atributos no es significativo. Por ejemplo, los bloques <A, B>de atributos , <A> <B><B, A>y <B> <A> son todos equivalentes.

Attributes
    : AttributeBlock+
    ;

AttributeBlock
    : LineTerminator? '<' AttributeList LineTerminator? '>' LineTerminator?
    ;

AttributeList
    : Attribute ( Comma Attribute )*
    ;

Attribute
    : ( AttributeModifier ':' )? SimpleTypeName
    ( OpenParenthesis AttributeArguments? CloseParenthesis )?
    ;

AttributeModifier
    : 'Assembly' | 'Module'
    ;

Es posible que no se especifique un atributo en un tipo de declaración que no admita y es posible que no se especifiquen atributos de uso único más de una vez en un bloque de atributos. En el ejemplo siguiente se producen errores porque intenta usar HelpString en la interfaz Interface1 y más de una vez en la declaración de Class1.

<AttributeUsage(AttributeTargets.Class)> _
Public Class HelpStringAttribute
    Inherits System.Attribute

    Private InternalValue As String

    Public Sub New(value As String)
        Me.InternalValue = value
    End Sub

    Public ReadOnly Property Value() As String
        Get
            Return InternalValue
        End Get
    End Property
End Class

' Error: HelpString only applies to classes.
<HelpString("Description of Interface1")> _
Interface Interface1
    Sub Sub1()
End Interface

' Error: HelpString is single-use.
<HelpString("Description of Class1"), _
    HelpString("Another description of Class1")> _
Public Class Class1
End Class

Un atributo consta de un modificador de atributo opcional, un nombre de atributo, una lista opcional de argumentos posicionales y inicializadores de variable/propiedad. Si no hay parámetros ni inicializadores, se pueden omitir los paréntesis. Si un atributo tiene un modificador, debe estar en un bloque de atributos en la parte superior de un archivo de origen.

Si un archivo de origen contiene un bloque de atributos en la parte superior del archivo que especifica atributos para el ensamblado o módulo que contendrá el archivo de origen, cada atributo del bloque de atributos debe tener como prefijo tanto el Assembly modificador o Module como dos puntos.

Nombres de atributo

El nombre de un atributo especifica una clase de atributo. Por convención, las clases de atributo se denominan con el sufijo Attribute. En el uso de un atributo se puede incluir u omitir este sufijo. Por consiguiente, el nombre de una clase de atributo que corresponde a un identificador de atributo es el propio identificador o la concatenación del identificador calificado y Attribute. Cuando el compilador resuelve un nombre de atributo, se anexa Attribute al nombre e intenta la búsqueda. Si se produce un error en esa búsqueda, el compilador intenta la búsqueda sin el sufijo. Por ejemplo, los usos de una clase SimpleAttribute de atributo pueden omitir el Attribute sufijo, lo que reduce el nombre a Simple:

<Simple> Class Class1
End Class

<Simple> Interface Interface1
End Interface

El ejemplo anterior es semánticamente equivalente a lo siguiente:

<SimpleAttribute> Class Class1
End Class

<SimpleAttribute> Interface Interface1
End Interface

En general, se prefieren los atributos denominados con el sufijo Attribute . En el ejemplo siguiente se muestran dos clases de atributo denominadas T y T``Attribute.

<AttributeUsage(AttributeTargets.All)> _
Public Class T
    Inherits System.Attribute
End Class

<AttributeUsage(AttributeTargets.All)> _
Public Class TAttribute
    Inherits System.Attribute
End Class

' Refers to TAttribute.
<T> Class Class1 
End Class

' Refers to TAttribute.
<TAttribute> Class Class2 
End Class

Tanto el bloque <T> de atributos como el bloque <TAttribute> de atributos hacen referencia a la clase de atributo denominada TAttribute. No es posible usar T como atributo hasta que quite la declaración de la clase TAttribute.

Argumentos de atributo

Los argumentos de un atributo pueden tener dos formas: argumentos posicionales e inicializadores de variable o propiedad de instancia. Los argumentos posicionales al atributo deben preceder a los inicializadores de propiedad o variable de instancia. Un argumento posicional consta de una expresión constante, una expresión unidimensional de creación de matrices o una GetType expresión. Un inicializador de propiedad o variable de instancia consta de un identificador, que puede coincidir con palabras clave, seguidos de dos puntos y un signo igual, y terminados por una expresión constante o una GetType expresión.

Dado un atributo con la clase Tde atributo , la lista de argumentos posicionales y la lista PNde inicializadores de variables o propiedades de instancia, estos pasos determinan si los argumentos son válidos:

  1. Siga los pasos de procesamiento en tiempo de compilación para compilar una expresión del formulario New T(P). Esto da como resultado un error en tiempo de compilación o determina un constructor en T que es más aplicable a la lista de argumentos.

  2. Si el constructor determinado en el paso 1 tiene parámetros que no son tipos de atributo o no es accesible en el sitio de declaración, se produce un error en tiempo de compilación.

  3. Para cada inicializador Arg de propiedad o variable de instancia en N, vamos Name a ser el identificador del inicializador Argde propiedad o variable de instancia . Namedebe identificar una propiedad sin parámetros o Public una variable de instancia que noShared sea grabable o sin parámetros en T cuyo tipo sea un tipo de atributo. Si T no tiene ninguna variable o propiedad de instancia, se produce un error en tiempo de compilación.

Por ejemplo:

<AttributeUsage(AttributeTargets.All)> _
Public Class GeneralAttribute
    Inherits Attribute

    Public Sub New(x As Integer)
    End Sub

    Public Sub New(x As Double)
    End Sub

    Public y As Type

    Public Property z As Integer
        Get
        End Get

        Set
        End Set
    End Property
End Class

' Calls the first constructor.
<General(10, z:=30, y:=GetType(Integer))> _
Class C1
End Class

' Calls the second constructor.
<General(10.5, z:=10)> _
Class C2
End Class

Los parámetros de tipo no se pueden usar en ningún lugar de los argumentos de atributo. Sin embargo, se pueden usar tipos construidos:

<AttributeUsage(AttributeTargets.All)> _
Class A 
   Inherits System.Attribute 

   Public Sub New(t As Type)
   End Sub 
End Class

Class List(Of T) 
    ' Error: attribute argument cannot use type parameter
    <A(GetType(T))> Dim t1 As T 

    ' OK: closed type
    <A(GetType(List(Of Integer)))> Dim y As Integer
End Class
AttributeArguments
    : AttributePositionalArgumentList
    | AttributePositionalArgumentList Comma VariablePropertyInitializerList
    | VariablePropertyInitializerList
    ;

AttributePositionalArgumentList
    : AttributeArgumentExpression? ( Comma AttributeArgumentExpression? )*
    ;

VariablePropertyInitializerList
    : VariablePropertyInitializer ( Comma VariablePropertyInitializer )*
    ;

VariablePropertyInitializer
    : IdentifierOrKeyword ColonEquals AttributeArgumentExpression
    ;

AttributeArgumentExpression
    : ConstantExpression
    | GetTypeExpression
    | ArrayExpression
    ;