Miembros de tipo

Los miembros de tipo definen ubicaciones de almacenamiento y código ejecutable. Pueden ser métodos, constructores, eventos, constantes, variables y propiedades.

Implementación del método de interfaz

Los métodos, eventos y propiedades pueden implementar miembros de interfaz. Para implementar un miembro de interfaz, una declaración de miembro especifica la Implements palabra clave y enumera uno o varios miembros de interfaz.

ImplementsClause
    : ( 'Implements' ImplementsList )?
    ;

ImplementsList
    : InterfaceMemberSpecifier ( Comma InterfaceMemberSpecifier )*
    ;

InterfaceMemberSpecifier
    : NonArrayTypeName Period IdentifierOrKeyword
    ;

Los métodos y propiedades que implementan miembros de interfaz se declaran MustOverrideimplícitamente NotOverridable como , Overridableo reemplazan a otro miembro. Se trata de un error para que un miembro que implemente un miembro de interfaz sea Shared. La accesibilidad de un miembro no tiene ningún efecto en su capacidad de implementar miembros de interfaz.

Para que una implementación de interfaz sea válida, la lista de implementaciones del tipo contenedor debe asignar un nombre a una interfaz que contenga un miembro compatible. Un miembro compatible es uno cuya firma coincide con la firma del miembro de implementación. Si se implementa una interfaz genérica, el argumento de tipo proporcionado en la cláusula Implements se sustituye por la firma al comprobar la compatibilidad. Por ejemplo:

Interface I1(Of T)
    Sub F(x As T)
End Interface

Class C1
    Implements I1(Of Integer)

    Sub F(x As Integer) Implements I1(Of Integer).F
    End Sub
End Class

Class C2(Of U)
    Implements I1(Of U)

    Sub F(x As U) Implements I1(Of U).F
    End Sub
End Class

Si un evento declarado mediante un tipo delegado implementa un evento de interfaz, un evento compatible es uno cuyo tipo de delegado subyacente es el mismo tipo. De lo contrario, el evento usa el tipo delegado del evento de interfaz que está implementando. Si este evento implementa varios eventos de interfaz, todos los eventos de interfaz deben tener el mismo tipo de delegado subyacente. Por ejemplo:

Interface ClickEvents
    Event LeftClick(x As Integer, y As Integer)
    Event RightClick(x As Integer, y As Integer)
End Interface

Class Button
    Implements ClickEvents

    ' OK. Signatures match, delegate type = ClickEvents.LeftClickHandler.
    Event LeftClick(x As Integer, y As Integer) _
        Implements ClickEvents.LeftClick

    ' OK. Signatures match, delegate type = ClickEvents.RightClickHandler.
    Event RightClick(x As Integer, y As Integer) _
        Implements ClickEvents.RightClick
End Class

Class Label
    Implements ClickEvents

    ' Error. Signatures match, but can't be both delegate types.
    Event Click(x As Integer, y As Integer) _
        Implements ClickEvents.LeftClick, ClickEvents.RightClick
End Class

Se especifica un miembro de interfaz en la lista de implementaciones mediante un nombre de tipo, un punto y un identificador. El nombre de tipo debe ser una interfaz en la lista de implementaciones o una interfaz base de una interfaz de la lista de implementaciones y el identificador debe ser miembro de la interfaz especificada. Un único miembro puede implementar más de un miembro de interfaz coincidente.

Interface ILeft
    Sub F()
End Interface

Interface IRight
    Sub F()
End Interface

Class Test
    Implements ILeft, IRight

    Sub F() Implements ILeft.F, IRight.F
    End Sub
End Class

Si el miembro de interfaz que se implementa no está disponible en todas las interfaces implementadas explícitamente debido a la herencia de varias interfaces, el miembro de implementación debe hacer referencia explícitamente a una interfaz base en la que el miembro está disponible. Por ejemplo, si I1 y I2 contienen un miembro M, y hereda de I1 y I2I3 , un tipo que implementa I3 implementará I1.M y I2.M. Si las sombras de una interfaz multiplican los miembros heredados, un tipo de implementación tendrá que implementar los miembros heredados y los miembros sombreados.

Interface ILeft
    Sub F()
End Interface

Interface IRight
    Sub F()
End Interface

Interface ILeftRight
    Inherits ILeft, IRight

    Shadows Sub F()
End Interface

Class Test
    Implements ILeftRight

    Sub LeftF() Implements ILeft.F
    End Sub

    Sub RightF() Implements IRight.F
    End Sub

    Sub LeftRightF() Implements ILeftRight.F
    End Sub
End Class

Si la interfaz contenedora del miembro de interfaz que se implementa es genérica, se deben proporcionar los mismos argumentos de tipo que la interfaz que se implementa. Por ejemplo:

Interface I1(Of T)
    Function F() As T
End Interface

Class C1
    Implements I1(Of Integer)
    Implements I1(Of Double)

    Function F1() As Integer Implements I1(Of Integer).F
    End Function

    Function F2() As Double Implements I1(Of Double).F
    End Function

    ' Error: I1(Of String) is not implemented by C1
    Function F3() As String Implements I1(Of String).F
    End Function
End Class

Class C2(Of U)
    Implements I1(Of U)

    Function F() As U Implements I1(Of U).F
    End Function
End Class

Métodos

Los métodos contienen las instrucciones ejecutables de un programa.

MethodMemberDeclaration
    : MethodDeclaration
    | ExternalMethodDeclaration
    ;

InterfaceMethodMemberDeclaration
    : InterfaceMethodDeclaration
    ;

MethodDeclaration
    : SubDeclaration
    | MustOverrideSubDeclaration
    | FunctionDeclaration
    | MustOverrideFunctionDeclaration
    ;

InterfaceMethodDeclaration
    : InterfaceSubDeclaration
    | InterfaceFunctionDeclaration
    ;

SubSignature
    : 'Sub' Identifier TypeParameterList?
      ( OpenParenthesis ParameterList? CloseParenthesis )?
    ;

FunctionSignature
    : 'Function' Identifier TypeParameterList?
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )?
    ;

SubDeclaration
    : Attributes? ProcedureModifier* SubSignature
      HandlesOrImplements? LineTerminator
      Block
      'End' 'Sub' StatementTerminator
    ;

MustOverrideSubDeclaration
    : Attributes? MustOverrideProcedureModifier+ SubSignature
      HandlesOrImplements? StatementTerminator
    ;

InterfaceSubDeclaration
    : Attributes? InterfaceProcedureModifier* SubSignature StatementTerminator
    ;

FunctionDeclaration
    : Attributes? ProcedureModifier* FunctionSignature
      HandlesOrImplements? LineTerminator
      Block
      'End' 'Function' StatementTerminator
    ;

MustOverrideFunctionDeclaration
    : Attributes? MustOverrideProcedureModifier+ FunctionSignature
      HandlesOrImplements? StatementTerminator
    ;

InterfaceFunctionDeclaration
    : Attributes? InterfaceProcedureModifier* FunctionSignature StatementTerminator
    ;

ProcedureModifier
    : AccessModifier | 'Shadows' | 'Shared' | 'Overridable' | 'NotOverridable' | 'Overrides'
    | 'Overloads' | 'Partial' | 'Iterator' | 'Async'
    ;

MustOverrideProcedureModifier
    : ProcedureModifier
    | 'MustOverride'
    ;

InterfaceProcedureModifier
    : 'Shadows' | 'Overloads'
    ;

HandlesOrImplements
    : HandlesClause
    | ImplementsClause
    ;

Los métodos, que tienen una lista opcional de parámetros y un valor devuelto opcional, se comparten o no. Se accede a los métodos compartidos a través de la clase o instancias de la clase . Se accede a los métodos no compartidos, también denominados métodos de instancia, a través de instancias de la clase . En el ejemplo siguiente se muestra una clase Stack que tiene varios métodos compartidos (Clone y Flip) y varios métodos de instancia (Push, Pop, y ToString):

Public Class Stack
    Public Shared Function Clone(s As Stack) As Stack
        ...
    End Function

    Public Shared Function Flip(s As Stack) As Stack
        ...
    End Function

    Public Function Pop() As Object
        ...
    End Function

    Public Sub Push(o As Object)
        ...
    End Sub 

    Public Overrides Function ToString() As String
        ...
    End Function 
End Class 

Module Test
    Sub Main()
        Dim s As Stack = New Stack()
        Dim i As Integer

        While i < 10
            s.Push(i)
        End While

        Dim flipped As Stack = Stack.Flip(s)
        Dim cloned As Stack = Stack.Clone(s)

        Console.WriteLine("Original stack: " & s.ToString())
        Console.WriteLine("Flipped stack: " & flipped.ToString())
        Console.WriteLine("Cloned stack: " & cloned.ToString())
    End Sub
End Module

Los métodos se pueden sobrecargar, lo que significa que varios métodos pueden tener el mismo nombre siempre que tengan firmas únicas. La firma de un método consta del número y los tipos de sus parámetros. La firma de un método específicamente no incluye los modificadores de parámetro o tipo de valor devuelto, como Optional, ByRef o ParamArray. En el ejemplo siguiente se muestra una clase con una serie de sobrecargas:

Module Test
    Sub F()
        Console.WriteLine("F()")
    End Sub 

    Sub F(o As Object)
        Console.WriteLine("F(Object)")
    End Sub

    Sub F(value As Integer)
        Console.WriteLine("F(Integer)")
    End Sub 

    Sub F(a As Integer, b As Integer)
        Console.WriteLine("F(Integer, Integer)")
    End Sub 

    Sub F(values() As Integer)
        Console.WriteLine("F(Integer())")
    End Sub 

    Sub G(s As String, Optional s2 As String = 5)
        Console.WriteLine("G(String, Optional String")
    End Sub

    Sub G(s As String)
        Console.WriteLine("G(String)")
    End Sub


    Sub Main()
        F()
        F(1)
        F(CType(1, Object))
        F(1, 2)
        F(New Integer() { 1, 2, 3 })
        G("hello")
        G("hello", "world")
    End Sub
End Module

La salida del programa es:

F()
F(Integer)
F(Object)
F(Integer, Integer)
F(Integer())
G(String)
G(String, Optional String)

Las sobrecargas que solo difieren en los parámetros opcionales se pueden usar para el "control de versiones" de las bibliotecas. Por ejemplo, la versión 1 de una biblioteca podría incluir una función con parámetros opcionales:

Sub fopen(fileName As String, Optional accessMode as Integer = 0)

A continuación, la versión 2 de la biblioteca quiere agregar otro parámetro opcional "password" y quiere hacerlo sin interrumpir la compatibilidad de origen (por lo que las aplicaciones que se usan para dirigirse a v1 se pueden volver a compilar) y sin interrumpir la compatibilidad binaria (por lo que las aplicaciones que se usan para hacer referencia a v1 ahora pueden hacer referencia a v2 sin volver a compilar). Así será el aspecto de v2:

Sub fopen(file As String, mode as Integer)
Sub fopen(file As String, Optional mode as Integer = 0, Optional pword As String = "")

Tenga en cuenta que los parámetros opcionales de una API pública no son compatibles con CLS. Sin embargo, Visual Basic y C#4 y F#pueden consumirlos al menos.

Declaraciones de método regular, asincrónico e iterador

Hay dos tipos de métodos: subrutinas, que no devuelven valores y funciones, que sí. El cuerpo y End la construcción de un método solo se pueden omitir si el método se define en una interfaz o tiene el MustOverride modificador . Si no se especifica ningún tipo de valor devuelto en una función y se está usando una semántica estricta, se produce un error en tiempo de compilación; de lo contrario, el tipo es implícitamente Object o el tipo del carácter de tipo del método. El dominio de accesibilidad del tipo de valor devuelto y los tipos de parámetro de un método debe ser el mismo que o un superconjunto del dominio de accesibilidad del propio método.

Un método normal es uno con ni Async modificadores ni Iterator . Puede ser una subrutina o una función. Section Regular Methods details what happens when a regular method is invoked.

Un método de iterador es uno con el Iterator modificador y ningún Async modificador. Debe ser una función y su tipo de valor devuelto debe ser IEnumerator, IEnumerableo IEnumerator(Of T)IEnumerable(Of T) para algunos T, y no ByRef debe tener parámetros. Los métodos de iterador de sección detallan lo que sucede cuando se invoca un método iterador.

Un método asincrónico es uno con el Async modificador y sin Iterator modificador. Debe ser una subrutina o una función con tipo Task de valor devuelto o Task(Of T) para algunos T, y no ByRef debe tener parámetros. Los métodos asincrónicos de sección detallan lo que sucede cuando se invoca un método asincrónico.

Es un error en tiempo de compilación si un método no es uno de estos tres tipos de método.

Las declaraciones de subrutina y función son especiales en que sus instrucciones de principio y fin deben comenzar al principio de una línea lógica. Además, el cuerpo de unaMustOverride declaración de función o no subrutina debe iniciarse al principio de una línea lógica. Por ejemplo:

Module Test
    ' Illegal: Subroutine doesn't start the line
    Public x As Integer : Sub F() : End Sub

    ' Illegal: First statement doesn't start the line
    Sub G() : Console.WriteLine("G")
    End Sub

    ' Illegal: End Sub doesn't start the line
    Sub H() : End Sub
End Module

Declaraciones de método externo

Una declaración de método externo introduce un nuevo método cuya implementación se proporciona externa al programa.

ExternalMethodDeclaration
    : ExternalSubDeclaration
    | ExternalFunctionDeclaration
    ;

ExternalSubDeclaration
    : Attributes? ExternalMethodModifier* 'Declare' CharsetModifier? 'Sub'
      Identifier LibraryClause AliasClause?
      ( OpenParenthesis ParameterList? CloseParenthesis )? StatementTerminator
    ;

ExternalFunctionDeclaration
    : Attributes? ExternalMethodModifier* 'Declare' CharsetModifier? 'Function'
      Identifier LibraryClause AliasClause?
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )?
      StatementTerminator
    ;

ExternalMethodModifier
    : AccessModifier
    | 'Shadows'
    | 'Overloads'
    ;

CharsetModifier
    : 'Ansi' | 'Unicode' | 'Auto'
    ;

LibraryClause
    : 'Lib' StringLiteral
    ;

AliasClause
    : 'Alias' StringLiteral
    ;

Dado que una declaración de método externo no proporciona ninguna implementación real, no tiene ningún cuerpo ni End construcción del método. Los métodos externos se comparten implícitamente, es posible que no tengan parámetros de tipo y que no controlen eventos ni implementen miembros de interfaz. Si no se especifica ningún tipo de valor devuelto en una función y se está usando una semántica estricta, se produce un error en tiempo de compilación. De lo contrario, el tipo es implícitamente Object o el tipo del carácter de tipo del método. El dominio de accesibilidad del tipo de valor devuelto y los tipos de parámetro de un método externo debe ser el mismo que o un superconjunto del dominio de accesibilidad del propio método externo.

