Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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:
OverridableyNotOverridableson mutuamente excluyentes y no se pueden combinar.MustOverrideimplicaOverridable(y, por tanto, no puede especificarlo) y no se puede combinar conNotOverridable.NotOverridableno se puede combinar conOverridableniMustOverridey debe combinarse conOverrides.OverridesimplicaOverridable(y, por tanto, no puede especificarlo) y no se puede combinar conMustOverride.
También hay restricciones adicionales en los métodos reemplazables:
Un
MustOverridemétodo puede no incluir un cuerpo de método o unaEndconstrucción, no invalidar otro método y solo puede aparecer enMustInheritclases.Si un método especifica
Overridesy 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 especifiqueShadows.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 Friendmétodo en otro ensamblado que no tieneFriendacceso debe especificarProtected(noProtected Friend).PrivateLos métodos no pueden serOverridable,NotOverridableoMustOverride, ni pueden invalidar otros métodos.Es posible que los métodos de
NotInheritablelas clases no se declarenOverridableoMustOverride.
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
ParamArraypuede ser una expresión única de un tipo que amplía alParamArraytipo. En este caso, actúaParamArrayexactamente 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 deParamArray. En este caso, la invocación crea una instancia delParamArraytipo 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
WithEventsmodificador o laMyBasepalabra clave oMeMyClass; 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
Privatede instancia con tipo delegado, denominadaXEvent.Dos métodos denominados
add_Xyremove_Xque 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 laAddHandlerdeclaración.Método denominado
remove_X, correspondiente a laRemoveHandlerdeclaración.Método denominado
fire_X, correspondiente a laRaiseEventdeclaració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)oSystem.TypedEventHandle(Of T, U), y no permitirán a otros usuarios.El
XEventcampo tiene el tipoSystem.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T)dondeTes 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
Mede oMyClass.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
Getdescriptor de acceso y unSetdescriptor de acceso. Se dice que la propiedad es una propiedad de lectura y escritura.Si la propiedad especifica el
ReadOnlymodificador, la propiedad debe tener unGetdescriptor de acceso y puede que no tenga unSetdescriptor 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
WriteOnlymodificador, la propiedad debe tener unSetdescriptor de acceso y puede que no tenga unGetdescriptor 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:
Privatees más restrictivo quePublic,Protected Friend,ProtectedoFriend.Friendes más restrictivo queProtected FriendoPublic.Protectedes más restrictivo queProtected FriendoPublic.Protected Friendes más restrictivo quePublic.
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
PublicyShared. ElPublicmodificador se puede omitir en contextos donde se asume el modificador.Los parámetros de un operador no se pueden declarar
ByRefcomo oOptionalParamArray.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
Returninstrucció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
IsTrueoperadores yIsFalse(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óduloMody 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_LessThanOrEqualNota. 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,OryXor(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 ,AndyOrsobrecargará 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_LogicalAndyop_LogicalOrparaNot,Andy , respectivamenteOr) 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.
Visual Basic language spec