La cláusula library de una declaración de método externo especifica el nombre del archivo externo que implementa el método . La cláusula alias opcional es una cadena que especifica el ordinal numérico (prefijo por un # carácter) o el nombre del método en el archivo externo. También se puede especificar un modificador de juego de caracteres único, que rige el juego de caracteres usado para serializar cadenas durante una llamada al método externo. El Unicode modificador serializa todas las cadenas en valores Unicode, el Ansi modificador serializa todas las cadenas en valores ANSI y el Auto modificador serializa las cadenas según las reglas de .NET Framework según el nombre del método o el nombre del alias si se especifica. Si no se especifica ningún modificador, el valor predeterminado es Ansi.

Si Ansi se especifica o Unicode , el nombre del método se busca en el archivo externo sin modificaciones. Si Auto se especifica, la búsqueda del nombre del método depende de la plataforma. Si la plataforma se considera ANSI (por ejemplo, Windows 95, Windows 98, Windows ME), el nombre del método se busca sin modificaciones. Si se produce un error en la búsqueda, se anexa y A se vuelve a intentar la búsqueda. Si la plataforma se considera Unicode (por ejemplo, Windows NT, Windows 2000, Windows XP), se anexa un W y se busca el nombre. Si se produce un error en la búsqueda, la búsqueda se vuelve a intentar sin .W Por ejemplo:

Module Test
    ' All platforms bind to "ExternSub".
    Declare Ansi Sub ExternSub Lib "ExternDLL" ()

    ' All platforms bind to "ExternSub".
    Declare Unicode Sub ExternSub Lib "ExternDLL" ()

    ' ANSI platforms: bind to "ExternSub" then "ExternSubA".
    ' Unicode platforms: bind to "ExternSubW" then "ExternSub".
    Declare Auto Sub ExternSub Lib "ExternDLL" ()
End Module

Los tipos de datos que se pasan a métodos externos se calculan según las convenciones de serialización de datos de .NET Framework con una excepción. Las variables de cadena que se pasan por valor (es decir, ByVal x As String) se serializan con el tipo BSTR de OLE Automation y los cambios realizados en BSTR en el método externo se reflejan de nuevo en el argumento de cadena. Esto se debe a que el tipo String de métodos externos es mutable y esta serialización especial imita ese comportamiento. Los parámetros de cadena que se pasan por referencia (es decir, ByRef x As String) se serializarán como puntero al tipo BSTR de automatización OLE. Es posible invalidar estos comportamientos especiales especificando el System.Runtime.InteropServices.MarshalAsAttribute atributo en el parámetro .

En el ejemplo se muestra el uso de métodos externos:

Class Path
    Declare Function CreateDirectory Lib "kernel32" ( _
        Name As String, sa As SecurityAttributes) As Boolean
    Declare Function RemoveDirectory Lib "kernel32" ( _
        Name As String) As Boolean
    Declare Function GetCurrentDirectory Lib "kernel32" ( _
        BufSize As Integer, Buf As String) As Integer
    Declare Function SetCurrentDirectory Lib "kernel32" ( _
        Name As String) As Boolean
End Class

Métodos reemplazables

El Overridable modificador indica que un método se puede invalidar. El Overrides modificador indica que un método invalida un método reemplazable de tipo base que tiene la misma firma. El NotOverridable modificador indica que un método reemplazable no se puede invalidar aún más. El MustOverride modificador indica que se debe invalidar un método en clases derivadas.

Algunas combinaciones de estos modificadores no son válidas:

  • Overridable y NotOverridable son mutuamente excluyentes y no se pueden combinar.

  • MustOverride implica Overridable (y, por tanto, no puede especificarlo) y no se puede combinar con NotOverridable.

  • NotOverridable no se puede combinar con Overridable ni MustOverride y debe combinarse con Overrides.

  • Overrides implica Overridable (y, por tanto, no puede especificarlo) y no se puede combinar con MustOverride.

También hay restricciones adicionales en los métodos reemplazables:

  • Un MustOverride método puede no incluir un cuerpo de método o una End construcción, no invalidar otro método y solo puede aparecer en MustInherit clases.

  • Si un método especifica Overrides y no hay ningún método base coincidente que invalide, se produce un error en tiempo de compilación. Es posible que un método de invalidación no especifique Shadows.

  • Es posible que un método no invalide otro método si el dominio de accesibilidad del método invalidado no es igual al dominio de accesibilidad del método que se está invalidando. La única excepción es que un método que reemplaza un Protected Friend método en otro ensamblado que no tiene Friend acceso debe especificar Protected (no Protected Friend).

  • Private Los métodos no pueden ser Overridable, NotOverridableo MustOverride, ni pueden invalidar otros métodos.

  • Es posible que los métodos de NotInheritable las clases no se declaren Overridable o MustOverride.

En el ejemplo siguiente se muestran las diferencias entre los métodos invalidables y no reemplazables:

Class Base
    Public Sub F()
        Console.WriteLine("Base.F")
    End Sub

    Public Overridable Sub G()
        Console.WriteLine("Base.G")
    End Sub
End Class

Class Derived
    Inherits Base

    Public Shadows Sub F()
        Console.WriteLine("Derived.F")
    End Sub

    Public Overrides Sub G()
        Console.WriteLine("Derived.G")
    End Sub
End Class

Module Test
    Sub Main()
        Dim d As Derived = New Derived()
        Dim b As Base = d

        b.F()
        d.F()
        b.G()
        d.G()
    End Sub
End Module

En el ejemplo, la clase Base presenta un método F y un Overridable método G. La clase Derived presenta un nuevo método F, por lo que sombrea el heredado Fy también invalida el método Gheredado . En el ejemplo se genera la siguiente salida:

Base.F
Derived.F
Derived.G
Derived.G

Observe que la instrucción b.G() invoca Derived.G, no Base.G. Esto se debe a que el tipo en tiempo de ejecución de la instancia (que es Derived) en lugar del tipo en tiempo de compilación de la instancia (que es Base) determina la implementación del método real que se va a invocar.

Métodos compartidos

El Shared modificador indica que un método es un método compartido. Un método compartido no funciona en una instancia específica de un tipo y se puede invocar directamente desde un tipo en lugar de a través de una instancia determinada de un tipo. Sin embargo, es válido usar una instancia para calificar un método compartido. No es válido hacer referencia a Me, MyClasso MyBase en un método compartido. Es posible que los métodos compartidos no sean Overridable, NotOverridableo MustOverridey no invaliden los métodos. Es posible que los métodos definidos en módulos e interfaces estándar no especifiquen Shared, ya que ya están implícitamente Shared .

Un método declarado en una estructura o clase sin un Shared modificador es un método de instancia. Un método de instancia funciona en una instancia determinada de un tipo. Los métodos de instancia solo se pueden invocar a través de una instancia de un tipo y pueden hacer referencia a la instancia a través de la Me expresión.

En el ejemplo siguiente se muestran las reglas para acceder a los miembros compartidos y de instancia:

Class Test
    Private x As Integer
    Private Shared y As Integer

    Sub F()
        x = 1 ' Ok, same as Me.x = 1.
        y = 1 ' Ok, same as Test.y = 1.
    End Sub

    Shared Sub G()
        x = 1 ' Error, cannot access Me.x.
        y = 1 ' Ok, same as Test.y = 1.
    End Sub

    Shared Sub Main()
        Dim t As Test = New Test()

        t.x = 1 ' Ok.
        t.y = 1 ' Ok.
        Test.x = 1 ' Error, cannot access instance member through type.
        Test.y = 1 ' Ok.
    End Sub
End Class

El método F muestra que, en un miembro de función de instancia, se puede usar un identificador para acceder a los miembros de instancia y a los miembros compartidos. El método G muestra que, en un miembro de función compartida, se trata de un error para acceder a un miembro de instancia a través de un identificador. El método Main muestra que, en una expresión de acceso de miembro, se debe tener acceso a los miembros de instancia a través de instancias, pero se puede acceder a los miembros compartidos a través de tipos o instancias.

Parámetros de métodos

Un parámetro es una variable que se puede usar para pasar información dentro y fuera de un método. Los parámetros de un método se declaran mediante la lista de parámetros del método, que consta de uno o varios parámetros separados por comas.

ParameterList
    : Parameter ( Comma Parameter )*
    ;

Parameter
    : Attributes? ParameterModifier* ParameterIdentifier ( 'As' TypeName )?
      ( Equals ConstantExpression )?
    ;

ParameterModifier
    : 'ByVal' | 'ByRef' | 'Optional' | 'ParamArray'
    ;

ParameterIdentifier
    : Identifier IdentifierModifiers
    ;

Si no se especifica ningún tipo para un parámetro y se usa una semántica estricta, se produce un error en tiempo de compilación. De lo contrario, el tipo predeterminado es Object o el tipo del carácter de tipo del parámetro. Incluso en la semántica permisiva, si un parámetro incluye una As cláusula , todos los parámetros deben especificar tipos.

Los parámetros se especifican como parámetros value, reference, optional o paramarray por los modificadores ByVal, ByRef, Optionaly ParamArray, respectivamente. Parámetro que no especifica ByRef ni ByVal tiene como valor predeterminado ByVal.

Los nombres de parámetro se limitan a todo el cuerpo del método y siempre son accesibles públicamente. Una invocación de método crea una copia, específica de esa invocación, de los parámetros y la lista de argumentos de la invocación asigna valores o referencias de variables a los parámetros recién creados. Dado que las declaraciones de método externo y las declaraciones de delegado no tienen cuerpo, los nombres de parámetro duplicados se permiten en listas de parámetros, pero no se recomienda.

El identificador puede ir seguido del modificador ? de nombre que acepta valores NULL para indicar que admite valores NULL y también por modificadores de nombre de matriz para indicar que es una matriz. Pueden combinarse, por ejemplo, "ByVal x?() As Integer". No se permite usar límites de matriz explícitos; además, si el modificador de nombre que acepta valores NULL está presente, debe haber una As cláusula.

Parámetros de valor

Un parámetro de valor se declara con un modificador explícito ByVal . Si se usa el ByVal modificador, es posible que no se especifique el ByRef modificador. Existe un parámetro de valor con la invocación del miembro al que pertenece el parámetro y se inicializa con el valor del argumento especificado en la invocación. Un parámetro de valor deja de existir al devolver el miembro.

Se permite que un método asigne nuevos valores a un parámetro value. Estas asignaciones solo afectan a la ubicación de almacenamiento local representada por el parámetro value; no tienen ningún efecto en el argumento real proporcionado en la invocación del método.

Un parámetro value se usa cuando el valor de un argumento se pasa a un método y las modificaciones del parámetro no afectan al argumento original. Un parámetro de valor hace referencia a su propia variable, una que es distinta de la variable del argumento correspondiente. Esta variable se inicializa copiando el valor del argumento correspondiente. En el ejemplo siguiente se muestra un método F que tiene un parámetro de valor denominado p:

Module Test
    Sub F(p As Integer)
        Console.WriteLine("p = " & p)
        p += 1
    End Sub 

    Sub Main()
        Dim a As Integer = 1

        Console.WriteLine("pre: a = " & a)
        F(a)
        Console.WriteLine("post: a = " & a)
    End Sub
End Module

En el ejemplo se genera la salida siguiente, aunque se modifique el parámetro p value:

pre: a = 1
p = 1
post: a = 1

Parámetros de referencia

Un parámetro de referencia es un parámetro declarado con un ByRef modificador . Si se especifica el ByRef modificador, es posible que no se use el ByVal modificador . Un parámetro de referencia no crea una nueva ubicación de almacenamiento. En su lugar, un parámetro de referencia representa la variable especificada como argumento en la invocación de método o constructor. Conceptualmente, el valor de un parámetro de referencia es siempre el mismo que la variable subyacente.

Los parámetros de referencia actúan en dos modos, ya sea como alias o a través de copy-in copy-back.

Alias. Se usa un parámetro de referencia cuando el parámetro actúa como alias para un argumento proporcionado por el autor de la llamada. Un parámetro de referencia no define una variable, sino que hace referencia a la variable del argumento correspondiente. Las modificaciones de un parámetro de referencia afectan directamente e inmediatamente al argumento correspondiente. En el ejemplo siguiente se muestra un método Swap que tiene dos parámetros de referencia:

Module Test
    Sub Swap(ByRef a As Integer, ByRef b As Integer)
        Dim t As Integer = a
        a = b
        b = t
    End Sub 

    Sub Main()
        Dim x As Integer = 1
        Dim y As Integer = 2

        Console.WriteLine("pre: x = " & x & ", y = " & y)
        Swap(x, y)
        Console.WriteLine("post: x = " & x & ", y = " & y)
    End Sub 
End Module

La salida del programa es:

pre: x = 1, y = 2
post: x = 2, y = 1

Para la invocación del método Swap en la clase Main, a representa x, y b representa y. Por lo tanto, la invocación tiene el efecto de intercambiar los valores de x y y.

En un método que toma parámetros de referencia, es posible que varios nombres representen la misma ubicación de almacenamiento:

Module Test
    Private s As String

    Sub F(ByRef a As String, ByRef b As String)
        s = "One"
        a = "Two"
        b = "Three"
    End Sub

    Sub G()
        F(s, s)
    End Sub
End Module

En el ejemplo, la invocación del método F en pasa una referencia a s para y abG . Por lo tanto, para esa invocación, los nombres s, ay b todos hacen referencia a la misma ubicación de almacenamiento, y las tres asignaciones modifican la variable sde instancia .

Copiar copia atrás. Si el tipo de la variable que se pasa a un parámetro de referencia no es compatible con el tipo del parámetro de referencia, o si una variable no variable (por ejemplo, una propiedad) se pasa como argumento a un parámetro de referencia, o si la invocación está enlazada en tiempo de ejecución, se asigna una variable temporal y se pasa al parámetro de referencia. El valor que se pasa se copiará en esta variable temporal antes de que se invoque el método y se copiará de nuevo en la variable original (si hay uno y si se puede escribir) cuando el método devuelva. Por lo tanto, un parámetro de referencia no puede contener necesariamente una referencia al almacenamiento exacto de la variable que se pasa, y es posible que los cambios en el parámetro de referencia no se reflejen en la variable hasta que el método salga. Por ejemplo:

Class Base
End Class

Class Derived
    Inherits Base
End Class

Module Test
    Sub F(ByRef b As Base)
        b = New Base()
    End Sub

    Property G() As Base
        Get
        End Get
        Set
        End Set
    End Property

    Sub Main()
        Dim d As Derived

        F(G)   ' OK.
        F(d)   ' Throws System.InvalidCastException after F returns.
    End Sub
End Module

En el caso de la primera invocación de F, se crea una variable temporal y el valor de la propiedad G se asigna a ella y se pasa a F. Tras devolver de F, el valor de la variable temporal se asigna de nuevo a la propiedad de G. En el segundo caso, se crea otra variable temporal y el valor de d se asigna a ella y se pasa a F. Al devolver de F, el valor de la variable temporal se devuelve al tipo de la variable , Derivedy se asigna a d. Dado que el valor que se pasa no se puede convertir a Derived, se produce una excepción en tiempo de ejecución.

Parámetros opcionales

Se declara un parámetro opcional con el Optional modificador . Los parámetros que siguen un parámetro opcional en la lista de parámetros formales también deben ser opcionales; Si no se especifica el Optional modificador en los parámetros siguientes, se desencadenará un error en tiempo de compilación. Un parámetro opcional de algún tipo T? que acepta valores NULL o un tipo T que no acepta valores NULL debe especificar una expresión e constante que se usará como valor predeterminado si no se especifica ningún argumento. Si e se evalúa como Nothing de tipo Object, el valor predeterminado del tipo de parámetro se usará como valor predeterminado para el parámetro . De lo contrario, CType(e, T) debe ser una expresión constante y se toma como valor predeterminado para el parámetro .

Los parámetros opcionales son la única situación en la que un inicializador en un parámetro es válido. La inicialización siempre se realiza como parte de la expresión de invocación, no dentro del propio cuerpo del método.

Module Test
    Sub F(x As Integer, Optional y As Integer = 20)
        Console.WriteLine("x = " & x & ", y = " & y)
    End Sub

    Sub Main()
        F(10)
        F(30,40)
    End Sub
End Module

La salida del programa es:

x = 10, y = 20
x = 30, y = 40

Es posible que no se especifiquen parámetros opcionales en declaraciones de delegado o evento, ni en expresiones lambda.

Parámetros paramArray

ParamArray Los parámetros se declaran con el ParamArray modificador . Si el ParamArray modificador está presente, se debe especificar el ByVal modificador y ningún otro parámetro puede usar el ParamArray modificador . El ParamArray tipo del parámetro debe ser una matriz unidimensional y debe ser el último parámetro de la lista de parámetros.

Un ParamArray parámetro representa un número indeterminado de parámetros del tipo de ParamArray. Dentro del propio método, un ParamArray parámetro se trata como su tipo declarado y no tiene semántica especial. Un ParamArray parámetro es implícitamente opcional, con un valor predeterminado de una matriz unidimensional vacía del tipo de ParamArray.

Un ParamArray permite especificar argumentos de una de estas dos maneras en una invocación de método:

  • El argumento proporcionado para un ParamArray puede ser una expresión única de un tipo que amplía al ParamArray tipo. En este caso, actúa ParamArray exactamente como un parámetro de valor.

  • Como alternativa, la invocación puede especificar cero o más argumentos para ParamArray, donde cada argumento es una expresión de un tipo que se puede convertir implícitamente al tipo de elemento de ParamArray. En este caso, la invocación crea una instancia del ParamArray tipo con una longitud correspondiente al número de argumentos, inicializa los elementos de la instancia de matriz con los valores de argumento especificados y usa la instancia de matriz recién creada como argumento real.

Excepto para permitir un número variable de argumentos en una invocación, un ParamArray es exactamente equivalente a un parámetro de valor del mismo tipo, como se muestra en el ejemplo siguiente.

Module Test
    Sub F(ParamArray args() As Integer)
        Dim i As Integer

        Console.Write("Array contains " & args.Length & " elements:")
        For Each i In args
            Console.Write(" " & i)
        Next i
        Console.WriteLine()
    End Sub

    Sub Main()
        Dim a As Integer() = { 1, 2, 3 }

        F(a)
        F(10, 20, 30, 40)
        F()
    End Sub
End Module

En el ejemplo se genera la salida.

Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:

La primera invocación de F simplemente pasa la matriz a como parámetro de valor. La segunda invocación de F crea automáticamente una matriz de cuatro elementos con los valores de elemento especificados y pasa esa instancia de matriz como parámetro de valor. Del mismo modo, la tercera invocación de F crea una matriz de elementos cero y pasa esa instancia como un parámetro de valor. Las invocaciones segunda y tercera son exactamente equivalentes a escribir:

F(New Integer() {10, 20, 30, 40})
F(New Integer() {})

ParamArray Es posible que no se especifiquen parámetros en declaraciones de eventos o delegados.

Control de eventos

Los métodos pueden controlar mediante declaración los eventos generados por objetos de instancia o variables compartidas. Para controlar eventos, una declaración de método especifica la Handles palabra clave y enumera uno o varios eventos.

HandlesClause
    : ( 'Handles' EventHandlesList )?
    ;

EventHandlesList
    : EventMemberSpecifier ( Comma EventMemberSpecifier )*
    ;

EventMemberSpecifier
    : Identifier Period IdentifierOrKeyword
    | 'MyBase' Period IdentifierOrKeyword
    | 'MyClass' Period IdentifierOrKeyword
    | 'Me' Period IdentifierOrKeyword
    ;

Un evento de la Handles lista se especifica mediante dos identificadores separados por un punto:

  • El primer identificador debe ser una instancia o variable compartida en el tipo contenedor que especifica el WithEvents modificador o la MyBase palabra clave o MeMyClass ; de lo contrario, se produce un error en tiempo de compilación. Esta variable contiene el objeto que generará los eventos controlados por este método.

  • El segundo identificador debe especificar un miembro del tipo del primer identificador. El miembro debe ser un evento y puede compartirse. Si se especifica una variable compartida para el primer identificador, el evento debe compartirse o se producirá un error.

Un método M de controlador se considera un controlador de eventos válido para un evento E si la instrucción AddHandler E, AddressOf M también sería válida. Sin embargo, a diferencia de una AddHandler instrucción, los controladores de eventos explícitos permiten controlar un evento con un método sin argumentos, independientemente de si se usan o no semántica estrictas:

Option Strict On

Class C1
    Event E(x As Integer)
End Class

Class C2
    withEvents C1 As New C1()

    ' Valid
    Sub M1() Handles C1.E
    End Sub

    Sub M2()
        ' Invalid
        AddHandler C1.E, AddressOf M1
    End Sub
End Class

Un único miembro puede controlar varios eventos coincidentes y varios métodos pueden controlar un solo evento. La accesibilidad de un método no tiene ningún efecto en su capacidad de controlar eventos. En el ejemplo siguiente se muestra cómo un método puede controlar eventos:

Class Raiser
    Event E1()

    Sub Raise()
        RaiseEvent E1
    End Sub
End Class

Module Test
    WithEvents x As Raiser

    Sub E1Handler() Handles x.E1
        Console.WriteLine("Raised")
    End Sub

    Sub Main()
        x = New Raiser()
        x.Raise()
        x.Raise()
    End Sub
End Module

Esto imprimirá:

Raised
Raised

Un tipo hereda todos los controladores de eventos proporcionados por su tipo base. Un tipo derivado no puede modificar de ninguna manera las asignaciones de eventos que hereda de sus tipos base, pero puede agregar controladores adicionales al evento.

Métodos de extensión

Los métodos se pueden agregar a tipos desde fuera de la declaración de tipo mediante métodos de extensión. Los métodos de extensión son métodos con el System.Runtime.CompilerServices.ExtensionAttribute atributo aplicado a ellos. Solo se pueden declarar en módulos estándar y deben tener al menos un parámetro, que especifica el tipo que extiende el método. Por ejemplo, el siguiente método de extensión extiende el tipo String:

Imports System.Runtime.CompilerServices

Module StringExtensions
    <Extension> _
    Sub Print(s As String)
        Console.WriteLine(s)
    End Sub
End Module

Nota. Aunque Visual Basic requiere que los métodos de extensión se declaren en un módulo estándar, otros lenguajes como C# pueden permitirles declararse en otros tipos de tipos. Siempre que los métodos sigan las otras convenciones que se describen aquí y el tipo contenedor no es un tipo genérico abierto y no se puede crear una instancia, Visual Basic reconocerá los métodos de extensión.

Cuando se invoca un método de extensión, la instancia en la que se invoca se pasa al primer parámetro. El primer parámetro no se puede declarar Optional ni ParamArray. Cualquier tipo, incluido un parámetro de tipo, puede aparecer como el primer parámetro de un método de extensión. Por ejemplo, los métodos siguientes amplían los tipos Integer(), cualquier tipo que implemente System.Collections.Generic.IEnumerable(Of T)y cualquier tipo en absoluto:

Imports System.Runtime.CompilerServices

Module Extensions
    <Extension> _
    Sub PrintArray(a() As Integer)
        ...
    End Sub

    <Extension> _
    Sub PrintList(Of T)(a As IEnumerable(Of T))
        ...
    End Sub

    <Extension> _
    Sub Print(Of T)(a As T)
        ...
    End Sub
End Module

Como se muestra en el ejemplo anterior, se pueden extender las interfaces. Los métodos de extensión de interfaz proporcionan la implementación del método, por lo que los tipos que implementan una interfaz que tiene métodos de extensión definidos en él todavía solo implementan los miembros declarados originalmente por la interfaz. Por ejemplo:

Imports System.Runtime.CompilerServices

Interface IAction
  Sub DoAction()
End Interface

Module IActionExtensions 
    <Extension> _
    Public Sub DoAnotherAction(i As IAction) 
        i.DoAction()
    End Sub
End Module

Class C
  Implements IAction

  Sub DoAction() Implements IAction.DoAction
    ...
  End Sub

  ' ERROR: Cannot implement extension method IAction.DoAnotherAction
  Sub DoAnotherAction() Implements IAction.DoAnotherAction
    ...
  End Sub
End Class

Los métodos de extensión también pueden tener restricciones de tipo en sus parámetros de tipo y, al igual que con los métodos genéricos que no son de extensión, se puede deducir el argumento de tipo:

Imports System.Runtime.CompilerServices

Module IEnumerableComparableExtensions
    <Extension> _
    Public Function Sort(Of T As IComparable(Of T))(i As IEnumerable(Of T)) _
        As IEnumerable(Of T)
        ...
    End Function
End Module

También se puede tener acceso a los métodos de extensión a través de expresiones de instancia implícitas dentro del tipo que se está ampliando:

Imports System.Runtime.CompilerServices

Class C1
    Sub M1()
        Me.M2()
        M2()
    End Sub
End Class

Module C1Extensions
    <Extension>
    Sub M2(c As C1)
        ...
    End Sub
End Module

Para los fines de accesibilidad, los métodos de extensión también se tratan como miembros del módulo estándar en los que se declaran, no tienen acceso adicional a los miembros del tipo que extienden más allá del acceso que tienen en virtud de su contexto de declaración.

Los métodos de extensiones solo están disponibles cuando el método de módulo estándar está en el ámbito. De lo contrario, el tipo original no parecerá haber sido extendido. Por ejemplo:

Imports System.Runtime.CompilerServices

Class C1
End Class

Namespace N1
    Module C1Extensions
        <Extension> _
        Sub M1(c As C1)
            ...
        End Sub
    End Module
End Namespace

Module Test
    Sub Main()
        Dim c As New C1()

        ' Error: c has no member named "M1"
        c.M1()
    End Sub
End Module

Si se hace referencia a un tipo cuando solo hay disponible un método de extensión en el tipo, se producirá un error en tiempo de compilación.

Es importante tener en cuenta que los métodos de extensión se consideran miembros del tipo en todos los contextos en los que los miembros están enlazados, como el patrón fuertemente tipado For Each . Por ejemplo:

Imports System.Runtime.CompilerServices

Class C1
End Class

Class C1Enumerator
    ReadOnly Property Current() As C1
        Get
            ...
        End Get
    End Property

    Function MoveNext() As Boolean
        ...
    End Function
End Class

Module C1Extensions
    <Extension> _
    Function GetEnumerator(c As C1) As C1Enumerator
        ...
    End Function
End Module

Module Test
    Sub Main()
        Dim c As New C1()

        ' Valid
        For Each o As Object In c
            ...
        Next o
    End Sub
End Module

También se pueden crear delegados que hacen referencia a métodos de extensión. Por lo tanto, el código:

Delegate Sub D1()

Module Test
    Sub Main()
        Dim s As String = "Hello, World!"
        Dim d As D1

        d = AddressOf s.Print
        d()
    End Sub
End Module

es aproximadamente equivalente a:

Delegate Sub D1()

Module Test
    Sub Main()
      Dim s As String = "Hello, World!"
      Dim d As D1

      d = CType([Delegate].CreateDelegate(GetType(D1), s, _
                GetType(StringExtensions).GetMethod("Print")), D1)
      d()
    End Sub
End Module

Nota. Visual Basic normalmente inserta una comprobación en una llamada al método de instancia que hace que se produzca una System.NullReferenceException excepción si la instancia en la que se invoca el método es Nothing. En el caso de los métodos de extensión, no hay ninguna manera eficaz de insertar esta comprobación, por lo que los métodos de extensión deberán comprobar explícitamente para Nothing.

Nota. Un tipo de valor se boxeará cuando se pase como argumento ByVal a un parámetro escrito como una interfaz. Esto implica que los efectos secundarios del método de extensión funcionarán en una copia de la estructura en lugar del original. Aunque el lenguaje no pone restricciones en el primer argumento de un método de extensión, se recomienda que los métodos de extensión no se usen para extender los tipos de valor o que al extender los tipos de valor, se pasa ByRef el primer parámetro para asegurarse de que los efectos secundarios funcionan en el valor original.

Métodos parciales

Un método parcial es un método que especifica una firma, pero no el cuerpo del método. El cuerpo del método se puede proporcionar mediante otra declaración de método con el mismo nombre y firma, lo más probable es que en otra declaración parcial del tipo. Por ejemplo:

a.vb:

' Designer generated code
Public Partial Class MyForm
    Private Partial Sub ValidateControls()
    End Sub

    Public Sub New()
        ' Initialize controls
        ...

        ValidateControls()
    End Sub    
End Class

b.vb:

Public Partial Class MyForm
    Public Sub ValidateControls()
        ' Validation logic goes here
        ...
    End Sub
End Class

En este ejemplo, una declaración parcial de la clase MyForm declara un método ValidateControls parcial sin ninguna implementación. El constructor de la declaración parcial llama al método parcial, aunque no haya ningún cuerpo proporcionado en el archivo. A continuación, la otra declaración parcial de MyForm proporciona la implementación del método .

Se puede llamar a métodos parciales independientemente de si se ha proporcionado un cuerpo; si no se proporciona ningún cuerpo de método, se omite la llamada. Por ejemplo:

Public Class C1
    Private Partial Sub M1()
    End Sub

    Public Sub New()
        ' Since no implementation is supplied, this call will not be made.
        M1()
    End Sub
End Class

Las expresiones que se pasan como argumentos a una llamada de método parcial que se omite se omiten también y no se evalúan. (Nota. Esto significa que los métodos parciales son una manera muy eficaz de proporcionar un comportamiento definido en dos tipos parciales, ya que los métodos parciales no tienen ningún costo si no se usan).

La declaración de método parcial debe declararse como Private y siempre debe ser una subrutina sin instrucciones en su cuerpo. Los métodos parciales no pueden implementar métodos de interfaz, aunque el método que proporciona su cuerpo puede.

Solo un método puede proporcionar un cuerpo a un método parcial. Un método que proporciona un cuerpo a un método parcial debe tener la misma firma que el método parcial, las mismas restricciones en cualquier parámetro de tipo, los mismos modificadores de declaración y los mismos nombres de parámetro de tipo y parámetro. Los atributos del método parcial y el método que proporciona su cuerpo se combinan, al igual que los atributos de los parámetros de los métodos. Del mismo modo, se combina la lista de eventos que controlan los métodos. Por ejemplo:

Class C1
    Event E1()
    Event E2()

    Private Partial Sub S() Handles Me.E1
    End Sub

    ' Handles both E1 and E2
    Private Sub S() Handles Me.E2
        ...
    End Sub
End Class

Constructores

Los constructores son métodos especiales que permiten controlar la inicialización. Se ejecutan después de que el programa comience o cuando se cree una instancia de un tipo. A diferencia de otros miembros, los constructores no se heredan y no introducen un nombre en el espacio de declaración de un tipo. Los constructores solo pueden invocarse mediante expresiones de creación de objetos o por .NET Framework; nunca se pueden invocar directamente.

Nota. Los constructores tienen la misma restricción en la colocación de línea que tienen las subrutinas. La instrucción inicial, la instrucción end y el bloque deben aparecer al principio de una línea lógica.

ConstructorMemberDeclaration
    : Attributes? ConstructorModifier* 'Sub' 'New'
      ( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
      Block?
      'End' 'Sub' StatementTerminator
    ;

ConstructorModifier
    : AccessModifier
    | 'Shared'
    ;

Constructores de instancia

Los constructores de instancia inicializan instancias de un tipo y las ejecuta .NET Framework cuando se crea una instancia. La lista de parámetros de un constructor está sujeta a las mismas reglas que la lista de parámetros de un método. Los constructores de instancia se pueden sobrecargar.

Todos los constructores de tipos de referencia deben invocar a otro constructor. Si la invocación es explícita, debe ser la primera instrucción en el cuerpo del método del constructor. La instrucción puede invocar a otro de los constructores de instancia del tipo ,por ejemplo, Me.New(...) o MyClass.New(...) bien , o si no es una estructura, puede invocar a un constructor de instancia del tipo base del tipo , por ejemplo, MyBase.New(...). No es válido para que un constructor se invoque a sí mismo. Si un constructor omite una llamada a otro constructor, MyBase.New() es implícito. Si no hay ningún constructor de tipo base sin parámetros, se produce un error en tiempo de compilación. Dado Me que no se considera construido hasta después de la llamada a un constructor de clase base, los parámetros de una instrucción de invocación de constructor no pueden hacer referencia Mea , MyClasso MyBase implícita o explícitamente.

Cuando la primera instrucción de un constructor tiene el formato MyBase.New(...), el constructor realiza implícitamente las inicializaciones especificadas por los inicializadores de variable de las variables de instancia declaradas en el tipo . Esto corresponde a una secuencia de asignaciones que se ejecutan inmediatamente después de invocar el constructor de tipo base directo. Este orden garantiza que todas las variables de instancia base se inicialicen mediante sus inicializadores de variables antes de que se ejecuten las instrucciones que tengan acceso a la instancia. Por ejemplo:

Class A
    Protected x As Integer = 1
End Class

Class B
    Inherits A

    Private y As Integer = x

    Public Sub New()
        Console.WriteLine("x = " & x & ", y = " & y)
    End Sub
End Class

Cuando New B() se usa para crear una instancia de B, se genera la siguiente salida:

x = 1, y = 1

El valor de es 1 porque el inicializador de variable se ejecuta después de y invocar el constructor de clase base. Los inicializadores de variables se ejecutan en el orden textual en que aparecen en la declaración de tipo.

Cuando un tipo declara solo Private constructores, no es posible en general que otros tipos deriven del tipo o creen instancias del tipo; la única excepción es los tipos anidados dentro del tipo. Private Los constructores se usan normalmente en tipos que solo Shared contienen miembros.

Si un tipo no contiene declaraciones de constructor de instancia, se proporciona automáticamente un constructor predeterminado. El constructor predeterminado simplemente invoca el constructor sin parámetros del tipo base directo. Si el tipo base directo no tiene un constructor sin parámetros accesible, se produce un error en tiempo de compilación. El tipo de acceso declarado para el constructor predeterminado es Public a menos que el tipo sea MustInherit, en cuyo caso el constructor predeterminado es Protected.

Nota. El acceso predeterminado para el constructor predeterminado de un MustInherit tipo es Protected porque MustInherit las clases no se pueden crear directamente. Por lo tanto, no hay ningún punto en hacer que el constructor Publicpredeterminado .

En el ejemplo siguiente se proporciona un constructor predeterminado porque la clase no contiene declaraciones de constructor:

Class Message
    Dim sender As Object
    Dim text As String
End Class

Por lo tanto, el ejemplo es exactamente equivalente a lo siguiente:

Class Message
    Dim sender As Object
    Dim text As String

    Sub New()
    End Sub
End Class

Los constructores predeterminados que se emiten en una clase generada por el diseñador marcada con el atributo Microsoft.VisualBasic.CompilerServices.DesignerGeneratedAttribute llamarán al método Sub InitializeComponent(), si existe, después de la llamada al constructor base. (Nota. Esto permite que los archivos generados por el diseñador, como los creados por el diseñador de WinForms, omitan el constructor en el archivo del diseñador. Esto permite al programador especificarlo a sí mismo, si así lo eligen).

Constructores compartidos

Los constructores compartidos inicializan las variables compartidas de un tipo; se ejecutan después de que el programa comience a ejecutarse, pero antes de cualquier referencia a un miembro del tipo. Un constructor compartido especifica el Shared modificador, a menos que esté en un módulo estándar en cuyo caso el Shared modificador esté implícito.

A diferencia de los constructores de instancia, los constructores compartidos tienen acceso público implícito, no tienen parámetros y pueden no llamar a otros constructores. Antes de la primera instrucción de un constructor compartido, el constructor compartido realiza implícitamente las inicializaciones especificadas por los inicializadores de variables compartidas declaradas en el tipo . Esto corresponde a una secuencia de asignaciones que se ejecutan inmediatamente al entrar en el constructor. Los inicializadores de variable se ejecutan en el orden textual en que aparecen en la declaración de tipo.

En el ejemplo siguiente se muestra una Employee clase con un constructor compartido que inicializa una variable compartida:

Imports System.Data

Class Employee
    Private Shared ds As DataSet

    Shared Sub New()
        ds = New DataSet()
    End Sub

    Public Name As String
    Public Salary As Decimal
End Class

Existe un constructor compartido independiente para cada tipo genérico cerrado. Dado que el constructor compartido se ejecuta exactamente una vez para cada tipo cerrado, es un lugar conveniente para aplicar comprobaciones en tiempo de ejecución en el parámetro de tipo que no se pueden comprobar en tiempo de compilación a través de restricciones. Por ejemplo, el siguiente tipo usa un constructor compartido para exigir que el parámetro de tipo sea Integer o Double:

Class EnumHolder(Of T)
    Shared Sub New() 
        If Not GetType(T).IsEnum() Then
            Throw New ArgumentException("T must be an enumerated type.")
        End If
    End Sub
End Class

Exactamente cuando se ejecutan constructores compartidos depende principalmente de la implementación, aunque se proporcionan varias garantías si se define explícitamente un constructor compartido:

  • Los constructores compartidos se ejecutan antes del primer acceso a cualquier campo estático del tipo.

  • Los constructores compartidos se ejecutan antes de la primera invocación de cualquier método estático del tipo.

  • Los constructores compartidos se ejecutan antes de la primera invocación de cualquier constructor para el tipo.

Las garantías anteriores no se aplican en la situación en la que se crea implícitamente un constructor compartido para inicializadores compartidos. El resultado del ejemplo siguiente no es seguro, porque no se define el orden exacto de carga y, por tanto, la ejecución del constructor compartido:

Module Test
    Sub Main()
        A.F()
        B.F()
    End Sub
End Module

Class A
    Shared Sub New()
        Console.WriteLine("Init A")
    End Sub

    Public Shared Sub F()
        Console.WriteLine("A.F")
    End Sub
End Class

Class B
    Shared Sub New()
        Console.WriteLine("Init B")
    End Sub

    Public Shared Sub F()
        Console.WriteLine("B.F")
    End Sub
End Class

La salida podría ser una de las siguientes:

Init A
A.F
Init B
B.F

o

Init B
Init A
A.F
B.F

Por el contrario, en el ejemplo siguiente se genera una salida predecible. Tenga en cuenta que el Shared constructor de la clase A nunca se ejecuta, aunque la clase B derive de ella:

Module Test
    Sub Main()
        B.G()
    End Sub
End Module

Class A
    Shared Sub New()
        Console.WriteLine("Init A")
    End Sub
End Class

Class B
    Inherits A

    Shared Sub New()
        Console.WriteLine("Init B")
    End Sub

    Public Shared Sub G()
        Console.WriteLine("B.G")
    End Sub
End Class

La salida es la siguiente:

Init B
B.G

También es posible construir dependencias circulares que permitan Shared observar variables con inicializadores de variables en su estado de valor predeterminado, como en el ejemplo siguiente:

Class A
    Public Shared X As Integer = B.Y + 1
End Class

Class B
    Public Shared Y As Integer = A.X + 1

    Shared Sub Main()
        Console.WriteLine("X = " & A.X & ", Y = " & B.Y)
    End Sub
End Class

Esto genera la salida:

X = 1, Y = 2

Para ejecutar el Main método , el sistema carga primero la clase B. El Shared constructor de la clase B continúa calculando el valor inicial de Y, que hace que se cargue la clase A de forma recursiva porque se hace referencia al valor de A.X . A su vez, el Shared constructor de la clase A continúa calculando el valor inicial de Xy, al hacerlo, captura el valor predeterminado de Y, que es cero. A.X Por lo tanto, se inicializa en 1. A continuación, se completa el proceso de carga A , volviendo al cálculo del valor inicial de Y, el resultado de que se convierte en 2.

Si el Main método se hubiera localizado en su lugar en la clase A, el ejemplo habría generado la siguiente salida:

X = 2, Y = 1

Evite referencias circulares en Shared inicializadores de variables, ya que generalmente es imposible determinar el orden en el que se cargan las clases que contienen dichas referencias.

Eventos

Los eventos se usan para notificar al código de una aparición determinada. Una declaración de evento consta de un identificador, ya sea un tipo delegado o una lista de parámetros, y una cláusula opcional Implements .

EventMemberDeclaration
    : RegularEventMemberDeclaration
    | CustomEventMemberDeclaration
    ;

RegularEventMemberDeclaration
    : Attributes? EventModifiers* 'Event'
      Identifier ParametersOrType ImplementsClause? StatementTerminator
    ;

InterfaceEventMemberDeclaration
    : Attributes? InterfaceEventModifiers* 'Event'
      Identifier ParametersOrType StatementTerminator
    ;

ParametersOrType
    : ( OpenParenthesis ParameterList? CloseParenthesis )?
    | 'As' NonArrayTypeName
    ;

EventModifiers
    : AccessModifier
    | 'Shadows'
    | 'Shared'
    ;

InterfaceEventModifiers
    : 'Shadows'
    ;

Si se especifica un tipo de delegado, es posible que el tipo delegado no tenga un tipo de valor devuelto. Si se especifica una lista de parámetros, puede que no contenga Optional parámetros o ParamArray . El dominio de accesibilidad de los tipos de parámetro o tipo delegado debe ser el mismo que, o un superconjunto de, el dominio de accesibilidad del propio evento. Los eventos se pueden compartir especificando el Shared modificador .

Además del nombre de miembro agregado al espacio de declaración del tipo, una declaración de evento declara implícitamente varios otros miembros. Dado un evento denominado X, se agregan los siguientes miembros al espacio de declaración:

  • Si el formulario de la declaración es una declaración de método, se introduce una clase delegada anidada denominada XEventHandler . La clase delegada anidada coincide con la declaración de método y tiene la misma accesibilidad que el evento. Los atributos de la lista de parámetros se aplican a los parámetros de la clase delegate.

  • Variable Private de instancia con tipo delegado, denominada XEvent.

  • Dos métodos denominados add_X y remove_X que no se pueden invocar, invalidar o sobrecargar.

Si un tipo intenta declarar un nombre que coincide con uno de los nombres anteriores, se producirá un error en tiempo de compilación y se omitirán las declaraciones implícitas add_X y remove_X para el enlace de nombres. No es posible invalidar ni sobrecargar ninguno de los miembros introducidos, aunque es posible sombrearlos en tipos derivados. Por ejemplo, la declaración de clase

Class Raiser
    Public Event Constructed(i As Integer)
End Class

es equivalente a la siguiente declaración

Class Raiser
    Public Delegate Sub ConstructedEventHandler(i As Integer)

    Protected ConstructedEvent As ConstructedEventHandler

    Public Sub add_Constructed(d As ConstructedEventHandler)
        ConstructedEvent = _
            CType( _
                [Delegate].Combine(ConstructedEvent, d), _
                    Raiser.ConstructedEventHandler)
    End Sub

    Public Sub remove_Constructed(d As ConstructedEventHandler)
        ConstructedEvent = _
            CType( _
                [Delegate].Remove(ConstructedEvent, d), _
                    Raiser.ConstructedEventHandler)
    End Sub
End Class

Declarar un evento sin especificar un tipo de delegado es la sintaxis más sencilla y compacta, pero tiene la desventaja de declarar un nuevo tipo de delegado para cada evento. Por ejemplo, en el ejemplo siguiente, se crean tres tipos de delegado ocultos, aunque los tres eventos tengan la misma lista de parámetros:

Public Class Button
    Public Event Click(sender As Object, e As EventArgs)
    Public Event DoubleClick(sender As Object, e As EventArgs)
    Public Event RightClick(sender As Object, e As EventArgs)
End Class

En el ejemplo siguiente, los eventos simplemente usan el mismo delegado: EventHandler

Public Delegate Sub EventHandler(sender As Object, e As EventArgs)

Public Class Button
    Public Event Click As EventHandler
    Public Event DoubleClick As EventHandler
    Public Event RightClick As EventHandler
End Class

Los eventos se pueden controlar de una de estas dos maneras: estática o dinámicamente. El control estático de eventos es más sencillo y solo requiere una WithEvents variable y una Handles cláusula . En el ejemplo siguiente, la clase Form1 controla estáticamente el evento Click de objeto Button:

Public Class Form1
    Public WithEvents Button1 As New Button()

    Public Sub Button1_Click(sender As Object, e As EventArgs) _
           Handles Button1.Click
        Console.WriteLine("Button1 was clicked!")
    End Sub
End Class

El control dinámico de eventos es más complejo porque el evento debe estar conectado y desconectado explícitamente en el código. La instrucción AddHandler agrega un controlador para un evento y la instrucción RemoveHandler quita un controlador para un evento. En el ejemplo siguiente se muestra una clase Form1 que agrega Button1_Click como controlador de eventos para Button1el evento de Click :

Public Class Form1
    Public Sub New()
        ' Add Button1_Click as an event handler for Button1's Click event.
        AddHandler Button1.Click, AddressOf Button1_Click
    End Sub 

    Private Button1 As Button = New Button()

    Sub Button1_Click(sender As Object, e As EventArgs)
        Console.WriteLine("Button1 was clicked!")
    End Sub

    Public Sub Disconnect()
        RemoveHandler Button1.Click, AddressOf Button1_Click
    End Sub 
End Class

En el método Disconnect, se quita el controlador de eventos.

Eventos personalizados

Como se explicó en la sección anterior, las declaraciones de eventos definen implícitamente un campo, un add_ método y un remove_ método que se usan para realizar un seguimiento de los controladores de eventos. Sin embargo, en algunas situaciones, puede ser conveniente proporcionar código personalizado para el seguimiento de controladores de eventos. Por ejemplo, si una clase define cuarenta eventos de los que solo se controlarán algunos, el uso de una tabla hash en lugar de cuarenta campos para realizar un seguimiento de los controladores de cada evento puede ser más eficaz. Los eventos personalizados permiten definir los add_X métodos y remove_X explícitamente, lo que permite el almacenamiento personalizado para los controladores de eventos.

Los eventos personalizados se declaran de la misma manera que los eventos que especifican un tipo delegado se declaran, con la excepción de que la palabra clave Custom debe preceder a la Event palabra clave. Una declaración de evento personalizada contiene tres declaraciones: una AddHandler declaración, una RemoveHandler declaración y una RaiseEvent declaración. Ninguna de las declaraciones puede tener modificadores, aunque pueden tener atributos.

CustomEventMemberDeclaration
    : Attributes? EventModifiers* 'Custom' 'Event'
      Identifier 'As' TypeName ImplementsClause? StatementTerminator
      EventAccessorDeclaration+
      'End' 'Event' StatementTerminator
    ;

EventAccessorDeclaration
    : AddHandlerDeclaration
    | RemoveHandlerDeclaration
    | RaiseEventDeclaration
    ;

AddHandlerDeclaration
    : Attributes? 'AddHandler'
      OpenParenthesis ParameterList CloseParenthesis LineTerminator
      Block?
      'End' 'AddHandler' StatementTerminator
    ;

RemoveHandlerDeclaration
    : Attributes? 'RemoveHandler'
      OpenParenthesis ParameterList CloseParenthesis LineTerminator
      Block?
      'End' 'RemoveHandler' StatementTerminator
    ;

RaiseEventDeclaration
    : Attributes? 'RaiseEvent'
      OpenParenthesis ParameterList CloseParenthesis LineTerminator
      Block?
      'End' 'RaiseEvent' StatementTerminator
    ;

Por ejemplo:

Class Test
    Private Handlers As EventHandler

    Public Custom Event TestEvent As EventHandler
        AddHandler(value As EventHandler)
            Handlers = CType([Delegate].Combine(Handlers, value), _
                EventHandler)
        End AddHandler

        RemoveHandler(value as EventHandler)
            Handlers = CType([Delegate].Remove(Handlers, value), _
                EventHandler)
        End RemoveHandler

        RaiseEvent(sender As Object, e As EventArgs)
            Dim TempHandlers As EventHandler = Handlers

            If TempHandlers IsNot Nothing Then
                TempHandlers(sender, e)
            End If
        End RaiseEvent
    End Event
End Class

La AddHandler declaración y RemoveHandler toman un ByVal parámetro, que debe ser del tipo delegado del evento. Cuando se ejecuta una AddHandler instrucción o RemoveHandler (o una cláusula controla automáticamente un Handles evento), se llamará a la declaración correspondiente. La RaiseEvent declaración toma los mismos parámetros que el delegado de eventos y se llamará cuando se ejecute una RaiseEvent instrucción . Todas las declaraciones deben proporcionarse y se consideran subrutinas.

Tenga en cuenta que AddHandlerlas declaraciones , RemoveHandler y RaiseEvent tienen la misma restricción en la colocación de línea que tienen las subrutinas. La instrucción inicial, la instrucción end y el bloque deben aparecer al principio de una línea lógica.

Además del nombre de miembro agregado al espacio de declaración del tipo, una declaración de evento personalizada declara implícitamente varios otros miembros. Dado un evento denominado X, se agregan los siguientes miembros al espacio de declaración:

  • Método denominado add_X, correspondiente a la AddHandler declaración.

  • Método denominado remove_X, correspondiente a la RemoveHandler declaración.

  • Método denominado fire_X, correspondiente a la RaiseEvent declaración.

Si un tipo intenta declarar un nombre que coincide con uno de los nombres anteriores, se producirá un error en tiempo de compilación y todas las declaraciones implícitas se omiten para los fines del enlace de nombres. No es posible invalidar ni sobrecargar ninguno de los miembros introducidos, aunque es posible sombrearlos en tipos derivados.

Nota. Custom no es una palabra reservada.

Eventos personalizados en ensamblados de WinRT

A partir de Microsoft Visual Basic 11.0, los eventos declarados en un archivo compilado con /target:winmdobjo declarados en una interfaz de este tipo de archivo y, a continuación, implementados en otro lugar, se tratan un poco de forma diferente.

  • Las herramientas externas que se usan para compilar el winmd normalmente solo permitirán determinados tipos de delegado, como System.EventHandler(Of T) o System.TypedEventHandle(Of T, U), y no permitirán a otros usuarios.

  • El XEvent campo tiene el tipo System.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T) donde T es el tipo de delegado.

  • El descriptor de acceso AddHandler devuelve un System.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokeny el descriptor de acceso RemoveHandler toma un único parámetro del mismo tipo.

Este es un ejemplo de este evento personalizado.

Imports System.Runtime.InteropServices.WindowsRuntime

Public NotInheritable Class ClassInWinMD
    Private XEvent As EventRegistrationTokenTable(Of EventHandler(Of Integer))

    Public Custom Event X As EventHandler(Of Integer)
        AddHandler(handler As EventHandler(Of Integer))
            Return EventRegistrationTokenTable(Of EventHandler(Of Integer)).
                   GetOrCreateEventRegistrationTokenTable(XEvent).
                   AddEventHandler(handler)
        End AddHandler

        RemoveHandler(token As EventRegistrationToken)
            EventRegistrationTokenTable(Of EventHandler(Of Integer)).
                GetOrCreateEventRegistrationTokenTable(XEvent).
                RemoveEventHandler(token)
        End RemoveHandler

        RaiseEvent(sender As Object, i As Integer)
            Dim table = EventRegistrationTokenTable(Of EventHandler(Of Integer)).
                GetOrCreateEventRegistrationTokenTable(XEvent).
                InvocationList
            If table IsNot Nothing Then table(sender, i)
        End RaiseEvent
    End Event
End Class

Constantes

Una constante es un valor constante que es un miembro de un tipo.

ConstantMemberDeclaration
    : Attributes? ConstantModifier* 'Const' ConstantDeclarators StatementTerminator
    ;

ConstantModifier
    : AccessModifier
    | 'Shadows'
    ;

ConstantDeclarators
    : ConstantDeclarator ( Comma ConstantDeclarator )*
    ;

ConstantDeclarator
    : Identifier ( 'As' TypeName )? Equals ConstantExpression StatementTerminator
    ;

Las constantes se comparten implícitamente. Si la declaración contiene una As cláusula , la cláusula especifica el tipo del miembro introducido por la declaración. Si se omite el tipo, se deduce el tipo de la constante. El tipo de una constante solo puede ser un tipo primitivo o Object. Si una constante se escribe como Object y no hay ningún carácter de tipo, el tipo real de la constante será el tipo de la expresión constante. De lo contrario, el tipo de la constante es el tipo del carácter de tipo de la constante.

En el ejemplo siguiente se muestra una clase denominada Constants que tiene dos constantes públicas:

Class Constants
    Public Const A As Integer = 1
    Public Const B As Integer = A + 1
End Class

Se puede tener acceso a las constantes a través de la clase , como en el ejemplo siguiente, que imprime los valores de Constants.A y Constants.B.

Module Test
    Sub Main()
        Console.WriteLine(Constants.A & ", " & Constants.B)
    End Sub 
End Module

Una declaración constante que declara varias constantes es equivalente a varias declaraciones de constantes únicas. En el ejemplo siguiente se declaran tres constantes en una instrucción de declaración.

Class A
    Protected Const x As Integer = 1, y As Long = 2, z As Short = 3
End Class

Esta declaración es equivalente a lo siguiente:

Class A
    Protected Const x As Integer = 1
    Protected Const y As Long = 2
    Protected Const z As Short = 3
End Class

El dominio de accesibilidad del tipo de la constante debe ser el mismo que o un superconjunto del dominio de accesibilidad de la propia constante. La expresión constante debe producir un valor del tipo de constante o de un tipo que se puede convertir implícitamente en el tipo de la constante. Es posible que la expresión constante no sea circular; es decir, es posible que una constante no se defina en términos propios.

El compilador evalúa automáticamente las declaraciones constantes en el orden adecuado. En el ejemplo siguiente, el compilador evalúa Yprimero , después Z, y finalmente X, generando los valores 10, 11 y 12, respectivamente.

Class A
    Public Const X As Integer = B.Z + 1
    Public Const Y As Integer = 10
End Class

Class B
    Public Const Z As Integer = A.Y + 1
End Class

Cuando se desea un nombre simbólico para un valor constante, pero el tipo del valor no se permite en una declaración constante o cuando el valor no se puede calcular en tiempo de compilación mediante una expresión constante, se puede usar una variable de solo lectura en su lugar.

Instancias y variables compartidas

Una instancia o variable compartida es un miembro de un tipo que puede almacenar información.

VariableMemberDeclaration
    : Attributes? VariableModifier+ VariableDeclarators StatementTerminator
    ;

VariableModifier
    : AccessModifier
    | 'Shadows'
    | 'Shared'
    | 'ReadOnly'
    | 'WithEvents'
    | 'Dim'
    ;

VariableDeclarators
    : VariableDeclarator ( Comma VariableDeclarator )*
    ;

VariableDeclarator
    : VariableIdentifiers 'As' ObjectCreationExpression
    | VariableIdentifiers ( 'As' TypeName )? ( Equals Expression )?
    ;

VariableIdentifiers
    : VariableIdentifier ( Comma VariableIdentifier )*
    ;

VariableIdentifier
    : Identifier IdentifierModifiers
    ;

El Dim modificador debe especificarse si no se especifica ningún modificador, pero se puede omitir de otro modo. Una sola declaración de variable puede incluir varios declaradores de variables; cada declarador de variable introduce una nueva instancia o un miembro compartido.

Si se especifica un inicializador, solo el declarador de variable puede declarar una instancia o variable compartida:

Class Test
    Dim a, b, c, d As Integer = 10  ' Invalid: multiple initialization
End Class

Esta restricción no se aplica a los inicializadores de objeto:

Class Test
    Dim a, b, c, d As New Collection() ' OK
End Class

Una variable declarada con el Shared modificador es una variable compartida. Una variable compartida identifica exactamente una ubicación de almacenamiento independientemente del número de instancias del tipo que se crean. Una variable compartida entra en vigor cuando un programa comienza a ejecutarse y deja de existir cuando finaliza el programa.

Una variable compartida solo se comparte entre instancias de un tipo genérico cerrado determinado. Por ejemplo, el programa:

Class C(Of V) 
    Shared InstanceCount As Integer = 0

    Public Sub New()  
        InstanceCount += 1 
    End Sub

    Public Shared ReadOnly Property Count() As Integer 
        Get
            Return InstanceCount
        End Get
    End Property
End Class

Class Application 
    Shared Sub Main() 
        Dim x1 As New C(Of Integer)()
        Console.WriteLine(C(Of Integer).Count)

        Dim x2 As New C(Of Double)() 
        Console.WriteLine(C(Of Integer).Count)

        Dim x3 As New C(Of Integer)() 
        Console.WriteLine(C(Of Integer).Count)
    End Sub
End Class

Imprime:

1
1
2

Una variable declarada sin el Shared modificador se denomina variable de instancia. Cada instancia de una clase contiene una copia independiente de todas las variables de instancia de la clase . Existe una variable de instancia de un tipo de referencia cuando se crea una nueva instancia de ese tipo y deja de existir cuando no hay referencias a esa instancia y el Finalize método se ha ejecutado. Una variable de instancia de un tipo de valor tiene exactamente la misma duración que la variable a la que pertenece. Es decir, cuando una variable de un tipo de valor entra en existencia o deja de existir, así que hace la variable de instancia del tipo de valor.

Si el declarador contiene una As cláusula , la cláusula especifica el tipo de los miembros introducidos por la declaración. Si se omite el tipo y se usa una semántica estricta, se produce un error en tiempo de compilación. De lo contrario, el tipo de los miembros es implícitamente Object o el tipo del carácter de tipo de los miembros.

Nota. No hay ambigüedad en la sintaxis: si un declarador omite un tipo, siempre usará el tipo de un declarador siguiente.

El dominio de accesibilidad de una instancia o del tipo o del tipo de elemento de matriz de una variable compartida debe ser el mismo que o un superconjunto del dominio de accesibilidad de la instancia o de la propia variable compartida.

En el ejemplo siguiente se muestra una Color clase que tiene variables de instancia internas denominadas redPart, greenParty bluePart:

Class Color
    Friend redPart As Short
    Friend bluePart As Short
    Friend greenPart As Short

    Public Sub New(red As Short, blue As Short, green As Short)
        redPart = red
        bluePart = blue
        greenPart = green
    End Sub
End Class

variables de Read-Only

Cuando una declaración de instancia o variable compartida incluye un ReadOnly modificador, las asignaciones a las variables introducidas por la declaración solo pueden producirse como parte de la declaración o en un constructor de la misma clase. En concreto, las asignaciones a una instancia de solo lectura o a una variable compartida solo se permiten en las situaciones siguientes:

  • En la declaración de variable que introduce la instancia o la variable compartida (mediante la inclusión de un inicializador de variable en la declaración).

  • Para una variable de instancia, en los constructores de instancia de la clase que contiene la declaración de variable. Solo se puede tener acceso a la variable de instancia de forma no calificada o a través Me de o MyClass.

  • Para una variable compartida, en el constructor compartido de la clase que contiene la declaración de variable compartida.

Una variable de solo lectura compartida es útil cuando se desea un nombre simbólico para un valor constante, pero cuando no se permite el tipo del valor en una declaración constante o cuando un valor no se puede calcular en tiempo de compilación mediante una expresión constante.

A continuación se muestra un ejemplo de la primera aplicación de este tipo, en la que se declaran ReadOnly variables compartidas de color para evitar que otros programas los cambien:

Class Color
    Friend redPart As Short
    Friend bluePart As Short
    Friend greenPart As Short

    Public Sub New(red As Short, blue As Short, green As Short)
        redPart = red
        bluePart = blue
        greenPart = green
    End Sub 

    Public Shared ReadOnly Red As Color = New Color(&HFF, 0, 0)
    Public Shared ReadOnly Blue As Color = New Color(0, &HFF, 0)
    Public Shared ReadOnly Green As Color = New Color(0, 0, &HFF)
    Public Shared ReadOnly White As Color = New Color(&HFF, &HFF, &HFF)
End Class

Las constantes y las variables compartidas de solo lectura tienen una semántica diferente. Cuando una expresión hace referencia a una constante, el valor de la constante se obtiene en tiempo de compilación, pero cuando una expresión hace referencia a una variable compartida de solo lectura, el valor de la variable compartida no se obtiene hasta el tiempo de ejecución. Tenga en cuenta la siguiente aplicación, que consta de dos programas independientes.

file1.vb:

Namespace Program1
    Public Class Utils
        Public Shared ReadOnly X As Integer = 1
    End Class
End Namespace

file2.vb:

Namespace Program2
    Module Test
        Sub Main()
            Console.WriteLine(Program1.Utils.X)
        End Sub
    End Module
End Namespace

Los espacios de nombres Program1 y Program2 indican dos programas que se compilan por separado. Dado que la variable Program1.Utils.X se declara como Shared ReadOnly, la salida del valor por la Console.WriteLine instrucción no se conoce en tiempo de compilación, sino que se obtiene en tiempo de ejecución. Por lo tanto, si se cambia el valor de X y Program1 se vuelve a compilar, la Console.WriteLine instrucción generará el nuevo valor incluso si Program2 no se vuelve a compilar. Sin embargo, si X hubiera sido una constante, el valor de X se habría obtenido en el momento Program2 de la compilación y habría quedado sin verse afectado por los cambios en Program1 hasta Program2 que se recompiló.

WithEvents Variables

Un tipo puede declarar que controla algunos conjuntos de eventos generados por una de sus instancias o variables compartidas declarando la instancia o variable compartida que genera los eventos con el WithEvents modificador . Por ejemplo:

Class Raiser
    Public Event E1()

    Public Sub Raise()
        RaiseEvent E1
    End Sub
End Class

Module Test
    Private WithEvents x As Raiser

    Private Sub E1Handler() Handles x.E1
        Console.WriteLine("Raised")
    End Sub

    Public Sub Main()
        x = New Raiser()
    End Sub
End Module

En este ejemplo, el método E1Handler controla el evento E1 que genera la instancia del tipo Raiser almacenado en la variable xde instancia .

El WithEvents modificador hace que se cambie el nombre de la variable por un carácter de subrayado inicial y se reemplace por una propiedad del mismo nombre que realiza el enlace de eventos. Por ejemplo, si el nombre de la variable es F, se cambia el nombre a _F y se declara implícitamente una propiedad F . Si hay un conflicto entre el nuevo nombre de la variable y otra declaración, se notificará un error en tiempo de compilación. Los atributos aplicados a la variable se transfieren a la variable cuyo nombre ha cambiado.

La propiedad implícita creada por una WithEvents declaración se encarga de enlazar y desenganchar a los controladores de eventos pertinentes. Cuando se asigna un valor a la variable, la propiedad llama primero al remove método para el evento en la instancia que se encuentra actualmente en la variable (desenganillando el controlador de eventos existente, si existe). A continuación, se realiza la asignación y la propiedad llama al add método para el evento en la nueva instancia de la variable (enlazando el nuevo controlador de eventos). El código siguiente es equivalente al código anterior para el módulo Testestándar :

Module Test
    Private _x As Raiser

    Public Property x() As Raiser
        Get
            Return _x
        End Get

        Set (Value As Raiser)
            ' Unhook any existing handlers.
            If _x IsNot Nothing Then
                RemoveHandler _x.E1, AddressOf E1Handler
            End If

            ' Change value.
            _x = Value

            ' Hook-up new handlers.
            If _x IsNot Nothing Then
                AddHandler _x.E1, AddressOf E1Handler
            End If
        End Set
    End Property

    Sub E1Handler()
        Console.WriteLine("Raised")
    End Sub

    Sub Main()
        x = New Raiser()
    End Sub
End Module

No es válido declarar una instancia o una variable compartida como WithEvents si la variable estuviera tipada como una estructura. Además, WithEvents no se puede especificar en una estructura y WithEvents no ReadOnly se puede combinar.

Inicializadores de variables

Las declaraciones de instancias y variables compartidas en las clases y las declaraciones de variables de instancia (pero no declaraciones de variables compartidas) en estructuras pueden incluir inicializadores de variables. En Shared el caso de las variables, los inicializadores de variables corresponden a instrucciones de asignación que se ejecutan después de que comience el programa, pero antes de que se haga referencia a la Shared variable por primera vez. Para las variables de instancia, los inicializadores de variables corresponden a instrucciones de asignación que se ejecutan cuando se crea una instancia de la clase . Las estructuras no pueden tener inicializadores de variables de instancia porque no se pueden modificar sus constructores sin parámetros.

Considere el ejemplo siguiente:

Class Test
    Public Shared x As Double = Math.Sqrt(2.0)
    Public i As Integer = 100
    Public s As String = "Hello"
End Class

Module TestModule
    Sub Main()
        Dim a As New Test()

        Console.WriteLine("x = " & Test.x & ", i = " & a.i & ", s = " & a.s)
    End Sub
End Module

En el ejemplo se genera la siguiente salida:

x = 1.4142135623731, i = 100, s = Hello

Una asignación a x se produce cuando se carga la clase y las asignaciones a i y s se producen cuando se crea una nueva instancia de la clase.

Resulta útil pensar en inicializadores de variables como instrucciones de asignación que se insertan automáticamente en el bloque del constructor del tipo. En el ejemplo siguiente se incluyen varios inicializadores de variables de instancia.

Class A
    Private x As Integer = 1
    Private y As Integer = -1
    Private count As Integer

    Public Sub New()
        count = 0
    End Sub

    Public Sub New(n As Integer)
        count = n
    End Sub
End Class

Class B
    Inherits A

    Private sqrt2 As Double = Math.Sqrt(2.0)
    Private items As ArrayList = New ArrayList(100)
    Private max As Integer

    Public Sub New()
        Me.New(100)
        items.Add("default")
    End Sub

    Public Sub New(n As Integer)
        MyBase.New(n - 1)
        max = n
    End Sub
End Class

El ejemplo corresponde al código que se muestra a continuación, donde cada comentario indica una instrucción insertada automáticamente.

Class A
    Private x, y, count As Integer

    Public Sub New()
        MyBase.New ' Invoke object() constructor.
        x = 1 ' This is a variable initializer.
        y = -1 ' This is a variable initializer.
        count = 0
    End Sub

    Public Sub New(n As Integer)
        MyBase.New ' Invoke object() constructor. 
        x = 1 ' This is a variable initializer.
        y = - 1 ' This is a variable initializer.
        count = n
    End Sub
End Class

Class B
    Inherits A

    Private sqrt2 As Double
    Private items As ArrayList
    Private max As Integer

    Public Sub New()
        Me.New(100) 
        items.Add("default")
    End Sub

    Public Sub New(n As Integer)
        MyBase.New(n - 1) 
        sqrt2 = Math.Sqrt(2.0) ' This is a variable initializer.
        items = New ArrayList(100) ' This is a variable initializer.
        max = n
    End Sub
End Class

Todas las variables se inicializan en el valor predeterminado de su tipo antes de que se ejecuten los inicializadores de variables. Por ejemplo:

Class Test
    Public Shared b As Boolean
    Public i As Integer
End Class

Module TestModule
    Sub Main()
        Dim t As New Test()
        Console.WriteLine("b = " & Test.b & ", i = " & t.i)
    End Sub
End Module

Dado b que se inicializa automáticamente en su valor predeterminado cuando se carga la clase y i se inicializa automáticamente en su valor predeterminado cuando se crea una instancia de la clase, el código anterior genera la siguiente salida:

b = False, i = 0

Cada inicializador de variable debe producir un valor del tipo de variable o de un tipo que se puede convertir implícitamente en el tipo de la variable. Un inicializador de variable puede ser circular o hacer referencia a una variable que se inicializará después de ella, en cuyo caso el valor de la variable a la que se hace referencia es su valor predeterminado para los fines del inicializador. Este inicializador es de valor dudoso.

Hay tres formas de inicializadores variables: inicializadores regulares, inicializadores de tamaño de matriz e inicializadores de objetos. Los dos primeros formularios aparecen después de un signo igual que sigue al nombre de tipo, los dos últimos forman parte de la propia declaración. Solo se puede usar una forma de inicializador en cualquier declaración determinada.

Inicializadores regulares

Un inicializador regular es una expresión que se puede convertir implícitamente en el tipo de la variable. Aparece después de un signo igual que sigue al nombre de tipo y debe clasificarse como un valor. Por ejemplo:

Module Test
    Dim x As Integer = 10
    Dim y As Integer = 20

    Sub Main()
        Console.WriteLine("x = " & x & ", y = " & y)
    End Sub
End Module

Este programa genera la salida:

x = 10, y = 20

Si una declaración de variable tiene un inicializador regular, solo se puede declarar una sola variable a la vez. Por ejemplo:

Module Test
    Sub Main()
        ' OK, only one variable declared at a time.
        Dim x As Integer = 10, y As Integer = 20

        ' Error: Can't initialize multiple variables at once.
        Dim a, b As Integer = 10
    End Sub
End Module

Inicializadores de objeto

Se especifica un inicializador de objeto mediante una expresión de creación de objetos en el lugar del nombre de tipo. Un inicializador de objeto es equivalente a un inicializador normal que asigna el resultado de la expresión de creación de objetos a la variable. SO

Module TestModule
    Sub Main()
        Dim x As New Test(10)
    End Sub
End Module

es equivalente a

Module TestModule
    Sub Main()
        Dim x As Test = New Test(10)
    End Sub
End Module

El paréntesis de un inicializador de objeto siempre se interpreta como la lista de argumentos del constructor y nunca como modificadores de tipo de matriz. Un nombre de variable con un inicializador de objeto no puede tener un modificador de tipo de matriz o un modificador de tipo que acepta valores NULL.

inicializadores de Array-Size

Un inicializador de tamaño de matriz es un modificador en el nombre de la variable que proporciona un conjunto de límites superiores de dimensión indicados por expresiones.

ArraySizeInitializationModifier
    : OpenParenthesis BoundList CloseParenthesis ArrayTypeModifiers?
    ;

BoundList
    : Bound ( Comma Bound )*
    ;

Bound
    : Expression
    | '0' 'To' Expression
    ;

Las expresiones de límite superior deben clasificarse como valores y deben convertirse implícitamente en Integer. El conjunto de límites superiores es equivalente a un inicializador de variable de una expresión de creación de matriz con los límites superiores especificados. El número de dimensiones del tipo de matriz se deduce del inicializador de tamaño de matriz. SO

Module Test
    Sub Main()
        Dim x(5, 10) As Integer
    End Sub
End Module

es equivalente a

Module Test
    Sub Main()
        Dim x As Integer(,) = New Integer(5, 10) {}
    End Sub
End Module

Todos los límites superiores deben ser iguales o mayores que -1 y todas las dimensiones deben tener un límite superior especificado. Si el tipo de elemento de la matriz que se va a inicializar es un tipo de matriz, los modificadores de tipo de matriz van a la derecha del inicializador de tamaño de matriz. Por ejemplo

Module Test
    Sub Main()
        Dim x(5,10)(,,) As Integer
    End Sub
End Module

declara una variable x local cuyo tipo es una matriz bidimensional de matrices tridimensionales de Integer, inicializada en una matriz con límites de 0..5 en la primera dimensión y 0..10 en la segunda dimensión. No es posible usar un inicializador de tamaño de matriz para inicializar los elementos de una variable cuyo tipo es una matriz de matrices.

Una declaración de variable con un inicializador de tamaño de matriz no puede tener un modificador de tipo de matriz en su tipo o un inicializador normal.

Clases System.MarshalByRefObject

Las clases que derivan de la clase System.MarshalByRefObject se serializarán a través de los límites de contexto mediante servidores proxy (es decir, por referencia) en lugar de mediante la copia (es decir, por valor). Esto significa que una instancia de tal clase puede no ser una instancia verdadera, sino que puede ser simplemente un código auxiliar que serializa las llamadas de variable y acceso a métodos a través de un límite de contexto.

Como resultado, no es posible crear una referencia a la ubicación de almacenamiento de variables definidas en estas clases. Esto significa que las variables tipadas como clases derivadas de System.MarshalByRefObject no se pueden pasar a parámetros de referencia, y es posible que no se pueda tener acceso a métodos y variables de variables con tipo de valor. En su lugar, Visual Basic trata las variables definidas en estas clases como si fueran propiedades (ya que las restricciones son las mismas en las propiedades).

Hay una excepción a esta regla: un miembro calificado implícita o explícitamente con Me está exento de las restricciones anteriores, ya que Me siempre se garantiza que sea un objeto real, no un proxy.

Propiedades

Las propiedades son una extensión natural de variables; ambos son miembros con nombre con tipos asociados y la sintaxis para acceder a variables y propiedades es la misma. Sin embargo, a diferencia de las variables, las propiedades no denotan ubicaciones de almacenamiento. En su lugar, las propiedades tienen descriptores de acceso, que especifican las instrucciones que se van a ejecutar para leer o escribir sus valores.

Las propiedades se definen con declaraciones de propiedad. La primera parte de una declaración de propiedad es similar a una declaración de campo. La segunda parte incluye un Get descriptor de acceso o un Set descriptor de acceso.

PropertyMemberDeclaration
    : RegularPropertyMemberDeclaration
    | MustOverridePropertyMemberDeclaration
    | AutoPropertyMemberDeclaration
    ;

PropertySignature
    : 'Property'
      Identifier ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )?
    ;

RegularPropertyMemberDeclaration
    : Attributes? PropertyModifier* PropertySignature
      ImplementsClause? LineTerminator
      PropertyAccessorDeclaration+
      'End' 'Property' StatementTerminator
    ;

MustOverridePropertyMemberDeclaration
    : Attributes? MustOverridePropertyModifier+ PropertySignature
      ImplementsClause? StatementTerminator
    ;

AutoPropertyMemberDeclaration
    : Attributes? AutoPropertyModifier* 'Property' Identifier
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      ( 'As' Attributes? TypeName )? ( Equals Expression )?
      ImplementsClause? LineTerminator
    | Attributes? AutoPropertyModifier* 'Property' Identifier
      ( OpenParenthesis ParameterList? CloseParenthesis )?
      'As' Attributes? 'New'
      ( NonArrayTypeName ( OpenParenthesis ArgumentList? CloseParenthesis )? )?
      ObjectCreationExpressionInitializer?
      ImplementsClause? LineTerminator
    ;

InterfacePropertyMemberDeclaration
    : Attributes? InterfacePropertyModifier* PropertySignature StatementTerminator
    ;

AutoPropertyModifier
    : AccessModifier
    | 'Shadows'
    | 'Shared'
    | 'Overridable'
    | 'NotOverridable'
    | 'Overrides'
    | 'Overloads'
    ;

PropertyModifier
    : AutoPropertyModifier
    | 'Default'
    | 'ReadOnly'
    | 'WriteOnly'
    | 'Iterator'
    ;

MustOverridePropertyModifier
    : PropertyModifier
    | 'MustOverride'
    ;

InterfacePropertyModifier
    : 'Shadows'
    | 'Overloads'
    | 'Default'
    | 'ReadOnly'
    | 'WriteOnly'
    ;

PropertyAccessorDeclaration
    : PropertyGetDeclaration
    | PropertySetDeclaration
    ;

En el ejemplo siguiente, la Button clase define una Caption propiedad .

Public Class Button
    Private captionValue As String

    Public Property Caption() As String
        Get
            Return captionValue
        End Get

        Set (Value As String)
            captionValue = value
            Repaint()
        End Set
    End Property

    ...
End Class

En función de la Button clase anterior, a continuación se muestra un ejemplo de uso de la Caption propiedad :

Dim okButton As Button = New Button()

okButton.Caption = "OK" ' Invokes Set accessor.
Dim s As String = okButton.Caption ' Invokes Get accessor.

Aquí, el Set descriptor de acceso se invoca asignando un valor a la propiedad y el Get descriptor de acceso se invoca haciendo referencia a la propiedad en una expresión.

Si no se especifica ningún tipo para una propiedad y se usa una semántica estricta, se produce un error en tiempo de compilación; De lo contrario, el tipo de la propiedad es implícitamente Object o el tipo del carácter de tipo de la propiedad. Una declaración de propiedad puede contener un Get descriptor de acceso, que recupera el valor de la propiedad, un Set descriptor de acceso, que almacena el valor de la propiedad o ambos. Dado que una propiedad declara implícitamente métodos, una propiedad se puede declarar con los mismos modificadores que un método. Si la propiedad se define en una interfaz o se define con el MustOverride modificador , se debe omitir el cuerpo de la propiedad y la End construcción; de lo contrario, se produce un error en tiempo de compilación.

La lista de parámetros de índice constituye la firma de la propiedad, por lo que las propiedades se pueden sobrecargar en parámetros de índice, pero no en el tipo de la propiedad. La lista de parámetros de índice es la misma que para un método normal. Sin embargo, ninguno de los parámetros se puede modificar con el ByRef modificador y ninguno de ellos puede denominarse Value (que está reservado para el parámetro de valor implícito en el Set descriptor de acceso).

Una propiedad se puede declarar de la siguiente manera:

  • Si la propiedad no especifica ningún modificador de tipo de propiedad, la propiedad debe tener un Get descriptor de acceso y un Set descriptor de acceso. Se dice que la propiedad es una propiedad de lectura y escritura.

  • Si la propiedad especifica el ReadOnly modificador, la propiedad debe tener un Get descriptor de acceso y puede que no tenga un Set descriptor de acceso. Se dice que la propiedad es de solo lectura. Es un error en tiempo de compilación para que una propiedad de solo lectura sea el destino de una asignación.

  • Si la propiedad especifica el WriteOnly modificador, la propiedad debe tener un Set descriptor de acceso y puede que no tenga un Get descriptor de acceso. Se dice que la propiedad es de solo escritura. Se trata de un error en tiempo de compilación para hacer referencia a una propiedad de solo escritura en una expresión, excepto como destino de una asignación o como argumento a un método.

Los Get descriptores de acceso y Set de una propiedad no son miembros distintos y no es posible declarar los descriptores de acceso de una propiedad por separado. En el ejemplo siguiente no se declara una sola propiedad de lectura y escritura. En su lugar, declara dos propiedades con el mismo nombre, una de solo lectura y una de solo escritura:

Class A
    Private nameValue As String

    ' Error, contains a duplicate member name.
    Public ReadOnly Property Name() As String 
        Get
            Return nameValue
        End Get
    End Property

    ' Error, contains a duplicate member name.
    Public WriteOnly Property Name() As String 
        Set (Value As String)
            nameValue = value
        End Set
    End Property
End Class

Dado que dos miembros declarados en la misma clase no pueden tener el mismo nombre, el ejemplo produce un error en tiempo de compilación.

De forma predeterminada, la accesibilidad de los descriptores de acceso y Set de una propiedad Get es la misma que la accesibilidad de la propia propiedad. Sin embargo, los Get descriptores de acceso y Set también pueden especificar la accesibilidad por separado de la propiedad . En ese caso, la accesibilidad de un descriptor de acceso debe ser más restrictiva que la accesibilidad de la propiedad y solo un descriptor de acceso puede tener un nivel de accesibilidad diferente de la propiedad. Los tipos de acceso se consideran más o menos restrictivos de la siguiente manera:

  • Private es más restrictivo que Public, Protected Friend, Protectedo Friend.

  • Friend es más restrictivo que Protected Friend o Public.

  • Protected es más restrictivo que Protected Friend o Public.

  • Protected Friend es más restrictivo que Public.

Cuando se puede acceder a uno de los descriptores de acceso de una propiedad, pero no lo es, la propiedad se trata como si fuera de solo lectura o de solo escritura. Por ejemplo:

Class A
    Public Property P() As Integer
        Get
            ...
        End Get

        Private Set (Value As Integer)
            ...
        End Set
    End Property
End Class

Module Test
    Sub Main()
        Dim a As A = New A()

        ' Error: A.P is read-only in this context.
        a.P = 10
    End Sub
End Module

Cuando un tipo derivado sombrea una propiedad, la propiedad derivada oculta la propiedad sombreada con respecto a la lectura y escritura. En el ejemplo siguiente, la P propiedad de B oculta la P propiedad en A con respecto a la lectura y escritura:

Class A
    Public WriteOnly Property P() As Integer
        Set (Value As Integer)
        End Set
    End Property
End Class

Class B
    Inherits A

    Public Shadows ReadOnly Property P() As Integer
       Get
       End Get
    End Property
End Class

Module Test
    Sub Main()
        Dim x As B = New B

        B.P = 10     ' Error, B.P is read-only.
    End Sub
End Module

El dominio de accesibilidad del tipo de valor devuelto o los tipos de parámetro debe ser el mismo que o un superconjunto del dominio de accesibilidad de la propia propiedad. Una propiedad solo puede tener un Set descriptor de acceso y un Get descriptor de acceso.

Excepto las diferencias en la sintaxis de invocación e declaración, las Overridablepropiedades , NotOverridableOverrides, MustOverride, y MustInherit se comportan exactamente como Overridablelos métodos , NotOverridable, Overrides, , MustOverridey MustInherit . Cuando se invalida una propiedad, la propiedad de invalidación debe ser del mismo tipo (lectura y escritura, solo lectura y solo escritura). Una Overridable propiedad no puede contener un Private descriptor de acceso.

En el ejemplo X siguiente se trata de una Overridable propiedad de solo lectura, Y es una Overridable propiedad de lectura y escritura y Z es una MustOverride propiedad de lectura y escritura.

MustInherit Class A
    Private _y As Integer

    Public Overridable ReadOnly Property X() As Integer
        Get
            Return 0
        End Get
    End Property

    Public Overridable Property Y() As Integer
        Get
            Return _y
         End Get
        Set (Value As Integer)
            _y = value
        End Set
    End Property

    Public MustOverride Property Z() As Integer
End Class

Dado que Z es MustOverride, la clase A contenedora debe declararse MustInherit.

Por el contrario, se muestra una clase que deriva de la clase A a continuación:

Class B
    Inherits A

    Private _z As Integer

    Public Overrides ReadOnly Property X() As Integer
        Get
            Return MyBase.X + 1
        End Get
    End Property

    Public Overrides Property Y() As Integer
        Get
            Return MyBase.Y
        End Get
        Set (Value As Integer)
            If value < 0 Then
                MyBase.Y = 0
            Else
                MyBase.Y = Value
            End If
        End Set
    End Property

    Public Overrides Property Z() As Integer
        Get
            Return _z
        End Get
        Set (Value As Integer)
            _z = Value
        End Set
    End Property
End Class

Aquí, las declaraciones de propiedades XYy Z invalidan las propiedades base. Cada declaración de propiedad coincide exactamente con los modificadores de accesibilidad, el tipo y el nombre de la propiedad heredada correspondiente. El Get descriptor de acceso de la propiedad X y el Set descriptor de acceso de la propiedad Y usan la MyBase palabra clave para tener acceso a las propiedades heredadas. La declaración de propiedad Z invalida la MustOverride propiedad ; por lo tanto, no hay miembros pendientes MustOverride en la clase By B se permite que sea una clase normal.

Las propiedades se pueden usar para retrasar la inicialización de un recurso hasta el momento en que se hace referencia por primera vez. Por ejemplo:

Imports System.IO

Public Class ConsoleStreams
    Private Shared reader As TextReader
    Private Shared writer As TextWriter
    Private Shared errors As TextWriter

    Public Shared ReadOnly Property [In]() As TextReader
        Get
            If reader Is Nothing Then
                reader = Console.In
            End If
            Return reader
        End Get
    End Property

    Public Shared ReadOnly Property Out() As TextWriter
        Get
            If writer Is Nothing Then
                writer = Console.Out
            End If
            Return writer
        End Get
    End Property

    Public Shared ReadOnly Property [Error]() As TextWriter
        Get
            If errors Is Nothing Then
                errors = Console.Error
            End If
            Return errors
        End Get
    End Property
End Class

La ConsoleStreams clase contiene tres propiedades, In, Outy Error, que representan los dispositivos estándar de entrada, salida y error, respectivamente. Al exponer estos miembros como propiedades, la ConsoleStreams clase puede retrasar su inicialización hasta que se usen realmente. Por ejemplo, al hacer referencia primero a la Out propiedad , como en ConsoleStreams.Out.WriteLine("hello, world"), se inicializa el subyacente TextWriter para el dispositivo de salida. Pero si la aplicación no hace referencia a las In propiedades y Error , no se crean objetos para esos dispositivos.

Obtener declaraciones de descriptor de acceso

Un Get descriptor de acceso (captador) se declara mediante una declaración de propiedad Get . Una declaración de propiedad Get consta de la palabra clave Get seguida de un bloque de instrucciones. Dada una propiedad denominada P, una Get declaración de descriptor de acceso declara implícitamente un método con el nombre get_P con los mismos modificadores, tipo y lista de parámetros que la propiedad . Si el tipo contiene una declaración con ese nombre, se produce un error en tiempo de compilación, pero la declaración implícita se omite para los fines del enlace de nombres.

Una variable local especial, que se declara implícitamente en el Get espacio de declaración del cuerpo del descriptor de acceso con el mismo nombre que la propiedad, representa el valor devuelto de la propiedad. La variable local tiene semántica de resolución de nombres especial cuando se usa en expresiones. Si la variable local se usa en un contexto que espera una expresión clasificada como un grupo de métodos, como una expresión de invocación, el nombre se resuelve en la función en lugar de en la variable local. Por ejemplo:

ReadOnly Property F(i As Integer) As Integer
    Get
        If i = 0 Then
            F = 1    ' Sets the return value.
        Else
            F = F(i - 1) ' Recursive call.
        End If
    End Get
End Property

El uso de paréntesis puede provocar situaciones ambiguas (por F(1) ejemplo, donde F es una propiedad cuyo tipo es una matriz unidimensional). En todas las situaciones ambiguas, el nombre se resuelve en la propiedad en lugar de en la variable local. Por ejemplo:

ReadOnly Property F(i As Integer) As Integer()
    Get
        If i = 0 Then
            F = new Integer(2) { 1, 2, 3 }
        Else
            F = F(i - 1) ' Recursive call, not index.
        End If
    End Get
End Property

Cuando el flujo de control deja el cuerpo del Get descriptor de acceso, el valor de la variable local se devuelve a la expresión de invocación. Dado que invocar un Get descriptor de acceso es conceptualmente equivalente a leer el valor de una variable, se considera un estilo de programación incorrecto para Get que los descriptores de acceso tengan efectos secundarios observables, como se muestra en el ejemplo siguiente:

Class Counter
    Private Value As Integer

    Public ReadOnly Property NextValue() As Integer
        Get
            Value += 1
            Return Value
        End Get
    End Property
End Class

El valor de la NextValue propiedad depende del número de veces que se ha accedido a la propiedad anteriormente. Por lo tanto, el acceso a la propiedad produce un efecto secundario observable y la propiedad debe implementarse en su lugar como un método.

La convención "sin efectos secundarios" para Get los descriptores de acceso no significa que Get los descriptores de acceso siempre deben escribirse para devolver simplemente valores almacenados en variables. De hecho, Get los descriptores de acceso suelen calcular el valor de una propiedad accediendo a varias variables o invocando métodos. Sin embargo, un descriptor de acceso diseñado Get correctamente no realiza ninguna acción que provoque cambios observables en el estado del objeto.

Nota. Get Los descriptores de acceso tienen la misma restricción en la colocación de línea que tienen las subrutinas. La instrucción inicial, la instrucción end y el bloque deben aparecer al principio de una línea lógica.

PropertyGetDeclaration
    : Attributes? AccessModifier? 'Get' LineTerminator
      Block?
      'End' 'Get' StatementTerminator
    ;

Declaraciones de descriptor de acceso Set

Un Set descriptor de acceso (establecedor) se declara mediante una declaración de conjunto de propiedades. Una declaración de conjunto de propiedades consta de la palabra clave Set, una lista de parámetros opcional y un bloque de instrucciones. Dada una propiedad denominada P, una declaración establecedor declara implícitamente un método con el nombre set_P con los mismos modificadores y lista de parámetros que la propiedad . Si el tipo contiene una declaración con ese nombre, se produce un error en tiempo de compilación, pero la declaración implícita se omite para los fines del enlace de nombres.

Si se especifica una lista de parámetros, debe tener un miembro, ese miembro no debe tener modificadores excepto ByVal, y su tipo debe ser el mismo que el tipo de la propiedad. El parámetro representa el valor de propiedad que se va a establecer. Si se omite el parámetro , se declara implícitamente un parámetro denominado Value .

Nota. Set Los descriptores de acceso tienen la misma restricción en la colocación de línea que tienen las subrutinas. La instrucción inicial, la instrucción end y el bloque deben aparecer al principio de una línea lógica.

PropertySetDeclaration
    : Attributes? AccessModifier? 'Set'
      ( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
      Block?
      'End' 'Set' StatementTerminator
    ;

Propiedades predeterminadas

Una propiedad que especifica el modificador Default se denomina propiedad predeterminada. Cualquier tipo que permita propiedades puede tener una propiedad predeterminada, incluidas las interfaces. Se puede hacer referencia a la propiedad predeterminada sin tener que calificar la instancia con el nombre de la propiedad. Por lo tanto, dada una clase

Class Test
    Public Default ReadOnly Property Item(i As Integer) As Integer
        Get
            Return i
        End Get
    End Property
End Class

el código

Module TestModule
    Sub Main()
        Dim x As Test = New Test()
        Dim y As Integer

        y = x(10)
    End Sub
End Module

es equivalente a

Module TestModule
    Sub Main()
        Dim x As Test = New Test()
        Dim y As Integer

        y = x.Item(10)
    End Sub
End Module

Una vez declarada Defaultuna propiedad , todas las propiedades sobrecargadas en ese nombre en la jerarquía de herencia se convierten en la propiedad predeterminada, independientemente de si se han declarado Default o no. Declarar una propiedad Default en una clase derivada cuando la clase base declaró una propiedad predeterminada por otro nombre no requiere ningún otro modificador como Shadows o Overrides. Esto se debe a que la propiedad predeterminada no tiene identidad ni firma, por lo que no se puede sombrear ni sobrecargar. Por ejemplo:

Class Base
    Public ReadOnly Default Property Item(i As Integer) As Integer
        Get
            Console.WriteLine("Base = " & i)
        End Get
    End Property
End Class

Class Derived
    Inherits Base

    ' This hides Item, but does not change the default property.
    Public Shadows ReadOnly Property Item(i As Integer) As Integer
        Get
            Console.WriteLine("Derived = " & i)
        End Get
    End Property
End Class

Class MoreDerived
    Inherits Derived

    ' This declares a new default property, but not Item.
    ' This does not need to be declared Shadows
    Public ReadOnly Default Property Value(i As Integer) As Integer
        Get
            Console.WriteLine("MoreDerived = " & i)
        End Get
    End Property
End Class

Module Test
    Sub Main()
        Dim x As MoreDerived = New MoreDerived()
        Dim y As Integer
        Dim z As Derived = x

        y = x(10)        ' Calls MoreDerived.Value.
        y = x.Item(10)   ' Calls Derived.Item
        y = z(10)        ' Calls Base.Item
    End Sub
End Module

Este programa generará la salida:

MoreDerived = 10
Derived = 10
Base = 10

Todas las propiedades predeterminadas declaradas dentro de un tipo deben tener el mismo nombre y, para mayor claridad, deben especificar el Default modificador. Dado que una propiedad predeterminada sin parámetros de índice provocaría una situación ambigua al asignar instancias de la clase contenedora, las propiedades predeterminadas deben tener parámetros de índice. Además, si una propiedad sobrecargada en un nombre determinado incluye el Default modificador, todas las propiedades sobrecargadas en ese nombre deben especificarla. Es posible que las propiedades predeterminadas no sean Shared, y al menos un descriptor de acceso de la propiedad no debe ser Private.

Propiedades implementadas automáticamente

Si una propiedad omite la declaración de los descriptores de acceso, se proporcionará automáticamente una implementación de la propiedad a menos que la propiedad se declare en una interfaz o se declare MustOverride. Solo se pueden implementar automáticamente las propiedades de lectura y escritura sin argumentos; De lo contrario, se produce un error en tiempo de compilación.

Una propiedad ximplementada automáticamente, incluso una invalidando otra propiedad, introduce una variable _x local privada con el mismo tipo que la propiedad . Si hay una colisión entre el nombre de la variable local y otra declaración, se notificará un error en tiempo de compilación. El descriptor de acceso de Get la propiedad implementada automáticamente devuelve el valor del descriptor de acceso local y el descriptor de acceso de Set la propiedad que establece el valor de la propiedad local. Por ejemplo, la declaración:

Public Property x() As Integer

es aproximadamente equivalente a:

Private _x As Integer
Public Property x() As Integer
    Get
        Return _x
    End Get
    Set (value As Integer)
        _x = value
    End Set
End Property

Al igual que con las declaraciones de variables, una propiedad implementada automáticamente puede incluir un inicializador. Por ejemplo:

Public Property x() As Integer = 10
Public Shared Property y() As New Customer() With { .Name = "Bob" }

Nota. Cuando se inicializa una propiedad implementada automáticamente, se inicializa a través de la propiedad , no el campo subyacente. Esto es así que la invalidación de propiedades puede interceptar la inicialización si es necesario.

Los inicializadores de matriz se permiten en propiedades implementadas automáticamente, salvo que no hay ninguna manera de especificar explícitamente los límites de la matriz. Por ejemplo:

' Valid
Property x As Integer() = {1, 2, 3}
Property y As Integer(,) = {{1, 2, 3}, {12, 13, 14}, {11, 10, 9}}

' Invalid
Property x4(5) As Short

Propiedades del iterador

Una propiedad de iterador es una propiedad con el Iterator modificador . Se usa por la misma razón por la que se usa un método de iterador (Métodos de iterador de sección) como una manera cómoda de generar una secuencia, una que la instrucción puede consumir For Each . El Get descriptor de acceso de una propiedad de iterador se interpreta de la misma manera que un método de iterador.

Una propiedad de iterador debe tener un descriptor de acceso explícito Get y su tipo debe ser IEnumerator, o IEnumerable, o IEnumerator(Of T)IEnumerable(Of T) para algunos T.

Este es un ejemplo de una propiedad de iterador:

Class Family
    Property Daughters As New List(Of String) From {"Beth", "Diane"}
    Property Sons As New List(Of String) From {"Abe", "Carl"}

    ReadOnly Iterator Property Children As IEnumerable(Of String)
        Get
            For Each name In Daughters : Yield name : Next
            For Each name In Sons : Yield name : Next
        End Get
    End Property
End Class

Module Module1
    Sub Main()
        Dim x As New Family
        For Each c In x.Children
            Console.WriteLine(c) ' prints Beth, Diane, Abe, Carl
        Next
    End Sub
End Module

Operadores

Los operadores son métodos que definen el significado de un operador de Visual Basic existente para la clase contenedora. Cuando el operador se aplica a la clase en una expresión, el operador se compila en una llamada al método de operador definido en la clase . Definir un operador para una clase también se conoce como sobrecargar el operador.

OperatorDeclaration
    : Attributes? OperatorModifier* 'Operator' OverloadableOperator
      OpenParenthesis ParameterList CloseParenthesis
      ( 'As' Attributes? TypeName )? LineTerminator
      Block?
      'End' 'Operator' StatementTerminator
    ;

OperatorModifier
    : 'Public' | 'Shared' | 'Overloads' | 'Shadows' | 'Widening' | 'Narrowing'
    ;

OverloadableOperator
    : '+' | '-' | '*' | '/' | '\\' | '&' | 'Like' | 'Mod' | 'And' | 'Or' | 'Xor'
    | '^' | '<' '<' | '>' '>' | '=' | '<' '>' | '>' | '<' | '>' '=' | '<' '='
    | 'Not' | 'IsTrue' | 'IsFalse' | 'CType'
    ;

No es posible sobrecargar un operador que ya existe; en la práctica, esto se aplica principalmente a los operadores de conversión. Por ejemplo, no es posible sobrecargar la conversión de una clase derivada a una clase base:

Class Base
End Class

Class Derived
    ' Cannot redefine conversion from Derived to Base,
    ' conversion will be ignored.
    Public Shared Widening Operator CType(s As Derived) As Base
        ...
    End Operator
End Class

Los operadores también se pueden sobrecargar en el sentido común de la palabra:

Class Base
    Public Shared Widening Operator CType(b As Base) As Integer
        ...
    End Operator

    Public Shared Narrowing Operator CType(i As Integer) As Base
        ...
    End Operator
End Class

Las declaraciones de operador no agregan explícitamente nombres al espacio de declaración del tipo contenedor; sin embargo, declaran implícitamente un método correspondiente a partir de los caracteres "op_". En las secciones siguientes se enumeran los nombres de método correspondientes con cada operador.

Hay tres clases de operadores que se pueden definir: operadores unarios, operadores binarios y operadores de conversión. Todas las declaraciones de operador comparten ciertas restricciones:

  • Las declaraciones de operador siempre deben ser Public y Shared. El Public modificador se puede omitir en contextos donde se asume el modificador.

  • Los parámetros de un operador no se pueden declarar ByRefcomo o OptionalParamArray.

  • El tipo de al menos uno de los operandos o el valor devuelto debe ser el tipo que contiene el operador .

  • No hay ninguna variable de devolución de función definida para los operadores. Por lo tanto, la Return instrucción debe usarse para devolver valores de un cuerpo de operador.

La única excepción a estas restricciones se aplica a los tipos de valor que aceptan valores NULL. Dado que los tipos de valor que aceptan valores NULL no tienen una definición de tipo real, un tipo de valor puede declarar operadores definidos por el usuario para la versión que acepta valores NULL del tipo. Al determinar si un tipo puede declarar un operador determinado definido por el usuario, los ? modificadores se quitan primero de todos los tipos implicados en la declaración con fines de comprobación de validez. Esta relajación no se aplica al tipo de valor devuelto de los IsTrue operadores y IsFalse ; todavía deben devolver Boolean, no Boolean?.

Una declaración de operador no puede modificar la precedencia y la asociatividad de un operador.

Nota. Los operadores tienen la misma restricción en la colocación de línea que tienen las subrutinas. La instrucción inicial, la instrucción end y el bloque deben aparecer al principio de una línea lógica.

Operadores unarios

Se pueden sobrecargar los siguientes operadores unarios:

  • Operador unario más + (método correspondiente: op_UnaryPlus)

  • Operador unario menos - (método correspondiente: op_UnaryNegation)

  • Operador lógico Not (método correspondiente: op_OnesComplement)

  • Los IsTrue operadores y IsFalse (métodos correspondientes: op_True, op_False)

Todos los operadores unarios sobrecargados deben tomar un único parámetro del tipo contenedor y pueden devolver cualquier tipo, excepto para IsTrue y IsFalse, que deben devolver Boolean. Si el tipo contenedor es un tipo genérico, los parámetros de tipo deben coincidir con los parámetros de tipo del tipo contenedor. Por ejemplo

Structure Complex
    ...

    Public Shared Operator +(v As Complex) As Complex
        Return v
    End Operator
End Structure

Si un tipo sobrecarga uno de IsTrue o IsFalse, también debe sobrecargar al otro. Si solo se sobrecarga uno, se produce un error en tiempo de compilación.

Nota. IsTrue y IsFalse no son palabras reservadas.

Operadores binarios

Se pueden sobrecargar los siguientes operadores binarios:

  • Los operadores de suma , resta -, multiplicación *, división /, división \integral , módulo Mod y exponente ^ (método correspondiente: op_Addition, op_Subtraction, op_Divisionop_Multiply, op_IntegerDivision, , , ) op_Exponentop_Modulus+

  • Los operadores =relacionales , <>, <, >, >=<=(métodos correspondientes: op_Equality, op_Inequalityop_LessThan, , op_GreaterThan, , ). op_GreaterThanOrEqualop_LessThanOrEqual Nota. Aunque el operador de igualdad se puede sobrecargar, no se puede sobrecargar el operador de asignación (solo se usa en instrucciones de asignación).

  • Operador Like (método correspondiente: op_Like)

  • Operador de concatenación & (método correspondiente: op_Concatenate)

  • Los operadores lógicos And, Or y Xor (métodos correspondientes: op_BitwiseAnd, op_BitwiseOr, op_ExclusiveOr)

  • Operadores de desplazamiento << y >> (métodos correspondientes: op_LeftShift, op_RightShift)

Todos los operadores binarios sobrecargados deben tomar el tipo contenedor como uno de los parámetros. Si el tipo contenedor es un tipo genérico, los parámetros de tipo deben coincidir con los parámetros de tipo del tipo contenedor. Los operadores de desplazamiento restringen aún más esta regla para exigir que el primer parámetro sea del tipo contenedor; el segundo parámetro siempre debe ser de tipo Integer.

Los operadores binarios siguientes deben declararse en pares:

  • Operador = y operador <>

  • Operador > y operador <

  • Operador >= y operador <=

Si se declara uno de los pares, el otro también debe declararse con los tipos de parámetro y valor devuelto coincidentes, o se producirá un error en tiempo de compilación. (Nota. El propósito de requerir declaraciones emparejadas de operadores relacionales es intentar y garantizar al menos un nivel mínimo de coherencia lógica en operadores sobrecargados).

A diferencia de los operadores relacionales, no se recomienda encarecidamente sobrecargar los operadores de división e división integral, aunque no es un error. (Nota. En general, los dos tipos de división deben ser completamente distintos: un tipo que admite divisiones es entero (en cuyo caso debe admitir \) o no (en cuyo caso debe admitir /). Consideramos que se produce un error para definir ambos operadores, pero porque sus lenguajes no distinguen generalmente entre dos tipos de división de la manera que hace Visual Basic, consideramos que era más seguro permitir la práctica, pero no lo desaconsejaban.

Los operadores de asignación compuesta no se pueden sobrecargar directamente. En su lugar, cuando se sobrecarga el operador binario correspondiente, el operador de asignación compuesto usará el operador sobrecargado. Por ejemplo:

Structure Complex
    ...

    Public Shared Operator +(x As Complex, y As Complex) _
        As Complex
        ...
    End Operator
End Structure

Module Test
    Sub Main()
        Dim c1, c2 As Complex
        ' Calls the overloaded + operator
        c1 += c2
    End Sub
End Module

Operadores de conversión

Los operadores de conversión definen nuevas conversiones entre tipos. Estas nuevas conversiones se denominan conversiones definidas por el usuario. Un operador de conversión convierte de un tipo de origen, indicado por el tipo de parámetro del operador de conversión, a un tipo de destino, indicado por el tipo de valor devuelto del operador de conversión. Las conversiones deben clasificarse como ampliación o restricción. Una declaración del operador de conversión que incluye la palabra clave introduce una conversión de ampliación definida por el Widening usuario (método correspondiente: op_Implicit). Una declaración del operador de conversión que incluye la palabra clave presenta una conversión de restricción definida por el Narrowing usuario (método correspondiente: op_Explicit).

En general, las conversiones de ampliación definidas por el usuario deben diseñarse para no producir excepciones y nunca perder información. Si una conversión definida por el usuario puede provocar excepciones (por ejemplo, porque el argumento de origen está fuera del intervalo) o la pérdida de información (como descartar bits de orden alto), esa conversión debe definirse como una conversión de restricción. En el ejemplo:

Structure Digit
    Dim value As Byte

    Public Sub New(value As Byte)
        if value < 0 OrElse value > 9 Then Throw New ArgumentException()
        Me.value = value
    End Sub

    Public Shared Widening Operator CType(d As Digit) As Byte
        Return d.value
    End Operator

    Public Shared Narrowing Operator CType(b As Byte) As Digit
        Return New Digit(b)
    End Operator
End Structure

la conversión de a DigitByte es una conversión de ampliación porque nunca produce excepciones o pierde información, pero la conversión de a ByteDigit es una conversión Digit de restricción, ya que solo puede representar un subconjunto de los valores posibles de .Byte

A diferencia de todos los demás miembros de tipo que se pueden sobrecargar, la firma de un operador de conversión incluye el tipo de destino de la conversión. Este es el único miembro de tipo para el que el tipo de valor devuelto participa en la firma. Sin embargo, la clasificación de ampliación o restricción de un operador de conversión no forma parte de la firma del operador. Por lo tanto, una clase o estructura no puede declarar un operador de conversión de ampliación y un operador de conversión de restricción con los mismos tipos de origen y destino.

Un operador de conversión definido por el usuario debe convertir en o desde el tipo contenedor; por ejemplo, es posible que una clase C defina una conversión de C a y de Integer a CInteger , pero no de Integer a Boolean. Si el tipo contenedor es un tipo genérico, los parámetros de tipo deben coincidir con los parámetros de tipo del tipo contenedor. Además, no es posible redefinir una conversión intrínseca (es decir, no definida por el usuario). Como resultado, un tipo no puede declarar una conversión donde:

  • El tipo de origen y el tipo de destino son los mismos.

  • Tanto el tipo de origen como el tipo de destino no son el tipo que define el operador de conversión.

  • El tipo de origen o el tipo de destino es un tipo de interfaz.

  • El tipo de origen y los tipos de destino están relacionados con la herencia (incluido Object).

La única excepción a estas reglas se aplica a los tipos de valor que aceptan valores NULL. Dado que los tipos de valor que aceptan valores NULL no tienen una definición de tipo real, un tipo de valor puede declarar conversiones definidas por el usuario para la versión que acepta valores NULL del tipo. Al determinar si un tipo puede declarar una conversión determinada definida por el usuario, los ? modificadores se quitan primero de todos los tipos implicados en la declaración con fines de comprobación de validez. Por lo tanto, la siguiente declaración es válida porque S puede definir una conversión de S a T:

Structure T
    ...
End Structure

Structure S
    Public Shared Widening Operator CType(ByVal v As S?) As T
    ...
    End Operator
End Structure

Sin embargo, la siguiente declaración no es válida porque la estructura S no puede definir una conversión de S a S:

Structure S
    Public Shared Widening Operator CType(ByVal v As S) As S?
        ...
    End Operator
End Structure

Asignación de operadores

Dado que es posible que el conjunto de operadores que admite Visual Basic no coincida exactamente con el conjunto de operadores que otros lenguajes de .NET Framework, algunos operadores se asignan especialmente a otros operadores cuando se definen o usan. Concretamente:

  • Al definir un operador de división integral, se definirá automáticamente un operador de división normal (que solo se puede usar desde otros lenguajes) que llamará al operador de división integral.

  • La sobrecarga de los Notoperadores , Andy Or sobrecargará solo el operador bit a bit desde la perspectiva de otros lenguajes que distinguen entre operadores lógicos y bit a bit.

  • Una clase que sobrecarga solo los operadores lógicos en un lenguaje que distingue entre operadores lógicos y bit a bit (es decir, un lenguaje que usa op_LogicalNot, op_LogicalAndy op_LogicalOr para Not, Andy , respectivamente Or) tendrá sus operadores lógicos asignados a los operadores lógicos de Visual Basic. Si se sobrecargan los operadores lógicos y bit a bit, solo se usarán los operadores bit a bit.

  • La sobrecarga de los << operadores y >> sobrecargará solo los operadores firmados desde la perspectiva de otros lenguajes que distinguen entre los operadores de desplazamiento firmados y sin signo.

  • Una clase que sobrecarga solo un operador de desplazamiento sin signo tendrá asignado el operador de desplazamiento sin signo al operador de desplazamiento de Visual Basic correspondiente. Si se sobrecarga un operador de desplazamiento sin signo y firmado, solo se usará el operador de desplazamiento firmado.