Especificación Del Lenguaje de Visual Basic
Especificación Del Lenguaje de Visual Basic
Especificación Del Lenguaje de Visual Basic
La especificación del lenguaje Visual Basic es la fuente autorizada para responder a todas las preguntas sobre la
gramática y la sintaxis de Visual Basic. Contiene información detallada sobre el lenguaje, incluidos numerosos
puntos que no se tratan en la documentación de referencia de Visual Basic.
La especificación está disponible en el Centro de descarga de Microsoft.
Este sitio contiene la especificación de Visual Basic 11. Se crea a partir de los archivos Markdown del repositorio
de GitHub dotnet/vblang.
Los problemas de la especificación deben crearse en el repositorio dotnet/vblang. O bien, si le interesa corregir
los errores que detecte, puede enviar una solicitud de incorporación de cambios al mismo repositorio.
Vea también
Referencia del lenguaje Visual Basic
S IG U IE N TE
Introducción
15/11/2021 • 17 minutes to read
Notación gramatical
Esta especificación describe dos gramáticas: una gramática léxica y una gramática sintáctica. La gramática léxica
define cómo se pueden combinar caracteres para formar tokens. la gramática sintáctica define cómo se pueden
combinar los tokens para formar Visual Basic programas. También hay varias gramáticas secundarias que se
usan para las operaciones de preprocesamiento como la compilación condicional.
Las gramáticas de esta especificación se escriben en formato ANTLR (vea) http://www.antlr.org/ .
El caso no es importante en Visual Basic programas. Para simplificar, todos los terminales se proporcionarán en
mayúsculas y minúsculas estándar, pero el uso de mayúsculas y minúsculas lo hará. Los terminales que son
elementos que se pueden imprimir del juego de caracteres ASCII se representan mediante sus correspondientes
caracteres ASCII. Visual Basic tampoco distingue el ancho al coincidir con los terminales, lo que permite que los
caracteres Unicode de ancho completo coincidan con sus equivalentes Unicode de ancho medio, pero solo en un
token completo. Un token no coincidirá si contiene caracteres de ancho medio y de ancho completo
combinados.
Los saltos de línea y la sangría se pueden agregar para mejorar la legibilidad y no forman parte de la
producción.
Compatibilidad
Una característica importante de un lenguaje de programación es la compatibilidad entre las distintas versiones
del lenguaje. Si una versión más reciente de un lenguaje no acepta el mismo código que una versión anterior
del lenguaje o lo interpreta de forma distinta a la versión anterior, se puede colocar una carga en un
programador al actualizar su código de una versión del lenguaje a otra. Como tal, se debe conservar la
compatibilidad entre las versiones excepto cuando la ventaja de los consumidores del lenguaje sea una
naturaleza clara y abrumadora.
La siguiente directiva rige los cambios en el idioma Visual Basic entre las versiones. El término lenguaje, cuando
se utiliza en este contexto, solo se refiere a los aspectos sintácticos y semánticos del propio lenguaje Visual Basic
y no incluye ninguna clase .NET Framework incluida como parte del Microsoft.VisualBasic espacio de nombres
(y los subespacios de nombres). Todas las clases del .NET Framework están tratadas por una directiva de control
de versiones y compatibilidad independiente fuera del ámbito de este documento.
Tipos de interrupciones de compatibilidad
En un mundo ideal, la compatibilidad sería del 100% entre la versión existente de Visual Basic y todas las
versiones futuras de Visual Basic. Sin embargo, puede haber situaciones en las que la necesidad de una
interrupción de compatibilidad puede superar el costo que puede imponer a los programadores. Estas
situaciones son:
Nuevas advertencias. La introducción de una nueva advertencia no es, por se trata de una interrupción de
compatibilidad. Sin embargo, dado que muchos desarrolladores compilan con "tratar advertencias como
errores" activado, se debe prestar atención adicional al introducir advertencias.
Nuevas palabras clave. Es posible que sea necesario introducir nuevas palabras clave al introducir nuevas
características de lenguaje. Se realizarán esfuerzos razonables para elegir palabras clave que minimicen
la posibilidad de colisionar con los identificadores de los usuarios y usar las palabras clave existentes
donde tenga sentido. Se proporcionará ayuda para actualizar los proyectos de versiones anteriores y
escapar cualquier palabra clave nueva.
Errores del compilador. Cuando el comportamiento del compilador es probable con un comportamiento
documentado en la especificación del lenguaje, puede ser necesario corregir el comportamiento del
compilador para que coincida con el comportamiento documentado.
Error de especificación. Cuando el compilador es coherente con la especificación del lenguaje pero la
especificación del lenguaje es claramente incorrecta, puede ser necesario cambiar la especificación del
lenguaje y el comportamiento del compilador. La frase "claramente mal" significa que el comportamiento
documentado se ejecuta en el contador de lo que cabría esperar una mayoría clara e inequívoca de los
usuarios y produce un comportamiento muy no deseado para los usuarios.
Ambigüedad de la especificación. Cuando la especificación del lenguaje debe deletrear lo que sucede en
una situación concreta pero no, y el compilador controla la situación de una manera incoherente o
incorrecta (con la misma definición del punto anterior), puede ser necesario aclarar la especificación y
corregir el comportamiento del compilador. En otras palabras, cuando la especificación cubre los casos a,
b, d y e, pero omite cualquier mención de lo que ocurre en el caso de c y el compilador se comporta
incorrectamente en el caso de c, puede que sea necesario documentar lo que sucede en el caso de c y
cambiar el comportamiento del compilador para que coincida. (Tenga en cuenta que si la especificación
era ambigua en lo que ocurre en una situación y el compilador se comporta de una manera que no es
claramente equivocada, el comportamiento del compilador se convierte en la especificación de facto).
Realizar errores en tiempo de ejecución en errores en tiempo de compilación. En una situación en la que
el código tiene garantizado un error del 100% en tiempo de ejecución (es decir, el código de usuario tiene
un error inequívoco en él), puede ser conveniente agregar un error en tiempo de compilación que detecte
la situación.
Omisión de especificación. Cuando la especificación del lenguaje no habilita o deshabilita
específicamente una situación concreta y el compilador controla la situación de una manera no deseada
(si el comportamiento del compilador fuera incorrecto, sería un error de especificación, no una omisión
de especificación), puede ser necesario aclarar la especificación y cambiar el comportamiento del
compilador. Además del análisis de impacto habitual, los cambios de este tipo se restringen aún más a los
casos en los que se considera que el impacto del cambio es extremadamente mínimo y la ventaja para los
desarrolladores es muy alta.
Nuevas características. En general, la introducción de nuevas características no debe cambiar las partes
existentes de la especificación del lenguaje ni el comportamiento existente del compilador. En la situación
en la que la introducción de una nueva característica requiere cambiar la especificación de lenguaje
existente, este tipo de interrupción de compatibilidad solo es razonable si el impacto sería
extremadamente mínimo y el beneficio de la característica es alto.
Seguridad. En situaciones extraordinarias, los problemas de seguridad pueden requerir una interrupción
de compatibilidad, como quitar o modificar una característica que es inherentemente insegura y plantea
un riesgo de seguridad claro para los usuarios.
Las siguientes situaciones no son razones aceptables para introducir interrupciones de compatibilidad:
Comportamiento no deseado o regrettable. El diseño del lenguaje o el comportamiento del compilador,
que es razonable pero se considera no deseado o regrettable en Retrospect no es una justificación para la
compatibilidad con versiones anteriores. En su lugar, se debe usar el proceso de desuso del lenguaje, que
se describe a continuación.
Algo más. De lo contrario, el comportamiento del compilador permanece compatible con versiones
anteriores.
Criterios de impacto
A la hora de considerar si un salto de compatibilidad puede ser aceptable, se usan varios criterios para
determinar cuál podría ser el impacto del cambio. Cuanto mayor sea el impacto, mayor será la barra para
aceptar los saltos de compatibilidad.
Los criterios son los siguientes:
¿Cuál es el ámbito del cambio? En otras palabras, ¿Cuántos programas se verán afectados? ¿Cuántos
usuarios se ven afectados? ¿Con qué frecuencia se escribirá el código que se ve afectado por el cambio?
¿Hay alguna solución alternativa para obtener el mismo comportamiento antes del cambio?
¿Es obvio el cambio? ¿Los usuarios obtendrán comentarios inmediatos de que algo ha cambiado o sus
programas se ejecutarán de manera diferente?
¿Se puede resolver el cambio razonablemente durante la actualización? ¿Es posible escribir una
herramienta que pueda encontrar la situación en la que se produce el cambio con una precisión perfecta
y cambiar el código para evitar el cambio?
¿Cuáles son los comentarios de la comunidad sobre el cambio?
Desuso de idioma
Con el tiempo, es posible que las partes del lenguaje o del compilador dejen de estar en desuso. Como se
explicó anteriormente, no es aceptable interrumpir la compatibilidad para quitar dichas características en
desuso. En su lugar, deben seguirse los siguientes pasos:
1. Dada una característica que existe en la versión a de Visual Studio, los comentarios deben ser solicitados
por la comunidad de usuarios en desuso de la característica y aviso completo antes de que se realice
cualquier decisión de desuso final. El proceso de desuso se puede invertir o abandonar en cualquier
momento en función de los comentarios de la comunidad de usuarios.
2. la versión completa (es decir, no una versión de punto) B de Visual Studio se debe liberar con
advertencias del compilador que avisen del uso en desuso. Las advertencias deben estar activas de forma
predeterminada y se pueden desactivar. Los desuso deben estar claramente documentados en la
documentación del producto y en la Web.
3. Se debe liberar una versión completa de Visual Studio con advertencias del compilador que no se pueden
desactivar.
4. Posteriormente, se debe publicar una versión completa D de Visual Studio con las advertencias del
compilador en desuso convertidas en errores del compilador. La versión de D debe producirse después
del final de la fase de soporte técnico estándar (5 años a partir de esta redacción) de la versión A.
5. Por último, se puede liberar una versión E de Visual Studio que quita los errores del compilador.
No se permitirán los cambios que no se pueden controlar dentro de este marco de desuso.
Gramática léxica
15/11/2021 • 24 minutes to read
La compilación de un programa Visual Basic primero implica la traducción del flujo sin formato de caracteres
Unicode en un conjunto ordenado de tokens léxicos. Dado que el lenguaje Visual Basic no es un formato libre, el
conjunto de tokens se divide posteriormente en una serie de líneas lógicas. Una línea lógica abarca desde el
inicio de la secuencia o un terminador de línea hasta el siguiente terminador de línea que no está precedido por
una continuación de línea o hasta el final de la secuencia.
Nota. Con la introducción de las expresiones literales XML en la versión 9,0 del lenguaje, Visual Basic ya no
tiene una gramática léxica distinta en el sentido de que Visual Basic código se pueda acortar sin tener en cuenta
el contexto sintáctico. Esto se debe al hecho de que XML y Visual Basic tienen reglas léxicas diferentes y el
conjunto de reglas léxicas que se usan en un momento determinado depende de qué construcción sintáctica se
está procesando en ese momento. Esta especificación conserva esta sección de gramática léxica como guía de
las reglas léxicas de código de Visual Basic normal.
LogicalLineStart
: LogicalLine*
;
LogicalLine
: LogicalLineElement* Comment? LineTerminator
;
LogicalLineElement
: WhiteSpace
| LineContinuation
| Token
;
Token
: Identifier
| Keyword
| Literal
| Separator
| Operator
;
Caracteres y líneas
Visual Basic programas se componen de caracteres del juego de caracteres Unicode.
Character:
'<Any Unicode character except a LineTerminator>'
;
Terminadores de línea
Los caracteres de salto de línea Unicode separan líneas lógicas.
LineTerminator
: '<Unicode 0x00D>'
| '<Unicode 0x00A>'
| '<CR>'
| '<LF>'
| '<Unicode 0x2028>'
| '<Unicode 0x2029>'
;
Continuación de línea
Una continuación de línea consiste en al menos un carácter de espacio en blanco que precede inmediatamente a
un carácter de subrayado único como el último carácter (excepto el espacio en blanco) en una línea de texto.
Una continuación de línea permite a una línea lógica abarcar más de una línea física. Las continuaciones de línea
se tratan como si fueran un espacio en blanco, aunque no lo sean.
LineContinuation
: WhiteSpace '_' WhiteSpace* LineTerminator
;
Module Test
Sub Print( _
Param1 As Integer, _
Param2 As Integer )
Algunos lugares de la gramática sintáctica permiten continuaciones de línea implícitas. Cuando se encuentra un
terminador de línea
después de una coma ( , ), abrir paréntesis ( ( ), abrir llave ( { ) o abrir expresión insertada ( <%= )
después de un calificador de miembro ( . o .@ o ... ), siempre que se califique algo (es decir, no esté
usando un With contexto implícito)
antes de un paréntesis de cierre ( ) ), cierre de llave ( } ) o cierre la expresión incrustada ( %> )
después de un menor que ( < ) en un contexto de atributo
antes de un signo mayor que ( > ) en un contexto de atributo
después de un mayor que ( > ) en un contexto de atributo que no es de nivel de archivo
operadores de consulta Before y After ( Where , Order , Select , etc.)
después de los operadores binarios ( + , - , / , * , etc.) en un contexto de expresión
después = de los operadores de asignación (, := , += , -= , etc.) en cualquier contexto.
el terminador de línea se trata como si fuera una continuación de línea.
Comma
: ',' LineTerminator?
;
Period
: '.' LineTerminator?
;
OpenParenthesis
: '(' LineTerminator?
;
CloseParenthesis
: LineTerminator? ')'
;
OpenCurlyBrace
: '{' LineTerminator?
;
CloseCurlyBrace
: LineTerminator? '}'
;
Equals
: '=' LineTerminator?
;
ColonEquals
: ':' '=' LineTerminator?
;
Module Test
Sub Print(
Param1 As Integer,
Param2 As Integer)
Las continuaciones de línea implícitas nunca se deducirán directamente antes o después del token especificado.
No se deducirán antes o después de una continuación de línea. Por ejemplo:
Dim y = 10
' Error: Expression expected for assignment to x
Dim x = _
Las continuaciones de línea no se deducirán en contextos de compilación condicionales. (Nota. Esta última
restricción es necesaria porque el texto de los bloques de compilación condicional que no están compilados no
tiene que ser válido sintácticamente. Por lo tanto, la instrucción de compilación condicional podría "recoger" de
forma accidental el texto del bloque, especialmente cuando el lenguaje se extienda en el futuro.)
Espacio en blanco
Los espacios en blanco solo sirven para separar los tokens y, de lo contrario, se omiten. Las líneas lógicas que
contienen solo espacios en blanco se omiten. (Nota. Los terminadores de línea no se consideran espacios en
blanco.
WhiteSpace
: '<Unicode class Zs>'
| '<Unicode Tab 0x0009>'
;
Comentarios
Un Comentario comienza con un carácter de comilla simple o la palabra clave REM . Un carácter de comilla
simple es un carácter de comilla simple ASCII, un carácter de comilla simple izquierda Unicode o un carácter de
comilla simple a la derecha Unicode. Los comentarios pueden empezar en cualquier parte de una línea de
código fuente y el final de la línea física finaliza el comentario. El compilador omite los caracteres situados entre
el principio del comentario y el terminador de línea. Por lo tanto, los comentarios no pueden extenderse a través
de varias líneas mediante continuaciones de línea.
Comment
: CommentMarker Character*
;
CommentMarker
: SingleQuoteCharacter
| 'REM'
;
SingleQuoteCharacter
: '\''
| '<Unicode 0x2018>'
| '<Unicode 0x2019>'
;
Identificadores
Un identificador es un nombre. Visual Basic identificadores se ajustan al Anexo 15 del estándar Unicode con una
excepción: los identificadores pueden comenzar con un carácter de subrayado (conector). Si un identificador
comienza con un carácter de subrayado, debe contener al menos otro carácter de identificador válido para
eliminar la ambigüedad de una continuación de línea.
Identifier
: NonEscapedIdentifier TypeCharacter?
| Keyword TypeCharacter
| EscapedIdentifier
;
NonEscapedIdentifier
: '<Any IdentifierName but not Keyword>'
;
EscapedIdentifier
: '[' IdentifierName ']'
;
IdentifierName
: IdentifierStart IdentifierCharacter*
;
IdentifierStart
: AlphaCharacter
| UnderscoreCharacter IdentifierCharacter
;
IdentifierCharacter
: UnderscoreCharacter
| AlphaCharacter
| NumericCharacter
| CombiningCharacter
| FormattingCharacter
;
AlphaCharacter
: '<Unicode classes Lu,Ll,Lt,Lm,Lo,Nl>'
;
NumericCharacter
: '<Unicode decimal digit class Nd>'
;
CombiningCharacter
: '<Unicode combining character classes Mn, Mc>'
;
FormattingCharacter
: '<Unicode formatting character class Cf>'
;
UnderscoreCharacter
: '<Unicode connection character class Pc>'
;
IdentifierOrKeyword
: Identifier
| Keyword
;
Los identificadores normales no pueden coincidir con palabras clave, pero los identificadores de escape o los
identificadores con un carácter de tipo pueden. Un identificador de escape es un identificador delimitado por
corchetes. Los identificadores de escape siguen las mismas reglas que los identificadores normales, salvo que
pueden coincidir con palabras clave y no pueden tener caracteres de tipo.
En este ejemplo se define una clase denominada class con un método compartido denominado shared que
toma un parámetro denominado boolean y, a continuación, llama al método.
Class [class]
Shared Sub [shared]([boolean] As Boolean)
If [boolean] Then
Console.WriteLine("true")
Else
Console.WriteLine("false")
End If
End Sub
End Class
Module [module]
Sub Main()
[class].[shared](True)
End Sub
End Module
Los identificadores no distinguen mayúsculas de minúsculas, por lo que se considera que dos identificadores
son el mismo identificador si solo difieren en mayúsculas y minúsculas. (Nota. Las asignaciones de mayúsculas
y minúsculas estándar Unicode se usan cuando se comparan los identificadores y se omiten las asignaciones de
casos específicas de la configuración regional.
Caracteres de tipo
Un carácter de tipo denota el tipo del identificador anterior. El carácter de tipo no se considera parte del
identificador.
TypeCharacter
: IntegerTypeCharacter
| LongTypeCharacter
| DecimalTypeCharacter
| SingleTypeCharacter
| DoubleTypeCharacter
| StringTypeCharacter
;
IntegerTypeCharacter
: '%'
;
LongTypeCharacter
: '&'
;
DecimalTypeCharacter
: '@'
;
SingleTypeCharacter
: '!'
;
DoubleTypeCharacter
: '#'
;
StringTypeCharacter
: '$'
;
Si una declaración incluye un carácter de tipo, el carácter de tipo debe coincidir con el tipo especificado en la
propia declaración; de lo contrario, se produce un error en tiempo de compilación. Si la declaración omite el tipo
(por ejemplo, si no especifica una As cláusula), el carácter de tipo se sustituye implícitamente como el tipo de la
declaración.
No puede haber ningún espacio en blanco entre un identificador y su carácter de tipo. No hay caracteres de tipo
para Byte , SByte , UShort , Short UInteger o ULong , debido a la falta de caracteres adecuados.
Si se anexa un carácter de tipo a un identificador que conceptualmente no tiene un tipo (por ejemplo, un
nombre de espacio de nombres) o a un identificador cuyo tipo no acepta el tipo de carácter de tipo, se produce
un error en tiempo de compilación.
En el ejemplo siguiente se muestra el uso de caracteres de tipo:
' The follow line will cause an error: standard modules have no type.
Module Test1#
End Module
Module Test2
' The following line causes an error because the type character
' conflicts with the declared type of Func and Param.
Func# = CStr(Param@)
El carácter de tipo ! presenta un problema especial en que se puede usar como un carácter de tipo y como
separador en el idioma. Para eliminar la ambigüedad, un ! carácter es un carácter de tipo siempre que el
carácter que le sigue no pueda iniciar un identificador. Si es posible, el ! carácter es un separador, no un
carácter de tipo.
Palabras clave
Una palabra clave es una palabra que tiene un significado especial en una construcción de lenguaje. Todas las
palabras clave están reservadas por el lenguaje y no se pueden usar como identificadores a menos que los
identificadores se escapen. (Nota. EndIf , GoSub , Let , Variant y Wend se conservan como palabras clave,
aunque ya no se usan en Visual Basic).
Keyword
: 'AddHandler' | 'AddressOf' | 'Alias' | 'And'
| 'AndAlso' | 'As' | 'Boolean' | 'ByRef'
| 'Byte' | 'ByVal' | 'Call' | 'Case'
| 'Catch' | 'CBool' | 'CByte' | 'CChar'
| 'CDate' | 'CDbl' | 'CDec' | 'Char'
| 'CInt' | 'Class' | 'CLng' | 'CObj'
| 'Const' | 'Continue' | 'CSByte' | 'CShort'
| 'CSng' | 'CStr' | 'CType' | 'CUInt'
| 'CULng' | 'CUShort' | 'Date' | 'Decimal'
| 'Declare' | 'Default' | 'Delegate' | 'Dim'
| 'DirectCast' | 'Do' | 'Double' | 'Each'
| 'Else' | 'ElseIf' | 'End' | 'EndIf'
| 'Enum' | 'Erase' | 'Error' | 'Event'
| 'Exit' | 'False' | 'Finally' | 'For'
| 'Friend' | 'Function' | 'Get' | 'GetType'
| 'GetXmlNamespace' | 'Global' | 'GoSub' | 'GoTo'
| 'Handles' | 'If' | 'Implements' | 'Imports'
| 'In' | 'Inherits' | 'Integer' | 'Interface'
| 'Is' | 'IsNot' | 'Let' | 'Lib'
| 'Like' | 'Long' | 'Loop' | 'Me'
| 'Mod' | 'Module' | 'MustInherit' | 'MustOverride'
| 'MyBase' | 'MyClass' | 'Namespace' | 'Narrowing'
| 'New' | 'Next' | 'Not' | 'Nothing'
| 'NotInheritable' | 'NotOverridable' | 'Object' | 'Of'
| 'On' | 'Operator' | 'Option' | 'Optional'
| 'Or' | 'OrElse' | 'Overloads' | 'Overridable'
| 'Overrides' | 'ParamArray' | 'Partial' | 'Private'
| 'Property' | 'Protected' | 'Public' | 'RaiseEvent'
| 'ReadOnly' | 'ReDim' | 'REM' | 'RemoveHandler'
| 'Resume' | 'Return' | 'SByte' | 'Select'
| 'Set' | 'Shadows' | 'Shared' | 'Short'
| 'Single' | 'Static' | 'Step' | 'Stop'
| 'String' | 'Structure' | 'Sub' | 'SyncLock'
| 'Then' | 'Throw' | 'To' | 'True'
| 'Try' | 'TryCast' | 'TypeOf' | 'UInteger'
| 'ULong' | 'UShort' | 'Using' | 'Variant'
| 'Wend' | 'When' | 'While' | 'Widening'
| 'With' | 'WithEvents' | 'WriteOnly' | 'Xor'
;
Literales
Un literal es una representación textual de un valor determinado de un tipo. Entre los tipos literales se incluyen
Boolean, integer, Float Point, String, Character y Date.
Literal
: BooleanLiteral
| IntegerLiteral
| FloatingPointLiteral
| StringLiteral
| CharacterLiteral
| DateLiteral
| Nothing
;
Literales booleanos
True y False son literales del Boolean tipo que se asignan al estado true y false, respectivamente.
BooleanLiteral
: 'True' | 'False'
;
Literales enteros
Los literales enteros pueden ser decimales (base 10), hexadecimal (base 16) o octal (base 8). Un literal entero
decimal es una cadena de dígitos decimales (0-9). Un literal hexadecimal va &H seguido de una cadena de
dígitos hexadecimales (0-9, a-F). Un literal octal va &O seguido de una cadena de dígitos octales (0-7). Los
literales decimales representan directamente el valor decimal del literal entero, mientras que los literales octales
y hexadecimales representan el valor binario del literal entero (por lo tanto, &H8000S es-32768, no un error de
desbordamiento).
IntegerLiteral
: IntegralLiteralValue IntegralTypeCharacter?
;
IntegralLiteralValue
: IntLiteral
| HexLiteral
| OctalLiteral
;
IntegralTypeCharacter
: ShortCharacter
| UnsignedShortCharacter
| IntegerCharacter
| UnsignedIntegerCharacter
| LongCharacter
| UnsignedLongCharacter
| IntegerTypeCharacter
| LongTypeCharacter
;
ShortCharacter
: 'S'
;
UnsignedShortCharacter
: 'US'
;
IntegerCharacter
: 'I'
;
UnsignedIntegerCharacter
: 'UI'
;
LongCharacter
: 'L'
;
UnsignedLongCharacter
: 'UL'
;
IntLiteral
: Digit+
;
HexLiteral
: '&' 'H' HexDigit+
;
;
OctalLiteral
: '&' 'O' OctalDigit+
;
Digit
: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
;
HexDigit
: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
| 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
;
OctalDigit
: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7'
;
El tipo de un literal viene determinado por su valor o por el carácter de tipo siguiente. Si no se especifica ningún
carácter de tipo, los valores del intervalo del Integer tipo se escriben como Integer ; los valores fuera del
intervalo para Integer se escriben como Long . Si el tipo de un literal entero es de tamaño insuficiente para
contener el literal entero, se produce un error en tiempo de compilación. (Nota. No hay un carácter de tipo para
Byte porque el carácter más natural sería B , que es un carácter válido en un literal hexadecimal.)
Literales de Floating-Point
Un literal de punto flotante es un literal entero seguido de un separador decimal opcional (el carácter de punto
ASCII) y de mantisa, y un exponente de base 10 opcional. De forma predeterminada, un literal de punto flotante
es de tipo Double . Si Single Double Decimal se especifica el carácter de tipo, o, el literal es de ese tipo. Si el
tipo de un literal de punto flotante no tiene un tamaño suficiente para contener el literal de punto flotante, se
producirá un error en tiempo de compilación.
Nota. Merece la pena mencionar que el Decimal tipo de datos puede codificar ceros finales en un valor.
Actualmente, la especificación no realiza ningún comentario sobre si Decimal un compilador debe respetar los
ceros finales de un literal.
FloatingPointLiteral
: FloatingPointLiteralValue FloatingPointTypeCharacter?
| IntLiteral FloatingPointTypeCharacter
;
FloatingPointTypeCharacter
: SingleCharacter
| DoubleCharacter
| DecimalCharacter
| SingleTypeCharacter
| DoubleTypeCharacter
| DecimalTypeCharacter
;
SingleCharacter
: 'F'
;
DoubleCharacter
: 'R'
;
DecimalCharacter
: 'D'
;
FloatingPointLiteralValue
: IntLiteral '.' IntLiteral Exponent?
| '.' IntLiteral Exponent?
| IntLiteral Exponent
;
Exponent
: 'E' Sign? IntLiteral
;
Sign
: '+'
| '-'
;
Literales de cadena
Un literal de cadena es una secuencia de cero o más caracteres Unicode que comienza y termina con un carácter
de comilla doble ASCII, un carácter de comilla doble de apertura Unicode o un carácter de comilla doble derecha
Unicode. Dentro de una cadena, una secuencia de dos caracteres de comillas dobles es una secuencia de escape
que representa una comilla doble de la cadena.
StringLiteral
: DoubleQuoteCharacter StringCharacter* DoubleQuoteCharacter
;
DoubleQuoteCharacter
: '"'
| '<unicode left double-quote 0x201c>'
| '<unicode right double-quote 0x201D>'
;
StringCharacter
: '<Any character except DoubleQuoteCharacter>'
| DoubleQuoteCharacter DoubleQuoteCharacter
;
El compilador puede reemplazar una expresión de cadena constante por un literal de cadena. Cada literal de
cadena no tiene necesariamente como resultado una nueva instancia de cadena. Cuando dos o más literales de
cadena que son equivalentes de acuerdo con el operador de igualdad de cadena que usa la semántica de
comparación binaria aparecen en el mismo programa, estos literales de cadena pueden hacer referencia a la
misma instancia de cadena. Por ejemplo, la salida del programa siguiente puede devolver True porque los dos
literales pueden hacer referencia a la misma instancia de cadena.
Module Test
Sub Main()
Dim a As Object = "he" & "llo"
Dim b As Object = "hello"
Console.WriteLine(a Is b)
End Sub
End Module
Literales de carácter
Un literal de carácter representa un carácter Unicode único del Char tipo. Dos caracteres de comillas dobles son
una secuencia de escape que representa el carácter de comillas dobles.
CharacterLiteral
: DoubleQuoteCharacter StringCharacter DoubleQuoteCharacter 'C'
;
Module Test
Sub Main()
Literales de fecha
Un literal de fecha representa un momento determinado en el tiempo expresado como un valor del Date tipo.
DateLiteral
: '#' WhiteSpace* DateOrTime WhiteSpace* '#'
;
DateOrTime
: DateValue WhiteSpace+ TimeValue
| DateValue
| TimeValue
;
DateValue
: MonthValue '/' DayValue '/' YearValue
| MonthValue '-' DayValue '-' YearValue
;
TimeValue
: HourValue ':' MinuteValue ( ':' SecondValue )? WhiteSpace* AMPM?
| HourValue WhiteSpace* AMPM
;
MonthValue
: IntLiteral
;
DayValue
: IntLiteral
;
YearValue
: IntLiteral
;
HourValue
: IntLiteral
;
MinuteValue
: IntLiteral
;
SecondValue
: IntLiteral
;
AMPM
: 'AM' | 'PM'
;
El literal puede especificar una fecha y una hora, solo una fecha o una sola vez. Si se omite el valor de fecha, se
supone que el 1 de enero del año 1 del calendario gregoriano. Si se omite el valor de hora, se supone 12:00:00
AM.
Para evitar problemas al interpretar el valor de año en un valor de fecha, el valor de año no puede ser de dos
dígitos. Al expresar una fecha en el primer siglo AD/CE, deben especificarse ceros iniciales.
Se puede especificar un valor de hora mediante un valor de 24 horas o un valor de 12 horas; los valores de hora
que omiten AM o PM se supone que son valores de 24 horas. Si un valor de hora omite los minutos, se utiliza el
literal de 0 forma predeterminada. Si un valor de hora omite los segundos, se utiliza el literal de 0 forma
predeterminada. Si se omiten los minutos y los segundos, se AM PM debe especificar o. Si el valor de fecha
especificado está fuera del intervalo del Date tipo, se produce un error en tiempo de compilación.
El ejemplo siguiente contiene varios literales de fecha.
Dim d As Date
d = # 8/23/1970 3:45:39AM #
d = # 8/23/1970 # ' Date value: 8/23/1970 12:00:00AM.
d = # 3:45:39AM # ' Date value: 1/1/1 3:45:39AM.
d = # 3:45:39 # ' Date value: 1/1/1 3:45:39AM.
d = # 13:45:39 # ' Date value: 1/1/1 1:45:39PM.
d = # 1AM # ' Date value: 1/1/1 1:00:00AM.
d = # 13:45:39PM # ' This date value is not valid.
Nada
Nothing es un literal especial; no tiene un tipo y se pueden convertir a todos los tipos del sistema de tipos,
incluidos los parámetros de tipo. Cuando se convierte a un tipo determinado, es el equivalente del valor
predeterminado de ese tipo.
Nothing
: 'Nothing'
;
Separadores
Los siguientes caracteres ASCII son separadores:
Separator
: '(' | ')' | '{' | '}' | '!' | '#' | ',' | '.' | ':' | '?'
;
Caracteres de operador
Los siguientes caracteres ASCII o secuencias de caracteres indican operadores:
Operator
: '&' | '*' | '+' | '-' | '/' | '\\' | '^' | '<' | '=' | '>'
;
Preprocesar directivas
15/11/2021 • 9 minutes to read
Una vez que un archivo se ha analizado léxicamente, se producen varios tipos de preprocesamiento de origen.
La compilación condicional más importante, determina qué origen se procesa mediante la gramática sintáctica;
otros dos tipos de directivas: directivas de código fuente externas y directivas de región: proporcionan
metainformación sobre el origen, pero no tienen ningún efecto en la compilación.
Compilación condicional
La compilación condicional controla si las secuencias de líneas lógicas se traducen en código real. Al principio de
la compilación condicional, se habilitan todas las líneas lógicas; sin embargo, las líneas de inclusión en
instrucciones de compilación condicional pueden deshabilitar selectivamente esas líneas en el archivo, lo que
provoca que se omitan durante el resto del proceso de compilación.
CCStart
: CCStatement*
;
CCStatement
: CCConstantDeclaration
| CCIfGroup
| LogicalLine
;
CCExpression
: LiteralExpression
| CCParenthesizedExpression
| CCSimpleNameExpression
| CCCastExpression
| CCOperatorExpression
| CCConditionalExpression
;
CCParenthesizedExpression
: '(' CCExpression ')'
;
CCSimpleNameExpression
: Identifier
;
CCCastExpression
: 'DirectCast' '(' CCExpression ',' TypeName ')'
| 'TryCast' '(' CCExpression ',' TypeName ')'
| 'CType' '(' CCExpression ',' TypeName ')'
| CastTarget '(' CCExpression ')'
;
CCOperatorExpression
: CCUnaryOperator CCExpression
| CCExpression CCBinaryOperator CCExpression
;
CCUnaryOperator
: '+' | '-' | 'Not'
;
CCBinaryOperator
: '+' | '-' | '*' | '/' | '\\' | 'Mod' | '^' | '='
| '<' '>' | '<' | '>' | '<' '=' | '>' '=' | '&'
| 'And' | 'Or' | 'Xor' | 'AndAlso' | 'OrElse'
| '<' '<' | '>' '>'
;
CCConditionalExpression
: 'If' '(' CCExpression ',' CCExpression ',' CCExpression ')'
| 'If' '(' CCExpression ',' CCExpression ')'
;
Class C
#If A Then
Sub F()
End Sub
#Else
Sub G()
End Sub
#End If
#If B Then
Sub H()
End Sub
#Else
Sub I()
End Sub
#End If
End Class
Class C
Sub F()
End Sub
Sub I()
End Sub
End Class
Las expresiones constantes que se permiten en las directivas de compilación condicional son un subconjunto de
expresiones constantes generales.
El preprocesador permite el espacio en blanco y las continuaciones de línea explícitas antes y después de cada
token.
Directivas de constantes condicionales
Las instrucciones de constantes condicionales definen constantes que existen en un espacio de declaración de
compilación condicional independiente cuyo ámbito es el archivo de código fuente.
CCConstantDeclaration
: '#' 'Const' Identifier '=' CCExpression LineTerminator
;
Console.WriteLine(Test)
End Sub
End Module
CCIfGroup
: '#' 'If' CCExpression 'Then'? LineTerminator CCStatement*
CCElseIfGroup* CCElseGroup? '#' 'End' 'If' LineTerminator
;
CCElseIfGroup
: '#' ElseIf CCExpression 'Then'? LineTerminator CCStatement*
;
CCElseGroup
: '#' 'Else' LineTerminator CCStatement*
;
Las constantes condicionales solo pueden hacer referencia a expresiones constantes y constantes de
compilación condicional. Cada una de las expresiones constantes dentro de un único grupo de compilación
condicional se evalúa y se convierte al Boolean tipo en orden textual de primero a último hasta que una de las
expresiones condicionales se evalúa como True . Si una expresión no es convertible a Boolean , se producirá
un error en tiempo de compilación. La semántica permisiva y las comparaciones de cadenas binarias siempre se
usan al evaluar expresiones constantes de compilación condicional, independientemente de las Option
directivas o la configuración del entorno de compilación.
Todas las líneas delimitadas por el grupo, incluidas las directivas de compilación condicional anidadas, se
deshabilitan, excepto las líneas entre la instrucción que contiene la True expresión y la siguiente instrucción
condicional del grupo, o las líneas entre la instrucción Else y la End If instrucción si Else aparece en el
grupo y todas las expresiones se evalúan como False .
En este ejemplo, la llamada a WriteToLog en la Trace Directiva de compilación condicional no se procesa
porque la Debug Directiva de compilación condicional circundante se evalúa como False .
#Const Debug = False ' Debugging off
#Const Trace = True ' Tracing on
Class PurchaseTransaction
Sub Commit()
ESDStart
: ExternalSourceStatement*
;
ExternalSourceStatement
: ExternalSourceGroup
| LogicalLine
;
ExternalSourceGroup
: '#' 'ExternalSource' '(' StringLiteral ',' IntLiteral ')' LineTerminator
LogicalLine* '#' 'End' 'ExternalSource' LineTerminator
;
Las directivas de origen externo no tienen ningún efecto en la compilación y pueden no estar anidadas. Por
ejemplo:
Module Test
Sub Main()
#ExternalSource("c:\wwwroot\inetpub\test.aspx", 30)
Console.WriteLine("In test.aspx")
#End ExternalSource
End Sub
End Module
Directivas de región
Las directivas de región agrupan líneas de código fuente, pero no tienen ningún otro efecto en la compilación.
Todo el grupo se puede contraer y ocultar, o expandir y ver, en el entorno de desarrollo integrado (IDE) de.
RegionStart
: RegionStatement*
;
RegionStatement
: RegionGroup
| LogicalLine
;
RegionGroup
: '#' 'Region' StringLiteral LineTerminator
RegionStatement*
'#' 'End' 'Region' LineTerminator
;
Las regiones pueden estar anidadas. Las directivas de región son especiales, ya que no pueden iniciar ni finalizar
dentro del cuerpo de un método, y deben respetar la estructura de bloque del programa. Por ejemplo:
Module Test
#Region "Startup code - do not edit"
Sub Main()
End Sub
#End Region
End Module
ExternalChecksumStart
: ExternalChecksumStatement*
;
ExternalChecksumStatement
: '#' 'ExternalChecksum' '('
StringLiteral ',' StringLiteral ',' StringLiteral
')' LineTerminator
;
Una directiva de suma de comprobación externa contiene el nombre del archivo externo, un identificador único
global (GUID) asociado al archivo y la suma de comprobación para el archivo. El GUID se especifica como una
constante de cadena con el formato "{xxxxxxxx-XXXX-XXXX-XXXX-XXXXXXXXXXXX}", donde x es un dígito
hexadecimal. La suma de comprobación se especifica como una constante de cadena con el formato "xxxx...",
donde x es un dígito hexadecimal. El número de dígitos de una suma de comprobación debe ser un número par.
Un archivo externo puede tener varias directivas de suma de comprobación externas asociadas a él, siempre que
todos los valores de GUID y suma de comprobación coincidan exactamente. Si el nombre del archivo externo
coincide con el nombre de un archivo que se está compilando, la suma de comprobación se omite en favor del
cálculo de la suma de comprobación del compilador.
Por ejemplo:
#ExternalChecksum("c:\wwwroot\inetpub\test.aspx", _
"{12345678-1234-1234-1234-123456789abc}", _
"1a2b3c4e5f617239a49b9a9c0391849d34950f923fab9484")
Module Test
Sub Main()
#ExternalSource("c:\wwwroot\inetpub\test.aspx", 30)
Console.WriteLine("In test.aspx")
#End ExternalSource
End Sub
End Module
Conceptos generales
15/11/2021 • 108 minutes to read
En este capítulo se tratan varios conceptos necesarios para entender la semántica del lenguaje Microsoft Visual
Basic. Muchos de los conceptos deben estar familiarizados con Visual Basic programadores o programadores de
C/C++, pero sus definiciones precisas pueden diferir.
Declaraciones
Un programa de Visual Basic se compone de entidades con nombre. Estas entidades se introducen a través de
declaraciones y representan el "significado" del programa.
En un nivel superior, los espacios de nombres son entidades que organizan otras entidades, como los tipos y
espacios de nombres anidados. Los tipos son entidades que describen valores y definen código ejecutable. Los
tipos pueden contener tipos anidados y miembros de tipo. Los miembros de tipo son constantes, variables,
métodos, operadores, propiedades, eventos, valores de enumeración y constructores.
Una entidad que puede contener otras entidades define un espacio de declaración. Las entidades se introducen
en un espacio de declaración a través de declaraciones o herencia; el espacio de declaración contenedor se
denomina contexto de declaración de entidades. Al declarar una entidad en un espacio de declaración a su vez,
se define un nuevo espacio de declaración que puede contener más declaraciones de entidades anidadas. por lo
tanto, las declaraciones de un programa forman una jerarquía de espacios de declaración.
Excepto en el caso de los miembros de tipo sobrecargados, no es válido para las declaraciones introducir
entidades idénticas con nombre de la misma clase en el mismo contexto de declaración. Además, un espacio de
declaración nunca puede contener diferentes tipos de entidades con el mismo nombre. por ejemplo, un espacio
de declaración no puede contener nunca una variable y un método con el mismo nombre.
Nota. Es posible que en otros lenguajes se cree un espacio de declaración que contenga distintos tipos de
entidades con el mismo nombre (por ejemplo, si el lenguaje distingue entre mayúsculas y minúsculas y permite
distintas declaraciones basadas en mayúsculas y minúsculas). En esa situación, se considera que la entidad más
accesible está enlazada a ese nombre; Si más de un tipo de entidad es más accesible, el nombre es ambiguo.
Public es más accesible que Protected Friend , Protected Friend es más accesible que Protected o Friend ,
y Protected o Friend es más accesible que Private .
El espacio de declaración de un espacio de nombres es "Open Terminated", por lo que dos declaraciones de
espacios de nombres con el mismo nombre completo contribuyen al mismo espacio de declaración. En el
ejemplo siguiente, las dos declaraciones de espacio de nombres contribuyen al mismo espacio de declaración;
en este caso, se declaran dos clases con los nombres completos Data.Customer y Data.Order:
Namespace Data
Class Customer
End Class
End Namespace
Namespace Data
Class Order
End Class
End Namespace
Dado que las dos declaraciones contribuyen al mismo espacio de declaración, se producirá un error en tiempo
de compilación si cada una de ellas contenía una declaración de una clase con el mismo nombre.
Sobrecarga y firmas
La única manera de declarar entidades con nombre idénticos del mismo tipo en un espacio de declaración es a
través de la sobrecarga. Solo los métodos, operadores, constructores de instancias y propiedades se pueden
sobrecargar.
Los miembros de tipo sobrecargados deben poseer firmas únicas. La signatura de un miembro de tipo consta
del número de parámetros de tipo y el número y los tipos de los parámetros del miembro. Los operadores de
conversión también incluyen el tipo de valor devuelto del operador en la signatura.
Los siguientes elementos no forman parte de la firma de un miembro y, por lo tanto, no se pueden sobrecargar
en:
Modificadores de un miembro de tipo (por ejemplo, Shared o Private ).
Modificadores de un parámetro (por ejemplo, ByVal o ByRef ).
Los nombres de los parámetros.
El tipo de valor devuelto de un método o un operador (excepto los operadores de conversión) o el tipo de
elemento de una propiedad.
Restricciones en un parámetro de tipo.
En el ejemplo siguiente se muestra un conjunto de declaraciones de método sobrecargado junto con sus firmas.
Esta declaración no sería válida ya que varias de las declaraciones de método tienen firmas idénticas.
Interface ITest
Sub F1() ' Signature is ().
Sub F2(x As Integer) ' Signature is (Integer).
Sub F3(ByRef x As Integer) ' Signature is (Integer).
Sub F4(x As Integer, y As Integer) ' Signature is (Integer, Integer).
Function F5(s As String) As Integer ' Signature is (String).
Function F6(x As Integer) As Integer ' Signature is (Integer).
Sub F7(a() As String) ' Signature is (String()).
Sub F8(ParamArray a() As String) ' Signature is (String()).
Sub F9(Of T)() ' Signature is !1().
Sub F10(Of T, U)(x As T, y As U) ' Signature is !2(!1, !2)
Sub F11(Of U, T)(x As T, y As U) ' Signature is !2(!2, !1)
Sub F12(Of T)(x As T) ' Signature is !1(!1)
Sub F13(Of T As IDisposable)(x As T) ' Signature is !1(!1)
End Interface
Es válido definir un tipo genérico que pueda contener miembros con firmas idénticas basadas en los
argumentos de tipo proporcionados. Las reglas de resolución de sobrecarga se usan para probar y eliminar la
ambigüedad entre estas sobrecargas, aunque puede haber situaciones en las que sea imposible eliminar la
ambigüedad. Por ejemplo:
Class C(Of T)
Sub F(x As Integer)
End Sub
Sub F(x As T)
End Sub
Module Test
Sub Main()
Dim x As New C(Of Integer)
x.F(10) ' Calls C(Of T).F(Integer)
x.G(Of Integer)(10, 10) ' Error: Can't choose between overloads
End Sub
End Module
Ámbito
El ámbito del nombre de una entidad es el conjunto de todos los espacios de declaración en los que es posible
hacer referencia a ese nombre sin calificación. En general, el ámbito del nombre de una entidad es todo su
contexto de declaración; sin embargo, la declaración de una entidad puede contener declaraciones anidadas de
entidades con el mismo nombre. En ese caso, la entidad anidada sombrea u oculta la entidad externa y el acceso
a la entidad con sombra solo es posible a través de la calificación.
El sombreado a través de la anidación se produce en los espacios de nombres o tipos anidados dentro de los
espacios de nombres, en los tipos anidados dentro de otros tipos y en los cuerpos de los miembros de tipo. El
sombreado mediante el anidamiento de declaraciones siempre se produce implícitamente. no se requiere
ninguna sintaxis explícita.
En el ejemplo siguiente, dentro del F método, la variable local prevalece sobre la variable de instancia i i ,
pero dentro del G método i todavía hace referencia a la variable de instancia.
Class Test
Private i As Integer = 0
Sub F()
Dim i As Integer = 1
End Sub
Sub G()
i = 1
End Sub
End Class
Cuando un nombre de un ámbito interno oculta un nombre en un ámbito externo, sombrea todas las
apariciones sobrecargadas de ese nombre. En el ejemplo siguiente, la llamada F(1) invoca el F declarado en
Inner porque todas las repeticiones externas de F están ocultas por la declaración interna. Por la misma
razón, la llamada F("Hello") es erróneo.
Class Outer
Shared Sub F(i As Integer)
End Sub
Class Inner
Shared Sub F(l As Long)
End Sub
Sub G()
F(1) ' Invokes Outer.Inner.F.
F("Hello") ' Error.
End Sub
End Class
End Class
Herencia
Una relación de herencia es aquella en la que un tipo (el tipo derivado ) deriva de otro (el tipo base ), de modo
que el espacio de declaración del tipo derivado contiene implícitamente los miembros de tipo no constructor
accesibles y los tipos anidados de su tipo base. En el ejemplo siguiente, A la clase es la clase base de B y B se
deriva de A .
Class A
End Class
Class B
Inherits A
End Class
Puesto que no A especifica explícitamente una clase base, su clase base es implícitamente Object .
Los siguientes son aspectos importantes de la herencia:
La herencia es transitiva. Si el tipo C se deriva del tipo b y el tipo b se deriva del tipo A, el tipo c hereda los
miembros de tipo declarados en el tipo b , así como los miembros de tipo declarados en el tipo A.
Un tipo derivado extiende, pero no puede reducir, su tipo base. Un tipo derivado puede agregar nuevos
miembros de tipo y puede prevalecer sobre los miembros de tipo heredados, pero no puede quitar la
definición de un miembro de tipo heredado.
Dado que una instancia de un tipo contiene todos los miembros de tipo de su tipo base, siempre existe
una conversión de un tipo derivado a su tipo base.
Todos los tipos deben tener un tipo base, excepto el tipo Object . Por lo tanto, Object es el tipo base
fundamental de todos los tipos y todos los tipos se pueden convertir en él.
No se permite la circularidad en la derivación. Es decir, cuando un tipo se B deriva de un tipo A , es un
error que el tipo se A derive directa o indirectamente del tipo B .
Un tipo no puede derivar directa o indirectamente de un tipo anidado dentro de él.
En el ejemplo siguiente se genera un error en tiempo de compilación porque las clases dependen circularmente
entre sí.
Class A
Inherits B
End Class
Class B
Inherits C
End Class
Class C
Inherits A
End Class
En el ejemplo siguiente también se genera un error en tiempo B de compilación porque deriva indirectamente
de su clase anidada C a través de la clase A .
Class A
Inherits B.C
End Class
Class B
Inherits A
Public Class C
End Class
End Class
Class A
Class B
Inherits A
End Class
End Class
MustInherit Class B
Inherits A
Class C
Inherits B
NotInheritable Class A
End Class
Class B
' Error, a class cannot derive from a NotInheritable class.
Inherits A
End Class
Interface ICounter
Sub Count(i As Integer)
End Interface
Interface IListCounter
Inherits IList
Inherits ICounter
End Interface
Module Test
Sub F(x As IListCounter)
x.Count(1) ' Error, Count is not available.
x.Count = 1 ' Error, Count is not available.
CType(x, IList).Count = 1 ' Ok, invokes IList.Count.
CType(x, ICounter).Count(1) ' Ok, invokes ICounter.Count.
End Sub
End Module
Como se muestra en el ejemplo, la ambigüedad se resuelve mediante la conversión x al tipo de interfaz base
adecuado. Tales conversiones no tienen ningún costo en tiempo de ejecución; simplemente se componen de ver
la instancia como un tipo menos derivado en tiempo de compilación.
Cuando un solo miembro de tipo se hereda de la misma interfaz base a través de varias rutas de acceso, el
miembro de tipo se trata como si solo se heredase una vez. En otras palabras, la interfaz derivada solo contiene
una instancia de cada miembro de tipo heredado de una interfaz base determinada. Por ejemplo:
Interface IBase
Sub F(i As Integer)
End Interface
Interface ILeft
Inherits IBase
End Interface
Interface IRight
Inherits IBase
End Interface
Interface IDerived
Inherits ILeft, IRight
End Interface
Class Derived
Implements IDerived
Si un nombre de miembro de tipo se sombrea en una ruta de acceso a través de la jerarquía de herencia, el
nombre se prevalece en todas las rutas de acceso. En el ejemplo siguiente, el miembro IBase.F es sombreado
por el ILeft.F miembro, pero no está sombreado en IRight :
Interface IBase
Sub F(i As Integer)
End Interface
Interface ILeft
Inherits IBase
Interface IRight
Inherits IBase
Sub G()
End Interface
Interface IDerived
Inherits ILeft, IRight
End Interface
Class Test
Sub H(d As IDerived)
d.F(1) ' Invokes ILeft.F.
CType(d, IBase).F(1) ' Invokes IBase.F.
CType(d, ILeft).F(1) ' Invokes ILeft.F.
CType(d, IRight).F(1) ' Invokes IBase.F.
End Sub
End Class
La invocación d.F(1) selecciona ILeft.F , aunque IBase.F parece que no está sombreado en la ruta de
acceso que conduce a través de IRight . Dado que la ruta de acceso de IDerived a ILeft IBase sombrea
IBase.F , el miembro también se sombrea en la ruta de acceso IDerived de IRight a IBase .
Sombreado
Un tipo derivado prevalece sobre el nombre de un miembro de tipo heredado al volver a declararlo. Al
sombrear un nombre no se quitan los miembros de tipo heredados con ese nombre; simplemente hace que
todos los miembros de tipo heredados con ese nombre no estén disponibles en la clase derivada. La declaración
de sombreado puede ser cualquier tipo de entidad.
Las entidades que se pueden sobrecargar pueden elegir una de las dos formas de sombreado. El sombreado por
nombre se especifica mediante la Shadows palabra clave. Una entidad que sombrea por nombre oculta todo con
ese nombre en la clase base, incluidas todas las sobrecargas. La sombra por nombre y firma se especifica
mediante la Overloads palabra clave. Una entidad que sombrea por nombre y firma oculta todo el nombre con
la misma firma que la entidad. Por ejemplo:
Class Base
Sub F()
End Sub
Sub G()
End Sub
Class Derived
Inherits Base
Module Test
Sub Main()
Dim x As New Derived()
Al sombrear un método con un ParamArray argumento por nombre y firma solo se oculta la firma individual, no
todas las firmas expandidas posibles. Esto es así incluso si la firma del método de sombreado coincide con la
firma no expandida del método sombreado. En el ejemplo siguiente:
Class Base
Sub F(ParamArray x() As Integer)
Console.WriteLine("Base")
End Sub
End Class
Class Derived
Inherits Base
Module Test
Sub Main
Dim d As New Derived()
d.F(10)
End Sub
End Module
imprime Base , aunque Derived.F tiene la misma firma que la forma no expandida de Base.F .
Por el contrario, un método con un ParamArray argumento solo prevalece sobre los métodos con la misma
firma, no todas las firmas expandidas posibles. En el ejemplo siguiente:
Class Base
Sub F(x As Integer)
Console.WriteLine("Base")
End Sub
End Class
Class Derived
Inherits Base
Module Test
Sub Main()
Dim d As New Derived()
d.F(10)
End Sub
End Module
imprime Base , aunque Derived.F tenga un formulario expandido que tenga la misma firma que Base.F .
Método o propiedad de sombreado que no especifica Shadows ni Overloads asume Overloads si se declara el
método o la propiedad Overrides ; de Shadows lo contrario,. Si un miembro de un conjunto de entidades
sobrecargadas especifica la Shadows Overloads palabra clave o, todas deben especificarla. Las Shadows
Overloads palabras clave y no se pueden especificar al mismo tiempo. Ni Shadows ni Overloads se pueden
especificar en un módulo estándar; los miembros de un módulo estándar sombrean implícitamente los
miembros heredados de Object .
Es válido para sombrear el nombre de un miembro de tipo que ha sido multiplicado por la herencia de interfaz
(y que, por tanto, no está disponible), lo que permite que el nombre esté disponible en la interfaz derivada.
Por ejemplo:
Interface ILeft
Sub F()
End Interface
Interface IRight
Sub F()
End Interface
Interface ILeftRight
Inherits ILeft, IRight
Module Test
Sub G(i As ILeftRight)
i.F() ' Calls ILeftRight.F.
CType(i, ILeft).F() ' Calls ILeft.F.
CType(i, IRight).F() ' Calls IRight.F.
End Sub
End Module
Dado que los métodos pueden crear instantáneas de métodos heredados, es posible que una clase contenga
varios Overridable métodos con la misma firma. Esto no presenta un problema de ambigüedad, ya que solo el
método más derivado es visible. En el ejemplo siguiente, las C D clases y contienen dos Overridable métodos
con la misma firma:
Class A
Public Overridable Sub F()
Console.WriteLine("A.F")
End Sub
End Class
Class B
Inherits A
Class C
Inherits B
Class D
Inherits C
Module Test
Sub Main()
Dim d As New D()
Dim a As A = d
Dim b As B = d
Dim c As C = d
a.F()
b.F()
c.F()
d.F()
End Sub
End Module
Hay dos Overridable métodos aquí: uno introducido por la clase A y el que se ha introducido por la clase C .
El método introducido por la clase C oculta el método heredado de la clase A . Por lo tanto, la Overrides
declaración de la clase D invalida el método introducido por la clase C y no es posible que la clase D invalide
el método introducido por la clase A . En el ejemplo se genera el resultado:
B.F
B.F
D.F
D.F
Es posible invocar el método oculto Overridable mediante el acceso a una instancia de D la clase a través de
un tipo menos derivado en el que el método no está oculto.
No es válido para prevalecer sobre un MustOverride método, porque en la mayoría de los casos esto haría que
la clase fuera inutilizable. Por ejemplo:
MustInherit Class Base
Public MustOverride Sub F()
End Class
Class MoreDerived
Inherits Derived
En este caso, MoreDerived se requiere que la clase invalide el MustOverride método Base.F , pero como la clase
Derived prevalece Base.F , esto no es posible. No hay ninguna manera de declarar un descendiente válido de
Derived .
A diferencia de la sombra de un nombre desde un ámbito externo, la sombra de un nombre accesible desde un
ámbito heredado provoca la notificación de una advertencia, como en el ejemplo siguiente:
Class Base
Public Sub F()
End Sub
Class Derived
Inherits Base
La declaración del método F en la clase Derived genera una advertencia. Sombrear un nombre heredado no
es específicamente un error, ya que esto impedirá la evolución independiente de las clases base. Por ejemplo, la
situación anterior podría haber surgido debido a que una versión posterior de Base la clase presentó un
método F que no estaba presente en una versión anterior de la clase. Si la situación anterior era un error,
cualquier cambio realizado en una clase base en una biblioteca de clases con versiones independientes podría
provocar que las clases derivadas dejen de ser válidas.
La advertencia causada por la sombra de un nombre heredado se puede eliminar mediante el uso del Shadows
Overloads modificador o:
Class Base
Public Sub F()
End Sub
End Class
Class Derived
Inherits Base
El Shadows modificador indica la intención de prevalecer sobre el miembro heredado. No es un error especificar
el Shadows Overloads modificador o si no hay ningún nombre de miembro de tipo para la sombra.
Una declaración de un miembro nuevo prevalece sobre un miembro heredado solo dentro del ámbito del nuevo
miembro, como en el ejemplo siguiente:
Class Base
Public Shared Sub F()
End Sub
End Class
Class Derived
Inherits Base
Private Shared Shadows Sub F() ' Shadows Base.F in class Derived only.
End Sub
End Class
Class MoreDerived
Inherits Derived
En el ejemplo anterior, la declaración del método F de la clase Derived prevalece sobre el método F
heredado de la clase Base , pero como el nuevo método F de la clase Derived tiene Private acceso, su
ámbito no se extiende a la clase MoreDerived . Por lo tanto, la llamada F() en MoreDerived.G es válida y
llamará a Base.F . En el caso de los miembros de tipo sobrecargados, todo el conjunto de miembros de tipo
sobrecargado se trata como si todos tuvieran el acceso más permisivo para el sombreado.
Class Base
Public Sub F()
End Sub
End Class
Class Derived
Inherits Base
Class MoreDerived
Inherits Derived
En este ejemplo, aunque la declaración de F() en Derived se declara con Private acceso, el sobrecargado
F(Integer) se declara con Public acceso. Por lo tanto, para la sombra, el nombre F de Derived se trata como
si fuera, de Public modo que ambos métodos prevalecen F Base .
Implementación
Existe una relación de implementación cuando un tipo declara que implementa una interfaz y el tipo
implementa todos los miembros de tipo de la interfaz. Un tipo que implementa una interfaz determinada es
convertible en esa interfaz. No se pueden crear instancias de las interfaces, pero es válido declarar variables de
interfaces; a estas variables solo se les puede asignar un valor que sea de una clase que implemente la interfaz.
Por ejemplo:
Interface ITestable
Function Test(value As Byte) As Boolean
End Interface
Class TestableClass
Implements ITestable
Module Test
Sub F()
Dim x As ITestable = New TestableClass
Dim b As Boolean
b = x.Test(34)
End Sub
End Module
Un tipo que implementa una interfaz con miembros de tipo que se heredan de multiplicación debe seguir
implementando esos métodos, aunque no se pueda tener acceso a ellos directamente desde la interfaz derivada
que se está implementando. Por ejemplo:
Interface ILeft
Sub Test()
End Interface
Interface IRight
Sub Test()
End Interface
Interface ILeftRight
Inherits ILeft, IRight
End Interface
Class LeftRight
Implements ILeftRight
Incluso MustInherit las clases deben proporcionar implementaciones de todos los miembros de interfaces
implementadas; sin embargo, pueden aplazar la implementación de estos métodos declarándolos como
MustOverride . Por ejemplo:
Interface ITest
Sub Test1()
Sub Test2()
End Interface
Class TestDerived
Inherits TestBase
Un tipo puede optar por volver a implementar una interfaz implementada por su tipo base. Para volver a
implementar la interfaz, el tipo debe indicar explícitamente que implementa la interfaz. Un tipo que vuelva a
implementar una interfaz puede optar por volver a implementar solo algunos de los miembros de la interfaz,
pero no todos, seguir usando la implementación del tipo base. Por ejemplo:
Class TestBase
Implements ITest
Class TestDerived
Inherits TestBase
Implements ITest ' Required to re-implement
Module Test
Sub Main()
Dim Test As ITest = New TestDerived()
Test.Test1()
Test.Test2()
End Sub
End Module
TestDerived.DerivedTest1
TestBase.Test2
Cuando un tipo derivado implementa una interfaz cuyas interfaces base se implementan mediante los tipos
base del tipo derivado, el tipo derivado puede elegir implementar solo los miembros de tipo de la interfaz que
no se hayan implementado en los tipos base. Por ejemplo:
Interface IBase
Sub Base()
End Interface
Interface IDerived
Inherits IBase
Sub Derived()
End Interface
Class Base
Implements IBase
Class Derived
Inherits Base
Implements IDerived
Un método de interfaz también puede implementarse mediante un método reemplazable en un tipo base. En
ese caso, un tipo derivado también puede invalidar el método reemplazable y modificar la implementación de la
interfaz. Por ejemplo:
Class Base
Implements ITest
Class Derived
Inherits Base
Implementar métodos
Un tipo implementa un miembro de tipo de una interfaz implementada proporcionando un método con una
Implements cláusula. Los dos miembros de tipo deben tener el mismo número de parámetros, todos los tipos y
modificadores de los parámetros deben coincidir, incluido el valor predeterminado de los parámetros
opcionales, el tipo de valor devuelto debe coincidir y todas las restricciones en los parámetros de método deben
coincidir. Por ejemplo:
Interface ITest
Sub F(ByRef x As Integer)
Sub G(Optional y As Integer = 20)
Sub H(Paramarray z() As Integer)
End Interface
Class Test
Implements ITest
Un único método puede implementar cualquier número de miembros de tipo de interfaz si todos cumplen los
criterios anteriores. Por ejemplo:
Interface ITest
Sub F(i As Integer)
Sub G(i As Integer)
End Interface
Class Test
Implements ITest
Al implementar un método en una interfaz genérica, el método de implementación debe proporcionar los
argumentos de tipo que corresponden a los parámetros de tipo de la interfaz. Por ejemplo:
Interface I1(Of U, V)
Sub M(x As U, y As List(Of V))
End Interface
Class C1(Of W, X)
Implements I1(Of W, X)
Class C2
Implements I1(Of String, Integer)
Interface I1(Of T, U)
Sub S1(x As T)
Sub S1(y As U)
End Interface
Class C1
' Unable to implement because I1.S1 has two identical signatures
Implements I1(Of Integer, Integer)
End Class
Polimorfismo
El polimorfismo proporciona la capacidad de modificar la implementación de un método o propiedad. Con
polimorfismo, el mismo método o propiedad puede realizar diferentes acciones en función del tipo en tiempo
de ejecución de la instancia que lo invoca. Los métodos o las propiedades que son polimórficos se denominan
Overridable. Por el contrario, la implementación de un método o propiedad no reemplazable es invariable; la
implementación es la misma si se invoca el método o la propiedad en una instancia de la clase en la que se
declara o una instancia de una clase derivada. Cuando se invoca un método o una propiedad no reemplazable,
el tipo en tiempo de compilación de la instancia es el factor determinante. Por ejemplo:
Class Base
Public Overridable Property X() As Integer
Get
End Get
Set
End Set
End Property
End Class
Class Derived
Inherits Base
Set
End Set
End Property
End Class
Module Test
Sub F()
Dim Z As Base
Z = New Base()
Z.X = 10 ' Calls Base.X
Z = New Derived()
Z.X = 10 ' Calls Derived.X
End Sub
End Module
Un método reemplazable también puede ser MustOverride , lo que significa que no proporciona ningún cuerpo
de método y se debe invalidar. MustOverride los métodos solo se permiten en MustInherit clases.
En el ejemplo siguiente, la clase Shape define la noción abstracta de un objeto de forma geométrica que puede
dibujarse:
El Paint método se debe a que no hay MustOverride ninguna implementación predeterminada significativa.
Las Ellipse Box clases y son Shape implementaciones concretas. Dado que estas clases no son MustInherit ,
son necesarias para invalidar el Paint método y proporcionar una implementación real.
Es un error que un acceso base haga referencia a un MustOverride método, como se muestra en el ejemplo
siguiente:
MustInherit Class A
Public MustOverride Sub F()
End Class
Class B
Inherits A
Se ha generado un error para la MyBase.F() invocación porque hace referencia a un MustOverride método.
Reemplazar métodos
Un tipo puede reemplazar un método reemplazable heredado declarando un método con el mismo nombre y
firma, y marcando la declaración con el Overrides modificador. Hay requisitos adicionales sobre los métodos de
invalidación que se enumeran a continuación. Mientras que una Overridable declaración de método introduce
un método nuevo, una Overrides declaración de método reemplaza la implementación heredada del método.
Se puede declarar un método de invalidación NotOverridable , lo que evita cualquier reemplazo adicional del
método en los tipos derivados. En efecto, NotOverridable los métodos pasan a ser no reemplazables en
cualquier clase derivada adicional.
Considere el ejemplo siguiente:
Class A
Public Overridable Sub F()
Console.WriteLine("A.F")
End Sub
Class B
Inherits A
Class C
Inherits B
Class A
Public Overridable Sub F()
Console.WriteLine("A.F")
End Sub
End Class
MustInherit Class B
Inherits A
En el ejemplo, la clase B reemplaza A.F con un MustOverride método. Esto significa que cualquier clase
derivada de tendrá B que invalidar F , a menos que también se declaren MustInherit .
Se produce un error en tiempo de compilación a menos que se cumplan todas las condiciones siguientes de un
método de invalidación:
El contexto de la declaración contiene un solo método heredado accesible con la misma firma y el mismo
tipo de valor devuelto (si existe) que el método de reemplazo.
El método heredado que se va a invalidar es Overridable. En otras palabras, el método heredado que se va a
reemplazar no es Shared ni NotOverridable .
El dominio de accesibilidad del método que se declara es el mismo que el dominio de accesibilidad del
método heredado que se va a invalidar. Existe una excepción: un método Protected Friend debe invalidar un
método Protected si el otro método está en otro ensamblado al que no tiene acceso el método de
reemplazo Friend .
Los parámetros del método de reemplazo coinciden con los parámetros del método invalidado en lo que
respecta al uso de los ByVal ByRef ParamArray, Optional modificadores, y, incluidos los valores
proporcionados para los parámetros opcionales.
Los parámetros de tipo del método de reemplazo coinciden con los parámetros de tipo del método
invalidado en lo que respecta a las restricciones de tipo.
Al reemplazar un método en un tipo genérico base, el método de reemplazo debe proporcionar los argumentos
de tipo que corresponden a los parámetros de tipo base. Por ejemplo:
Class Base(Of U, V)
Public Overridable Sub M(x As U, y As List(Of V))
End Sub
End Class
Class Derived(Of W, X)
Inherits Base(Of W, X)
Class MoreDerived
Inherits Derived(Of String, Integer)
Tenga en cuenta que es posible que un método reemplazable en una clase genérica no pueda reemplazarse en
algunos conjuntos de argumentos de tipo. Si se declara el método MustOverride , esto significa que es posible
que algunas cadenas de herencia no sean posibles. Por ejemplo:
Class Derived
Inherits Base(Of Integer, Integer)
Una declaración de invalidación puede tener acceso al método base invalidado mediante un acceso base, como
en el ejemplo siguiente:
Class Base
Private x As Integer
Class Derived
Inherits Base
Private y As Integer
Solo cuando incluye un Overrides modificador puede reemplazar a otro método. En todos los demás casos, un
método con la misma signatura que un método heredado simplemente prevalece sobre el método heredado,
como en el ejemplo siguiente:
Class Base
Public Overridable Sub F()
End Sub
End Class
Class Derived
Inherits Base
En el ejemplo, el método F de la clase no Derived incluye un Overrides modificador y, por tanto, no invalida
el método F en la clase Base . En su lugar, F el método de Derived la clase prevalece sobre el método de la
clase Base y se genera una advertencia porque la declaración no incluye un Shadows Overloads modificador o.
En el ejemplo siguiente, F el método de Derived la clase prevalece sobre el método reemplazable F
heredado de la clase Base :
Class Base
Public Overridable Sub F()
End Sub
End Class
Class Derived
Inherits Base
Class MoreDerived
Inherits Derived
Dado que el nuevo método F de la clase Derived tiene Private acceso, su ámbito solo incluye el cuerpo de la
clase de Derived y no se extiende a la clase MoreDerived . F Por tanto, se permite que la declaración del
método en la clase MoreDerived invalide el método F heredado de la clase Base .
Cuando Overridable se invoca un método, se llama a la implementación más derivada del método de instancia,
basándose en el tipo de la instancia, independientemente de si la llamada es al método de la clase base o la
clase derivada. La implementación más derivada de un Overridable método M con respecto a una clase R se
determina de la manera siguiente:
Si R contiene la Overridable declaración de introducción de M , se trata de la implementación más
derivada de M .
De lo contrario, si R contiene una invalidación de M , se trata de la implementación más derivada de M
.
De lo contrario, la implementación más derivada de M es la misma que la de la clase base directa de R .
Accesibilidad
Una declaración especifica la accesibilidad de la entidad que declara. La accesibilidad de una entidad no cambia
el ámbito del nombre de una entidad. El dominio de accesibilidad de una declaración es el conjunto de todos los
espacios de declaración en los que se puede tener acceso a la entidad declarada.
Los cinco tipos de acceso son Public ,, Protected Friend , Protected Friend y Private . Public es el tipo de
acceso más permisivo y los otros cuatro tipos son subconjuntos de Public . El tipo de acceso menos permisivo
es Private , y los otros cuatro tipos de acceso son todos los superconjuntos de Private .
AccessModifier
: 'Public'
| 'Protected'
| 'Friend'
| 'Private'
| 'Protected' 'Friend'
;
El tipo de acceso de una declaración se especifica mediante un modificador de acceso opcional, que puede ser
Public , Protected , Friend , Private o la combinación de Protected y Friend . Si no se especifica ningún
modificador de acceso, el tipo de acceso predeterminado depende del contexto de la declaración; los tipos de
acceso permitidos también dependen del contexto de la declaración.
Las entidades declaradas con el Public modificador tienen Public acceso. No hay restricciones en el
uso de Public entidades.
Las entidades declaradas con el Protected modificador tienen Protected acceso. Protected el acceso
solo puede especificarse en miembros de clases (tanto miembros de tipo normales como clases
anidadas) o en Overridable miembros de módulos y estructuras estándar (que deben heredarse, por
definición, de System.Object o System.ValueType ). Un Protected miembro es accesible a una clase
derivada, siempre que el miembro no sea un miembro de instancia o el acceso tenga lugar a través de
una instancia de la clase derivada. Protected el acceso no es un superconjunto de Friend acceso.
Las entidades declaradas con el Friend modificador tienen Friend acceso. Una entidad con Friend
acceso solo es accesible dentro del programa que contiene la declaración de la entidad o cualquier
ensamblado al que se haya concedido Friend acceso a través del
System.Runtime.CompilerServices.InternalsVisibleToAttribute atributo.
Las entidades declaradas con los Protected Friend modificadores tienen la Unión de Protected y el
Friend acceso.
Las entidades declaradas con el Private modificador tienen Private acceso. Una Private entidad solo
es accesible dentro de su contexto de declaración, incluidas las entidades anidadas.
La accesibilidad en una declaración no depende de la accesibilidad del contexto de la declaración. Por ejemplo,
un tipo declarado con Private acceso puede contener un miembro de tipo con Public acceso.
En el código siguiente se muestran varios dominios de accesibilidad:
Public Class A
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
End Class
Friend Class B
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
Public Class C
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
End Class
Private Class D
Public Shared X As Integer
Friend Shared Y As Integer
Private Shared Z As Integer
End Class
End Class
Las clases y los miembros de este ejemplo tienen los siguientes dominios de accesibilidad:
El dominio de accesibilidad de A y A.X es ilimitado.
El dominio de accesibilidad de A.Y , B , B.X , B.Y , B.C , B.C.X y B.C.Y es el programa que lo
contiene.
El dominio de accesibilidad de A.Z es A.
El acceso a Protected los miembros de instancia debe realizarse a través de una instancia del tipo derivado
para que los tipos no relacionados no puedan tener acceso a los miembros protegidos de los demás. Por
ejemplo:
Class User
Protected Password As String
End Class
Class Employee
Inherits User
End Class
Class Guest
Inherits User
En el ejemplo anterior, la clase Guest solo tiene acceso al campo protegido Password si está calificado con una
instancia de Guest . Esto evita que Guest obtenga acceso al Password campo de un Employee objeto
simplemente convirtiéndolo en User .
Para los fines de Protected acceso a miembros en tipos genéricos, el contexto de la Declaración incluye
parámetros de tipo. Esto significa que un tipo derivado con un conjunto de argumentos de tipo no tiene acceso
a los Protected miembros de un tipo derivado con un conjunto diferente de argumentos de tipo. Por ejemplo:
Class Base(Of T)
Protected x As T
End Class
Class Derived(Of T)
Inherits Base(Of T)
Nota. El lenguaje C# (y posiblemente otros lenguajes) permite que un tipo genérico tenga acceso a Protected
los miembros, independientemente de los argumentos de tipo que se proporcionen. Esto debe tenerse en
cuenta al diseñar clases genéricas que contienen Protected miembros.
Tipos constituyentes
Los tipos constituyentes de una declaración son los tipos a los que hace referencia la declaración. Por ejemplo, el
tipo de una constante, el tipo de valor devuelto de un método y los tipos de parámetro de un constructor son
todos los tipos constituyentes. El dominio de accesibilidad de un tipo constituyente de una declaración debe ser
el mismo que o un supraconjunto del dominio de accesibilidad de la propia declaración. Por ejemplo:
Public Class X
Private Class Y
End Class
Friend Class B
Private Class C
End Class
Class A ' A.
End Class
Namespace X ' X.
Class B ' X.B.
Class C ' X.B.C.
End Class
End Class
Observe que el espacio de nombres X. Y se ha declarado en dos ubicaciones diferentes en el código fuente, pero
estas dos declaraciones parciales constituyen solo un espacio de nombres denominado X. Y que contiene las
clases D y E.
En algunas situaciones, un nombre completo puede comenzar con la palabra clave Global . La palabra clave
representa el espacio de nombres exterior sin nombre, que es útil en situaciones en las que una declaración
sombrea un espacio de nombres envolvente. La Global palabra clave permite "escapar" en el espacio de
nombres más externo en esa situación. Por ejemplo:
Namespace NS1
Class System
End Class
Module Test
Sub Main()
' Error: Class System does not contain Int32
Dim x As System.Int32
En el ejemplo anterior, la primera llamada al método no es válida porque el identificador se System enlaza a la
clase System , no al espacio de nombres System . La única manera de tener acceso al System espacio de
nombres es usar Global para escapar al espacio de nombres más externo. Global no se puede usar en una
Imports instrucción o Namespace declaración.
Dado que otros lenguajes pueden introducir tipos y espacios de nombres que coincidan con las palabras clave
del lenguaje, Visual Basic reconoce que las palabras clave forman parte de un nombre completo siempre que
sigan un punto. Las palabras clave utilizadas de esta manera se tratan como identificadores. Por ejemplo, el
identificador calificado X.Default.Class es un identificador calificado válido, mientras que Default.Class no es.
Resolución de nombres completa para espacios de nombres y tipos
Dado un espacio de nombres o un nombre de tipo calificados con el formato N.R(Of A) , donde R es el
identificador más a la derecha en el nombre completo y A es una lista de argumentos de tipo opcional, los
pasos siguientes describen cómo determinar a qué espacio de nombres o tipo se refiere el nombre completo:
1. Resuelva N el uso de las reglas para la resolución de nombres calificada o no calificada.
2. Si N se produce un error en la resolución de o se resuelve en un parámetro de tipo, se produce un error
en tiempo de compilación.
3. De lo contrario, si R coincide con el nombre de un espacio de nombres en N y no se proporcionó ningún
argumento de tipo, o R coincide con un tipo accesible en N con el mismo número de parámetros de
tipo que los argumentos de tipo, si hay alguno, el nombre completo hace referencia a ese espacio de
nombres o tipo.
4. De lo contrario, si N contiene uno o más módulos estándar y R coincide con el nombre de un tipo
accesible con el mismo número de parámetros de tipo que los argumentos de tipo, si hay alguno, en
exactamente un módulo estándar, el nombre completo hace referencia a ese tipo. Si R coincide con el
nombre de los tipos accesibles con el mismo número de parámetros de tipo que los argumentos de tipo,
si los hay, en más de un módulo estándar, se produce un error en tiempo de compilación.
5. De lo contrario, se produce un error en tiempo de compilación.
Nota. Una implicación de este proceso de resolución es que los miembros de tipo no sombrean los espacios de
nombres o tipos al resolver nombres de espacio de nombres o de tipo.
Resolución de nombres sin calificar para espacios de nombres y tipos
Dado un nombre no completo R(Of A) , donde A es una lista de argumentos de tipo opcional, en los pasos
siguientes se describe cómo determinar el espacio de nombres o el tipo al que hace referencia el nombre no
completo:
1. Si R coincide con el nombre de un parámetro de tipo del método actual y no se proporcionó ningún
argumento de tipo, el nombre no completo hace referencia a ese parámetro de tipo.
2. Para cada tipo anidado que contenga la referencia de nombre, empezando por el tipo más interno y
pasando al exterior:
a. Si R coincide con el nombre de un parámetro de tipo en el tipo actual y no se proporcionó ningún
argumento de tipo, el nombre no completo hace referencia a ese parámetro de tipo.
b. De lo contrario, si R coincide con el nombre de un tipo anidado accesible con el mismo número de
parámetros de tipo que los argumentos de tipo, si hay alguno, el nombre no completo hace referencia
a ese tipo.
3. Para cada espacio de nombres anidado que contenga la referencia de nombre, empezando por el espacio
de nombres más interno y yendo al espacio de nombres más externo:
a. Si R coincide con el nombre de un espacio de nombres anidado en el espacio de nombres actual y no
se proporciona ninguna lista de argumentos de tipo, el nombre no completo hace referencia a ese
espacio de nombres anidado.
b. De lo contrario, si R coincide con el nombre de un tipo accesible con el mismo número de
parámetros de tipo que los argumentos de tipo, si hay alguno, en el espacio de nombres actual, el
nombre no completo hace referencia a ese tipo.
c. De lo contrario, si el espacio de nombres contiene uno o más módulos estándar accesibles y R
coincide con el nombre de un tipo anidado accesible con el mismo número de parámetros de tipo que
los argumentos de tipo, si hay alguno, en exactamente un módulo estándar, el nombre no completo
hace referencia a ese tipo anidado. Si R coincide con el nombre de los tipos anidados accesibles con
el mismo número de parámetros de tipo que los argumentos de tipo, si los hay, en más de un módulo
estándar, se produce un error en tiempo de compilación.
4. Si el archivo de origen tiene uno o varios alias de importación y R coincide con el nombre de uno de
ellos, el nombre no completo hace referencia a ese alias de importación. Si se proporciona una lista de
argumentos de tipo, se produce un error en tiempo de compilación.
5. Si el archivo de código fuente que contiene la referencia de nombre tiene una o más importaciones:
a. Si R coincide con el nombre de un tipo accesible con el mismo número de parámetros de tipo que
los argumentos de tipo, si hay alguno, en exactamente una importación, el nombre no completo hace
referencia a ese tipo. Si R coincide con el nombre de un tipo accesible con el mismo número de
parámetros de tipo que los argumentos de tipo, si hay alguno, en más de una importación y todos no
son del mismo tipo, se produce un error en tiempo de compilación.
b. De lo contrario, si no se proporcionó ninguna lista de argumentos de tipo y R coincide con el nombre
de un espacio de nombres con tipos accesibles en exactamente una importación, el nombre no
completo hace referencia a ese espacio de nombres. Si no se proporcionó ninguna lista de
argumentos de tipo y R coincide con el nombre de un espacio de nombres con tipos accesibles en
más de una importación y todos no son el mismo espacio de nombres, se produce un error en tiempo
de compilación.
c. En caso contrario, si las importaciones contienen uno o más módulos estándar accesibles y R
coincide con el nombre de un tipo anidado accesible con el mismo número de parámetros de tipo que
los argumentos de tipo, si hay alguno, en exactamente un módulo estándar, el nombre no completo
hace referencia a ese tipo. Si R coincide con el nombre de los tipos anidados accesibles con el mismo
número de parámetros de tipo que los argumentos de tipo, si los hay, en más de un módulo estándar,
se produce un error en tiempo de compilación.
6. Si el entorno de compilación define uno o varios alias de importación y R coincide con el nombre de
uno de ellos, el nombre no completo hace referencia a ese alias de importación. Si se proporciona una
lista de argumentos de tipo, se produce un error en tiempo de compilación.
7. Si el entorno de compilación define una o varias importaciones:
a. Si R coincide con el nombre de un tipo accesible con el mismo número de parámetros de tipo que
los argumentos de tipo, si hay alguno, en exactamente una importación, el nombre no completo hace
referencia a ese tipo. Si R coincide con el nombre de un tipo accesible con el mismo número de
parámetros de tipo que los argumentos de tipo, si hay alguno, en más de una importación, se produce
un error en tiempo de compilación.
b. De lo contrario, si no se proporcionó ninguna lista de argumentos de tipo y R coincide con el nombre
de un espacio de nombres con tipos accesibles en exactamente una importación, el nombre no
completo hace referencia a ese espacio de nombres. Si no se proporcionó ninguna lista de
argumentos de tipo y R coincide con el nombre de un espacio de nombres con tipos accesibles en
más de una importación, se produce un error en tiempo de compilación.
c. En caso contrario, si las importaciones contienen uno o más módulos estándar accesibles y R
coincide con el nombre de un tipo anidado accesible con el mismo número de parámetros de tipo que
los argumentos de tipo, si hay alguno, en exactamente un módulo estándar, el nombre no completo
hace referencia a ese tipo. Si R coincide con el nombre de los tipos anidados accesibles con el mismo
número de parámetros de tipo que los argumentos de tipo, si los hay, en más de un módulo estándar,
se produce un error en tiempo de compilación.
8. De lo contrario, se produce un error en tiempo de compilación.
Nota. Una implicación de este proceso de resolución es que los miembros de tipo no sombrean los espacios de
nombres o tipos al resolver nombres de espacio de nombres o de tipo.
Normalmente, un nombre solo puede aparecer una vez en un espacio de nombres determinado. Sin embargo,
dado que los espacios de nombres se pueden declarar en varios ensamblados .NET, es posible tener una
situación en la que dos ensamblados definen un tipo con el mismo nombre completo. En ese caso, se prefiere
un tipo declarado en el conjunto actual de archivos de código fuente a un tipo declarado en un ensamblado .NET
externo. De lo contrario, el nombre es ambiguo y no hay ninguna manera de eliminar la ambigüedad del
nombre.
variables
Una variable representa una ubicación de almacenamiento. Cada variable tiene un tipo que determina qué
valores se pueden almacenar en la variable. Dado que Visual Basic es un lenguaje con seguridad de tipos, cada
variable de un programa tiene un tipo y el lenguaje garantiza que los valores almacenados en variables sean
siempre del tipo adecuado. Las variables siempre se inicializan en el valor predeterminado de su tipo antes de
que se pueda realizar cualquier referencia a la variable. No es posible tener acceso a la memoria no inicializada.
Items(CurrentIndex) = Data
CurrentIndex += 1
End Sub
CurrentIndex -= 1
Return Items(CurrentIndex + 1)
End Function
End Class
Las declaraciones que usan la Stack(Of ItemType) clase deben proporcionar un argumento de tipo para el
parámetro de tipo ItemType . A continuación, este tipo se rellena siempre ItemType que se use dentro de la
clase:
Option Strict On
Module Test
Sub Main()
Dim s1 As New Stack(Of Integer)()
Dim s2 As New Stack(Of Double)()
Parámetros de tipo
Los parámetros de tipo se pueden proporcionar en declaraciones de tipo o método. Cada parámetro de tipo es
un identificador que es un marcador de posición para un argumento de tipo que se proporciona para crear un
tipo o método construido. Por el contrario, un argumento de tipo es el tipo real que se sustituye por el
parámetro de tipo cuando se usa un tipo o un método genérico.
TypeParameterList
: OpenParenthesis 'Of' TypeParameter ( Comma TypeParameter )* CloseParenthesis
;
TypeParameter
: VarianceModifier? Identifier TypeParameterConstraints?
;
VarianceModifier
: 'In' | 'Out'
;
Cada parámetro de tipo de una declaración de tipo o método define un nombre en el espacio de declaración de
ese tipo o método. Por lo tanto, no puede tener el mismo nombre que otro parámetro de tipo, un miembro de
tipo, un parámetro de método o una variable local. El ámbito de un parámetro de tipo en un tipo o método es
todo el tipo o método. Dado que los parámetros de tipo se limitan a la declaración de tipos completa, los tipos
anidados pueden usar parámetros de tipo externo. Esto también significa que siempre se deben especificar los
parámetros de tipo al obtener acceso a los tipos anidados dentro de los tipos genéricos:
Module Test
Sub Main()
Dim x As New Outer(Of Integer).Inner()
...
End Sub
End Module
A diferencia de otros miembros de una clase, los parámetros de tipo no se heredan. Solo se puede hacer
referencia a los parámetros de tipo de un tipo mediante su nombre simple; en otras palabras, no se pueden
calificar con el nombre de tipo contenedor. Aunque el estilo de programación es incorrecto, los parámetros de
tipo de un tipo anidado pueden ocultar un miembro o un parámetro de tipo declarado en el tipo externo:
Class Outer(Of T)
Class Inner(Of T)
Public t1 As T ' Refers to Inner's T
End Class
End Class
Los tipos y métodos se pueden sobrecargar en función del número de parámetros de tipo (o aridad) que
declaran los tipos o métodos. Por ejemplo, las siguientes declaraciones son válidas:
Module C
Sub M()
End Sub
Structure C(Of T)
Dim x As T
End Structure
Class C(Of T, U)
End Class
En el caso de los tipos, las sobrecargas siempre coinciden con el número de argumentos de tipo especificado.
Esto resulta útil cuando se usan clases genéricas y no genéricas juntas en el mismo programa:
Class Queue
End Class
Class Queue(Of T)
End Class
Class X
Dim q1 As Queue ' Non-generic queue
Dim q2 As Queue(Of Integer) ' Generic queue
End Class
Las reglas de los métodos sobrecargados en los parámetros de tipo se describen en la sección sobre la
resolución de sobrecarga del método.
Dentro de la declaración contenedora, los parámetros de tipo se consideran tipos completos. Dado que se
pueden crear instancias de un parámetro de tipo con muchos argumentos de tipo reales diferentes, los
parámetros de tipo tienen operaciones y restricciones ligeramente diferentes de las que se describen a
continuación:
Un parámetro de tipo no se puede usar directamente para declarar una clase base o una interfaz.
Las reglas para la búsqueda de miembros en parámetros de tipo dependen de las restricciones, si las hay,
que se aplican al parámetro de tipo.
Las conversiones disponibles para un parámetro de tipo dependen de las restricciones, si las hay, que se
aplican a los parámetros de tipo.
En ausencia de una Structure restricción, un valor con un tipo representado por un parámetro de tipo
puede compararse con Nothing mediante Is y IsNot .
Un parámetro de tipo solo se puede usar en una New expresión si el parámetro de tipo está restringido
por una New Structure restricción o.
Un parámetro de tipo no se puede usar en ninguna parte dentro de una excepción de atributo dentro de
una GetType expresión.
Los parámetros de tipo se pueden usar como argumentos de tipo para otros tipos y parámetros
genéricos.
El ejemplo siguiente es un tipo genérico que extiende la Stack(Of ItemType) clase:
Cuando una declaración proporciona un argumento de tipo a MyStack , también se aplica el mismo argumento
de tipo a Stack .
Como tipo, los parámetros de tipo son únicamente una construcción en tiempo de compilación. En tiempo de
ejecución, cada parámetro de tipo se enlaza a un tipo en tiempo de ejecución que se especificó proporcionando
un argumento de tipo a la declaración genérica. Por lo tanto, el tipo de una variable declarada con un parámetro
de tipo, en tiempo de ejecución, será un tipo no genérico o un tipo construido específico. La ejecución en tiempo
de ejecución de todas las instrucciones y expresiones que impliquen parámetros de tipo usa el tipo real que se
proporcionó como argumento de tipo para ese parámetro.
Restricciones de tipos
Dado que un argumento de tipo puede ser cualquier tipo en el sistema de tipos, un tipo o método genérico no
puede hacer ninguna suposición sobre un parámetro de tipo. Por lo tanto, los miembros de un parámetro de
tipo se consideran miembros del tipo Object , ya que todos los tipos se derivan de Object .
En el caso de una colección como Stack(Of ItemType) , este hecho podría no ser una restricción especialmente
importante, pero puede haber casos en los que un tipo genérico desee hacer una suposición sobre los tipos que
se proporcionarán como argumentos de tipo. Las restricciones de tipo se pueden colocar en parámetros de tipo
que restringen los tipos que se pueden proporcionar como parámetro de tipo y permiten a los tipos o métodos
genéricos asumir más información sobre los parámetros de tipo.
TypeParameterConstraints
: 'As' Constraint
| 'As' OpenCurlyBrace ConstraintList CloseCurlyBrace
;
ConstraintList
: Constraint ( Comma Constraint )*
;
Constraint
: TypeName
| 'New'
| 'Structure'
| 'Class'
;
Public Class DisposableStack(Of ItemType As IDisposable)
Implements IDisposable
En este ejemplo, DisposableStack(Of ItemType) restringe su parámetro de tipo a solo los tipos que implementan
la interfaz System.IDisposable . Como resultado, puede implementar un Dispose método que elimine cualquier
objeto que quede todavía en la cola.
Una restricción de tipo debe ser una de las restricciones especiales Class , Structure o New , o bien debe ser
un tipo T donde:
T es una clase, una interfaz o un parámetro de tipo.
T no es NotInheritable .
T no es uno de, o un tipo heredado de uno de los siguientes tipos especiales: System.Array ,
System.Delegate , System.MulticastDelegate , System.Enum o System.ValueType .
T no es Object . Dado que todos los tipos derivan de Object , este tipo de restricción no tendría ningún
efecto si se permitiera.
T debe ser al menos tan accesible como el tipo o método genérico que se está declarando.
Se pueden especificar varias restricciones de tipo para un parámetro de tipo único si se incluyen las
restricciones de tipo entre llaves ( {} ). Solo una restricción de tipo para un parámetro de tipo determinado
puede ser una clase. Es un error combinar una Structure restricción especial con una restricción de clase con
nombre o la Class restricción especial.
Las restricciones de tipo pueden usar los tipos contenedores o cualquiera de los parámetros de tipo de los tipos
contenedores. En el ejemplo siguiente, la restricción requiere que el argumento de tipo proporcionado
implemente una interfaz genérica que se usa a sí misma como un argumento de tipo:
La restricción de tipo especial New requiere que el argumento de tipo proporcionado debe tener un constructor
sin parámetros accesible y no se puede declarar MustInherit . Por ejemplo:
Una restricción de tipo de clase requiere que el argumento de tipo proporcionado deba ser de ese tipo o
heredar de él. Una restricción de tipo de interfaz requiere que el argumento de tipo proporcionado debe
implementar esa interfaz. Una restricción de parámetro de tipo requiere que el argumento de tipo
proporcionado debe derivar de o implementar todos los límites especificados para el parámetro de tipo
coincidente. Por ejemplo:
Class List(Of T)
Sub AddRange(Of S As T)(collection As IEnumerable(Of S))
...
End Sub
End Class
En este ejemplo, el parámetro de tipo S de AddRange está restringido al parámetro de tipo T de List . Esto
significa que un List(Of Control) AddRange parámetro de tipo de restringiría a cualquier tipo que sea o herede
de Control .
Una restricción de parámetro de tipo Of S As T se resuelve mediante la adición transitiva de todas las
restricciones de a T S , excepto las restricciones especiales ( Class , Structure , New ). Es un error tener
restricciones circulares (por ejemplo, Of S As T, T As S ). Es un error tener una restricción de parámetro de
tipo que tiene la Structure restricción. Después de agregar restricciones, es posible que se produzcan varias
situaciones especiales:
Si existen varias restricciones de clase, se considera que la clase más derivada es la restricción. Si una o
más restricciones de clase no tienen ninguna relación de herencia, la restricción es imposibles y es un
error.
Si un parámetro de tipo combina una Structure restricción especial con una restricción de clase con
nombre o la Class restricción especial, se trata de un error. Una restricción de clase puede ser
NotInheritable , en cuyo caso no se acepta ningún tipo derivado de esa restricción y es un error.
El tipo puede ser uno de, o un tipo heredado de, los siguientes tipos especiales: System.Array , System.Delegate
, System.MulticastDelegate , System.Enum o System.ValueType . En ese caso, solo se acepta el tipo o un tipo
heredado de él. Un parámetro de tipo restringido a uno de estos tipos solo puede utilizar las conversiones
permitidas por el DirectCast operador. Por ejemplo:
Class Derived
Inherits Base(Of Integer)
Además, un parámetro de tipo restringido a un tipo de valor debido a una de las relajaciones anteriores no
puede llamar a ningún método definido en ese tipo de valor. Por ejemplo:
Class C1(Of T)
Overridable Sub F(Of G As T)(x As G)
End Sub
End Class
Class C2
Inherits C1(Of IntPtr)
Si la restricción, después de la sustitución, termina como un tipo de matriz, también se permite cualquier tipo de
matriz covariante. Por ejemplo:
Module Test
Class B
End Class
Class D
Inherits B
End Class
Sub Main()
Dim a(9) As B
Dim b(9) As D
Se considera que un parámetro de tipo con una restricción de clase o interfaz tiene los mismos miembros que
esa restricción de clase o interfaz. Si un parámetro de tipo tiene varias restricciones, se considera que el
parámetro de tipo tiene la Unión de todos los miembros de las restricciones. Si hay miembros con el mismo
nombre en más de una restricción, los miembros se ocultan en el orden siguiente: la restricción de clase oculta
los miembros en las restricciones de interfaz, que ocultan los miembros de System.ValueType (si Structure se
especifica la restricción), que oculta los miembros en Object . Si un miembro con el mismo nombre aparece en
más de una restricción de interfaz, el miembro no está disponible (como en varias herencia de interfaz) y el
parámetro de tipo debe convertirse a la interfaz deseada. Por ejemplo:
Class C1
Sub S1(x As Integer)
End Sub
End Class
Interface I1
Sub S1(x As Integer)
End Interface
Interface I2
Sub S1(y As Double)
End Interface
Module Test
Sub T1(Of T As {C1, I1, I2})()
Dim a As T
a.S1(10) ' Calls C1.S1, which is preferred
a.S1(10.10) ' Also calls C1.S1, class is still preferred
End Sub
Al proporcionar parámetros de tipo como argumentos de tipo, los parámetros de tipo deben cumplir las
restricciones de los parámetros de tipo coincidentes.
Class Derived(Of V)
' Error: V does not satisfy the constraints of T
Inherits Base(Of V)
End Class
Los valores de un parámetro de tipo restringido se pueden utilizar para tener acceso a los miembros de
instancia, incluidos los métodos de instancia, especificados en la restricción.
Interface IPrintable
Sub Print()
End Interface
Class Derived
Inherits Base
End Class
Module Test
Sub Main()
Dim x As IEnumerable(Of Derived) = ...
Las interfaces genéricas que tienen parámetros de tipo con modificadores de varianza tienen varias
restricciones:
No pueden contener una declaración de evento que especifique una lista de parámetros (pero se permite
una declaración de evento personalizado o una declaración de evento con un tipo de delegado).
No pueden contener una clase anidada, una estructura o un tipo enumerado.
Nota. Estas restricciones se deben al hecho de que los tipos anidados en tipos genéricos copian implícitamente
los parámetros genéricos de su elemento primario. En el caso de las clases anidadas, estructuras o tipos
enumerados, esos tipos de tipos no pueden tener modificadores de varianza en sus parámetros de tipo. En el
caso de una declaración de evento con una lista de parámetros, la clase de delegado anidada generada podría
tener errores confusos cuando un tipo que parece usarse en una In posición (es decir, un tipo de parámetro) se
usa realmente en una Out posición (es decir, el tipo del evento).
Un parámetro de tipo que se declara con el modificador out es covariante. De manera informativa, un
parámetro de tipo covariante solo se puede usar en una posición de salida; es decir, un valor que se devuelve
desde el tipo de interfaz o delegado, y no se puede usar en una posición de entrada. T Se considera que un tipo
es válido de forma covariante si:
T es una clase, estructura o tipo enumerado.
T es un tipo de interfaz o delegado no genérico.
T es un tipo de matriz cuyo tipo de elemento es una covariante válida.
T es un parámetro de tipo que no se declaró como Out parámetro de tipo.
T es un tipo de interfaz o delegado construido X(Of P1,...,Pn) con argumentos de tipo A1,...,An
que:
Si Pi se declaró como un parámetro de tipo out, entonces Ai es una covariante válida.
Si Pi se declaró como un parámetro de tipo in, entonces Ai es un contravariantly válido.
Los siguientes elementos deben ser covariantes válidos en un tipo de interfaz o delegado:
La interfaz base de una interfaz.
El tipo de valor devuelto de una función o el tipo de delegado.
El tipo de una propiedad si hay un Get descriptor de acceso.
Tipo de cualquier ByRef parámetro.
Por ejemplo:
Interface I1(Of In T)
End Interface
Interface I2(Of In T)
' OK, T is only used in an In position
Sub M1(x As I1(Of T))
En el caso de que un tipo debe ser válido como contravariantly y covariante (como una propiedad con un Get
Set descriptor de acceso y o un ByRef parámetro), no se puede usar un parámetro de tipo Variant.
Class C
Implements IEnumerable(Of String)
Implements IEnumerable(Of Exception)
El lenguaje Visual Basic permite al programador especificar modificadores en las declaraciones, que representan
información sobre las entidades que se declaran. Por ejemplo, la colocación de un método de clase con los
modificadores Public ,, Protected Friend , Protected Friend o Private especifica su accesibilidad.
Además de los modificadores definidos por el lenguaje, Visual Basic también permite a los programadores crear
nuevos modificadores, denominados atributos, y utilizarlos al declarar nuevas entidades. Estos nuevos
modificadores, que se definen a través de la declaración de clases de atributos, se asignan a entidades a través
de bloques de atributos.
Nota. Los atributos se pueden recuperar en tiempo de ejecución a través de las API de reflexión del .NET
Framework. Estas API están fuera del ámbito de esta especificación.
Por ejemplo, un marco podría definir un Help atributo que se puede colocar en elementos de programa, como
clases y métodos, para proporcionar una asignación de los elementos del programa a la documentación, como
se muestra en el ejemplo siguiente:
<AttributeUsage(AttributeTargets.All)> _
Public Class HelpAttribute
Inherits Attribute
En el ejemplo se define una clase HelpAttribute de atributo denominada, o Help para abreviar, que tiene un
parámetro posicional ( UrlValue ) y un argumento con nombre ( Topic ).
En el ejemplo siguiente se muestran varios usos del atributo:
<Help("http://www.example.com/.../Class1.htm")> _
Public Class Class1
<Help("http://www.example.com/.../Class1.htm", Topic:="F")> _
Public Sub F()
End Sub
End Class
En el ejemplo siguiente se comprueba si Class1 tiene un Help atributo y se escriben los Topic valores y
asociados Url si el atributo está presente.
Module Test
Sub Main()
Dim type As Type = GetType(Class1)
Dim arr() As Object = _
type.GetCustomAttributes(GetType(HelpAttribute), True)
If arr.Length = 0 Then
Console.WriteLine("Class1 has no Help attribute.")
Else
Dim ha As HelpAttribute = CType(arr(0), HelpAttribute)
Console.WriteLine("Url = " & ha.Url & ", Topic = " & ha.Topic)
End If
End Sub
End Module
Clases de atributos
Una clase de atributo es una clase no genérica que se deriva de System.Attribute y no es MustInherit . La clase
de atributo puede tener un System.AttributeUsage atributo que declara el atributo válido en, si se puede usar
varias veces en una declaración y si se hereda. En el ejemplo siguiente se define una clase SimpleAttribute de
atributo denominada que se puede colocar en declaraciones de clase y declaraciones de interfaz:
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Interface)> _
Public Class SimpleAttribute
Inherits System.Attribute
End Class
En el ejemplo siguiente se muestran algunos usos del Simple atributo. Aunque la clase de atributo se denomina
SimpleAttribute , los usos de este atributo pueden omitir el Attribute sufijo, lo que reduce el nombre a
Simple :
En el ejemplo se muestra una declaración de clase con dos usos del Author atributo:
El System.AttributeUsage atributo tiene una variable de instancia pública, Inherited , que especifica si el
atributo, cuando se especifica en un tipo base, también es heredado por los tipos que derivan de este tipo base.
Si Inherited no se inicializa la variable de instancia pública, se usa un valor predeterminado de False . Las
propiedades y los eventos no heredan atributos, aunque los métodos definidos por propiedades y eventos sí lo
hacen. Las interfaces no heredan atributos.
Si un atributo de uso único se hereda y se especifica en un tipo derivado, el atributo especificado en el tipo
derivado invalida el atributo heredado. Si un atributo de varios uso se hereda y se especifica en un tipo
derivado, ambos atributos se especifican en el tipo derivado. Por ejemplo:
<AttributeUsage(AttributeTargets.Class, AllowMultiple:=True, _
Inherited:=True) > _
Class MultiUseAttribute
Inherits System.Attribute
<AttributeUsage(AttributeTargets.Class, Inherited:=True)> _
Class SingleUseAttribute
Inherits Attribute
Los parámetros de posición del atributo se definen mediante los parámetros de los constructores públicos de la
clase de atributo. Los parámetros posicionales deben ser ByVal y no pueden especificar ByRef . Las variables y
propiedades de instancia pública se definen mediante propiedades de lectura y escritura públicas o variables de
instancia de la clase de atributo. Los tipos que se pueden usar en los parámetros posicionales y en las
propiedades y variables de instancia pública se restringen a los tipos de atributo. Un tipo es un tipo de atributo
si es uno de los siguientes:
Cualquier tipo primitivo excepto para Date y Decimal .
El tipo Object .
El tipo System.Type .
Tipo enumerado, siempre y cuando los tipos en los que está anidado (si los hubiera) tengan Public
accesibilidad.
Matriz unidimensional de uno de los tipos anteriores de esta lista.
Bloques de atributos
Los atributos se especifican en bloques de atributos. Cada bloque de atributos está delimitado por corchetes
angulares ("<>") y se pueden especificar varios atributos en una lista separada por comas dentro de un bloque
de atributos o en varios bloques de atributos. El orden en que se especifican los atributos no es significativo. Por
ejemplo, los bloques de <A, B> atributos <B, A> , <A> <B> y <B> <A> son equivalentes.
Attributes
: AttributeBlock+
;
AttributeBlock
: LineTerminator? '<' AttributeList LineTerminator? '>' LineTerminator?
;
AttributeList
: Attribute ( Comma Attribute )*
;
Attribute
: ( AttributeModifier ':' )? SimpleTypeName
( OpenParenthesis AttributeArguments? CloseParenthesis )?
;
AttributeModifier
: 'Assembly' | 'Module'
;
No se puede especificar un atributo en un tipo de declaración que no admita, y los atributos de un solo uso no
se pueden especificar más de una vez en un bloque de atributos. En el ejemplo siguiente se producen errores
porque intentan usar HelpString en la interfaz Interface1 y más de una vez en la declaración de Class1 .
<AttributeUsage(AttributeTargets.Class)> _
Public Class HelpStringAttribute
Inherits System.Attribute
Un atributo está compuesto de un modificador de atributo opcional, un nombre de atributo, una lista opcional
de argumentos posicionales y los inicializadores de variable/propiedad. Si no hay ningún parámetro o
inicializador, se pueden omitir los paréntesis. Si un atributo tiene un modificador, debe estar en un bloque de
atributos situado en la parte superior de un archivo de código fuente.
Si un archivo de código fuente contiene un bloque de atributos en la parte superior del archivo que especifica
los atributos del ensamblado o módulo que va a contener el archivo de código fuente, cada atributo del bloque
de atributos debe ir precedido del Assembly Module modificador o y dos puntos.
Nombres de atributo
El nombre de un atributo especifica una clase de atributo. Por Convención, las clases de atributo se denominan
con el sufijo Attribute . Los usos de un atributo pueden incluir u omitir este sufijo. Por consiguiente, el nombre
de una clase de atributo que se corresponde con un identificador de atributo es el propio identificador o la
concatenación del identificador calificado y Attribute . Cuando el compilador resuelve un nombre de atributo,
se anexa Attribute al nombre y se intenta realizar la búsqueda. Si se produce un error en esa búsqueda, el
compilador intenta la búsqueda sin el sufijo. Por ejemplo, los usos de una clase de atributo SimpleAttribute
pueden omitir el Attribute sufijo, lo que reduce el nombre a Simple :
En general, se prefieren los atributos denominados con el sufijo Attribute . En el ejemplo siguiente se
muestran dos clases de atributos denominadas T y T``Attribute .
<AttributeUsage(AttributeTargets.All)> _
Public Class T
Inherits System.Attribute
End Class
<AttributeUsage(AttributeTargets.All)> _
Public Class TAttribute
Inherits System.Attribute
End Class
Tanto el bloque de atributos <T> como el bloque de atributos <TAttribute> hacen referencia a la clase de
atributo denominada TAttribute . No es posible utilizar T como atributo hasta que quite la declaración de la
clase TAttribute .
Argumentos de atributo
Los argumentos de un atributo pueden tomar dos formas: argumentos posicionales y inicializadores de
variable/propiedad. Cualquier argumento posicional para el atributo debe preceder a los inicializadores de
propiedad/variable de instancia. Un argumento posicional consta de una expresión constante, una expresión de
creación de matriz unidimensional o una GetType expresión. Un inicializador de variable/propiedad de instancia
está compuesto de un identificador, que puede coincidir con palabras clave, seguido de un signo de dos puntos
y un signo igual, y terminada por una expresión constante o una GetType expresión.
Dado un atributo con la clase de atributo T , la lista de argumentos posicionales P y la lista de inicializadores
de variable/propiedad de instancia N , estos pasos determinan si los argumentos son válidos:
1. Siga los pasos de procesamiento en tiempo de compilación para compilar una expresión del formulario
New T(P) . Esto da como resultado un error en tiempo de compilación o determina un constructor en T
que es más aplicable a la lista de argumentos.
2. Si el constructor determinado en el paso 1 tiene parámetros que no son tipos de atributo o que no son
accesibles en el sitio de declaración, se produce un error en tiempo de compilación.
3. Para cada inicializador de variable/propiedad Arg de instancia de N , supongamos que Name es el
identificador del inicializador de propiedad/variable de instancia Arg . Name debe identificar una
propiedad no Shared , grabable, Public variable de instancia o sin parámetros en T cuyo tipo sea un
tipo de atributo. Si T no tiene tal variable o propiedad de instancia, se producirá un error en tiempo de
compilación.
Por ejemplo:
<AttributeUsage(AttributeTargets.All)> _
Public Class GeneralAttribute
Inherits Attribute
Public y As Type
Set
End Set
End Property
End Class
Los parámetros de tipo no se pueden usar en ninguna parte de los argumentos de atributo. Sin embargo, se
pueden usar tipos construidos:
<AttributeUsage(AttributeTargets.All)> _
Class A
Inherits System.Attribute
Class List(Of T)
' Error: attribute argument cannot use type parameter
<A(GetType(T))> Dim t1 As T
AttributePositionalArgumentList
: AttributeArgumentExpression? ( Comma AttributeArgumentExpression? )*
;
VariablePropertyInitializerList
: VariablePropertyInitializer ( Comma VariablePropertyInitializer )*
;
VariablePropertyInitializer
: IdentifierOrKeyword ColonEquals AttributeArgumentExpression
;
AttributeArgumentExpression
: ConstantExpression
| GetTypeExpression
| ArrayExpression
;
Archivos de código fuente y espacios de nombres
15/11/2021 • 32 minutes to read
Un programa Visual Basic consta de uno o varios archivos de código fuente. Cuando se compila un programa,
todos los archivos de código fuente se procesan juntos; por lo tanto, los archivos de origen pueden depender
unos de otros, posiblemente de manera circular, sin ningún requisito de declaración de avance. El orden textual
de las declaraciones en el texto del programa no suele ser significativo.
Un archivo de código fuente se compone de un conjunto opcional de instrucciones de opciones, instrucciones
de importación y atributos, que van seguidos de un cuerpo de espacio de nombres. Los atributos, que deben
tener cada uno de ellos tienen el Assembly Module modificador o, se aplican al ensamblado o módulo .net
generado por la compilación. El cuerpo del archivo de código fuente funciona como una declaración de espacio
de nombres implícita para el espacio de nombres global, lo que significa que todas las declaraciones en el nivel
superior de un archivo de código fuente se colocan en el espacio de nombres global. Por ejemplo:
Archivoa. VB:
Class A
End Class
FileB. VB:
Class B
End Class
Los dos archivos de código fuente contribuyen al espacio de nombres global; en este caso, se declaran dos
clases con los nombres completos A y B . Dado que los dos archivos de código fuente contribuyen al mismo
espacio de declaración, habría sido un error si cada uno contenía una declaración de un miembro con el mismo
nombre.
Nota. El entorno de compilación puede invalidar las declaraciones de espacio de nombres en las que se coloca
implícitamente un archivo de código fuente.
Excepto en los casos en los que se indique, las instrucciones dentro de un programa Visual Basic pueden
finalizar mediante un terminador de línea o dos puntos.
Start
: OptionStatement* ImportsStatement* AttributesStatement* NamespaceMemberDeclaration*
;
StatementTerminator
: LineTerminator
| ':'
;
AttributesStatement
: Attributes StatementTerminator
;
Sub Main()
Sub Main(args() As String)
Function Main() As Integer
Function Main(args() As String) As Integer
La accesibilidad del método de punto de entrada es irrelevante. Si un programa contiene más de un punto de
entrada adecuado, el entorno de compilación debe designar uno como punto de entrada. De lo contrario, se
produce un error en tiempo de compilación. El entorno de compilación también puede crear un método de
punto de entrada si no existe uno.
Cuando se inicia un programa, si el punto de entrada tiene un parámetro, el argumento proporcionado por el
entorno de ejecución contiene los argumentos de la línea de comandos para el programa representado como
cadenas. Si el punto de entrada tiene un tipo de valor devuelto de Integer , el valor devuelto de la función se
devuelve al entorno de ejecución como resultado del programa.
En todos los demás aspectos, los métodos de punto de entrada se comportan de la misma manera que otros
métodos. Cuando la ejecución deja la invocación del método de punto de entrada realizado por el entorno de
ejecución, el programa finaliza.
Opciones de compilación
Un archivo de código fuente puede especificar opciones de compilación en el código fuente mediante
instrucciones Option.
OptionStatement
: OptionExplicitStatement
| OptionStrictStatement
| OptionCompareStatement
| OptionInferStatement
;
Una Option instrucción solo se aplica al archivo de código fuente en el que aparece y solo una de cada tipo de
Option instrucción puede aparecer en un archivo de código fuente. Por ejemplo:
Option Strict On
Option Compare Text
Option Strict Off ' Not allowed, Option Strict is already specified.
Option Compare Text ' Not allowed, Option Compare is already specified.
Hay cuatro opciones de compilación: semántica de tipos estricta, semántica de declaración explícita, semántica
de comparación y semántica de inferencia de tipo de variable local. Si un archivo de código fuente no incluye
una Option instrucción determinada, el entorno de compilación determina qué conjunto concreto de semántica
se usará. También hay una quinta opción de compilación, comprobaciones de desbordamiento de enteros, que
solo se pueden especificar a través del entorno de compilación.
Instrucción Option Explicit
La Option Explicit instrucción determina si las variables locales se pueden declarar implícitamente. Las
palabras clave On o Off pueden seguir a la instrucción; si no se especifica ninguno, el valor predeterminado es
On . Si no se especifica ninguna instrucción en un archivo, el entorno de compilación determina cuál se usará.
OptionExplicitStatement
: 'Option' 'Explicit' OnOff? StatementTerminator
;
OnOff
: 'On' | 'Off'
;
Module Test
Sub Main()
x = 5 ' Valid because Option Explicit is off.
End Sub
End Module
En este ejemplo, la variable local x se declara implícitamente mediante la asignación a ella. El tipo de x es
Object .
OptionStrictStatement
: 'Option' 'Strict' OnOff? StatementTerminator
;
Option Strict On
Module Test
Sub Main()
Dim x ' Error, no type specified.
Dim o As Object
Dim b As Byte = o ' Error, narrowing conversion.
OptionCompareStatement
: 'Option' 'Compare' CompareOption StatementTerminator
;
CompareOption
: 'Binary' | 'Text'
;
Module Test
Sub Main()
Console.WriteLine("a" = "A") ' Prints True.
End Sub
End Module
En este caso, la comparación de cadenas se realiza mediante una comparación de texto que omite las diferencias
entre mayúsculas y minúsculas. Si Option Compare Binary se hubiera especificado, se habría impreso False .
Comprobaciones de desbordamiento de enteros
Las operaciones de enteros se pueden comprobar o no para las condiciones de desbordamiento en tiempo de
ejecución. Si se comprueban las condiciones de desbordamiento y se desborda una operación de entero,
System.OverflowException se produce una excepción. Si las condiciones de desbordamiento no se marcan, los
desbordamientos de operación de enteros no indican ninguna excepción. El entorno de compilación determina
si esta opción está activada o desactivada.
Option Infer (instrucción)
La Option Infer instrucción determina si las declaraciones de variables locales que no tienen ninguna As
cláusula tienen un tipo deducido o un uso Object . La instrucción puede ir seguida de las palabras clave On o
Off ; si no se especifica ninguno, el valor predeterminado es On . Si no se especifica ninguna instrucción en un
archivo, el entorno de compilación determina cuál se usará.
OptionInferStatement
: 'Option' 'Infer' OnOff? StatementTerminator
;
Module Test
Sub Main()
' The type of x is Integer
Dim x = 10
Imports (instrucción)
Imports las instrucciones importan los nombres de las entidades en un archivo de código fuente, lo que
permite hacer referencia a los nombres sin calificación, o bien importar un espacio de nombres para su uso en
expresiones XML.
ImportsStatement
: 'Imports' ImportsClauses StatementTerminator
;
ImportsClauses
: ImportsClause ( Comma ImportsClause )*
;
ImportsClause
: AliasImportsClause
| MembersImportsClause
| XMLNamespaceImportsClause
;
Dentro de las declaraciones de miembro de un archivo de código fuente que contiene una Imports instrucción,
se puede hacer referencia a los tipos contenidos en el espacio de nombres especificado directamente, tal como
se muestra en el ejemplo siguiente:
Imports N1.N2
Namespace N1.N2
Class A
End Class
End Namespace
Namespace N3
Class B
Inherits A
End Class
End Namespace
Aquí, dentro del archivo de código fuente, los miembros de tipo del espacio de nombres N1.N2 están
directamente disponibles y, por lo tanto, la clase N3.B deriva de la clase N1.N2.A .
Imports las instrucciones deben aparecer después de cualquier Option instrucción, pero antes de cualquier
declaración de tipo. El entorno de compilación también puede definir Imports instrucciones implícitas.
Imports las instrucciones hacen que los nombres estén disponibles en un archivo de código fuente, pero no
declaran nada en el espacio de declaración del espacio de nombres global. El ámbito de los nombres
importados por una Imports instrucción se extiende por las declaraciones de miembros de espacio de nombres
contenidos en el archivo de código fuente. El ámbito de una Imports instrucción no incluye específicamente
otras Imports instrucciones ni incluye otros archivos de código fuente. Imports es posible que las
instrucciones no hagan referencia entre sí.
En este ejemplo, la última Imports instrucción es un error porque no se ve afectada por el primer alias de
importación.
Namespace N1.N2
End Namespace
Nota. Los nombres de espacios de nombres o tipos que aparecen en las Imports instrucciones siempre se
tratan como si fueran completos. Es decir, el identificador situado más a la izquierda en un espacio de nombres o
nombre de tipo siempre se resuelve en el espacio de nombres global y el resto de la resolución continúa según
las reglas de resolución de nombres normales. Este es el único lugar en el lenguaje que aplica este tipo de regla.
la regla garantiza que un nombre no se puede ocultar completamente de la calificación. Sin la regla, si un
nombre en el espacio de nombres global estuviera oculto en un archivo de código fuente determinado, sería
imposible especificar los nombres de ese espacio de nombres de forma completa.
En este ejemplo, la Imports instrucción siempre hace referencia al System espacio de nombres global y no a la
clase del archivo de código fuente.
Class System
End Class
Alias de importación
Un alias de importación define un alias para un espacio de nombres o un tipo.
AliasImportsClause
: Identifier Equals TypeName
;
Imports A = N1.N2.A
Namespace N1.N2
Class A
End Class
End Namespace
Namespace N3
Class B
Inherits A
End Class
End Namespace
Aquí, dentro del archivo de código fuente, A es un alias para N1.N2.A y, por lo tanto, la clase N3.B deriva de la
clase N1.N2.A . Se puede obtener el mismo efecto si se crea un alias R para N1.N2 y, a continuación, se hace
referencia a R.A :
Imports R = N1.N2
Namespace N3
Class B
Inherits R.A
End Class
End Namespace
El identificador de un alias de importación debe ser único en el espacio de declaración del espacio de nombres
global (no solo en la declaración de espacio de nombres global en el archivo de código fuente en el que se
define el alias de importación), aunque no declare un nombre en el espacio de declaración del espacio de
nombres global.
Nota. Las declaraciones de un módulo no introducen nombres en el espacio de declaración contenedor. Por lo
tanto, es válido que una declaración de un módulo tenga el mismo nombre que un alias de importación, aunque
el nombre de la declaración será accesible en el espacio de declaración contenedor.
Class A
End Class
Namespace N3
Class A
End Class
End Namespace
Aquí, el espacio de nombres global ya contiene un miembro A , por lo que es un error que un alias de
importación use ese identificador. Es igualmente un error para dos o más alias de importación en el mismo
archivo de código fuente para declarar alias con el mismo nombre.
Un alias de importación puede crear un alias para cualquier espacio de nombres o tipo. El acceso a un espacio
de nombres o a un tipo a través de un alias produce exactamente el mismo resultado que el acceso al espacio de
nombres o el tipo a través de su nombre declarado.
Imports R1 = N1
Imports R2 = N1.N2
Namespace N1.N2
Class A
End Class
End Namespace
Namespace N3
Class B
Private a As N1.N2.A
Private b As R1.N2.A
Private c As R2.A
End Class
End Namespace
Aquí, los nombres N1.N2.A , R1.N2.A y R2.A son equivalentes y todos hacen referencia a la clase cuyo nombre
completo es N1.N2.A .
La importación especifica el nombre exacto del espacio de nombres o el tipo en el que se crea un alias. Debe ser
el nombre completo exacto de ese espacio de nombres o tipo: no utiliza las reglas normales para la resolución
de nombres completa (que por ejemplo permite el acceso a los miembros de una clase base a través de una
clase derivada).
Si un alias de importación apunta a un tipo o un espacio de nombres que estas reglas no pueden resolver, se
omite la instrucción Import (y el compilador emite una advertencia).
Además, la referencia no puede ser a un tipo genérico abierto: todos los tipos genéricos deben tener
argumentos de tipo válidos suministrados y todos los argumentos de tipo deben poder resolverse mediante las
reglas anteriores. Cualquier enlace incorrecto de un tipo genérico es un error.
Class Base
Class Nested : End Class
End Class
Module Module1
Sub Main()
Dim x As C ' error: "C" wasn't succesfully defined
Dim y As Derived.Nested ' okay
End Sub
End Module
Las declaraciones del archivo de código fuente pueden prevalecer en el nombre del alias de importación.
Imports R = N1.N2
Namespace N1.N2
Class A
End Class
End Namespace
Namespace N3
Class R
End Class
Class B
Inherits R.A ' Error, R has no member A
End Class
End Namespace
En el ejemplo anterior, la referencia a R.A en la declaración de B provoca un error porque R hace referencia a
N3.R , no a N1.N2 .
Un alias de importación hace que un alias esté disponible en un archivo de código fuente determinado, pero no
aporta ningún miembro nuevo al espacio de declaración subyacente. En otras palabras, un alias de importación
no es transitivo, sino que solo afecta al archivo de origen en el que se produce.
Archivo1. VB:
Imports R = N1.N2
Namespace N1.N2
Class A
End Class
End Namespace
Archivo2. VB:
Class B
Inherits R.A ' Error, R unknown.
End Class
En el ejemplo anterior, dado que el ámbito del alias de importación que introduce R solo se extiende a las
declaraciones del archivo de código fuente en el que está contenido, R se desconoce en el segundo archivo de
código fuente.
Importaciones de espacio de nombres
Una importación de espacio de nombres importa todos los miembros de un espacio de nombres o tipo, lo que
permite usar el identificador de cada miembro del espacio de nombres o tipo sin calificación. En el caso de los
tipos, una importación de espacio de nombres solo permite el acceso a los miembros compartidos del tipo sin
necesidad de la calificación del nombre de clase. En concreto, permite usar los miembros de tipos enumerados
sin calificación.
MembersImportsClause
: TypeName
;
Por ejemplo:
Imports Colors
Enum Colors
Red
Green
Blue
End Enum
Module M1
Sub Main()
Dim c As Colors = Red
End Sub
End Module
A diferencia de un alias de importación, una importación de espacio de nombres no tiene restricciones en los
nombres que importa y puede importar espacios de nombres y tipos cuyos identificadores ya se hayan
declarado dentro del espacio de nombres global. Los nombres importados por una importación normal se
separan mediante el alias de importación y las declaraciones del archivo de código fuente.
En el ejemplo siguiente, A hace referencia a en N3.A lugar de a las N1.N2.A declaraciones de miembros en el
N3 espacio de nombres.
Imports N1.N2
Namespace N1.N2
Class A
End Class
End Namespace
Namespace N3
Class A
End Class
Class B
Inherits A
End Class
End Namespace
Cuando más de un espacio de nombres importado contiene miembros con el mismo nombre (y ese nombre no
está sombreado de otro modo por un alias o una declaración de importación), una referencia a ese nombre es
ambigua y produce un error en tiempo de compilación.
Imports N1
Imports N2
Namespace N1
Class A
End Class
End Namespace
Namespace N2
Class A
End Class
End Namespace
Namespace N3
Class B
Inherits A ' Error, A is ambiguous.
End Class
End Namespace
Imports N1
Imports N2
Imports A = N1.A
Namespace N3
Class B
Inherits A ' A means N1.A.
End Class
End Namespace
Solo se pueden importar los espacios de nombres, las clases, las estructuras, los tipos enumerados y los
módulos estándar.
Importaciones de espacio de nombres XML
Una importación de espacio de nombres XML define un espacio de nombres o el espacio de nombres
predeterminado para las expresiones XML incompletas contenidas en la unidad de compilación.
XMLNamespaceImportsClause
: '<' XMLNamespaceAttributeName XMLWhitespace? Equals XMLWhitespace?
XMLNamespaceValue '>'
;
XMLNamespaceValue
: DoubleQuoteCharacter XMLAttributeDoubleQuoteValueCharacter* DoubleQuoteCharacter
| SingleQuoteCharacter XMLAttributeSingleQuoteValueCharacter* SingleQuoteCharacter
;
Por ejemplo:
Imports <xmlns:db="http://example.org/database">
Module Test
Sub Main()
' db namespace is "http://example.org/database"
Dim x = <db:customer><db:Name>Bob</></>
Console.WriteLine(x.<db:Name>)
End Sub
End Module
Un espacio de nombres XML, incluido el espacio de nombres predeterminado, solo se puede definir una vez
para un conjunto determinado de importaciones. Por ejemplo:
Imports <xmlns:db="http://example.org/database-one">
' Error: namespace db is already defined
Imports <xmlns:db="http://example.org/database-two">
Espacios de nombres
Visual Basic programas se organizan mediante espacios de nombres. Los espacios de nombres organizan
internamente un programa y organizan la manera en que los elementos de programa se exponen a otros
programas.
A diferencia de otras entidades, los espacios de nombres están abiertos y se pueden declarar varias veces en el
mismo programa y en varios programas, y cada declaración contribuye a los miembros al mismo espacio de
nombres. En el ejemplo siguiente, las dos declaraciones de espacio de nombres contribuyen al mismo espacio
de declaración y declaran dos clases con los nombres completos N1.N2.A y N1.N2.B .
Namespace N1.N2
Class A
End Class
End Namespace
Namespace N1.N2
Class B
End Class
End Namespace
Dado que las dos declaraciones contribuyen al mismo espacio de declaración, sería un error si cada una de ellas
contenía una declaración de un miembro con el mismo nombre.
Hay un espacio de nombres global que no tiene nombre y a cuyos espacios de nombres y tipos anidados
siempre se puede tener acceso sin calificación. El ámbito de un miembro de espacio de nombres declarado en el
espacio de nombres global es el texto del programa completo. De lo contrario, el ámbito de un tipo o espacio de
nombres declarado en un espacio de nombres cuyo nombre completo es N el texto del programa de cada
espacio de nombres cuyo nombre completo del espacio de nombres correspondiente empieza por N o es el N
propio. (Tenga en cuenta que un compilador puede elegir colocar declaraciones en un espacio de nombres
determinado de forma predeterminada. Esto no altera el hecho de que todavía hay un espacio de nombres
global sin nombre).
En este ejemplo, la clase B puede ver la clase A porque el B espacio de nombres de N1.N2.N3 se anida
conceptualmente dentro del espacio de nombres N1.N2 .
Namespace N1.N2
Class A
End Class
End Namespace
Namespace N1.N2.N3
Class B
Inherits A
End Class
End Namespace
NamespaceDeclaration
: 'Namespace' NamespaceName StatementTerminator
NamespaceMemberDeclaration*
'End' 'Namespace' StatementTerminator
;
NamespaceName
: RelativeNamespaceName
| 'Global'
| 'Global' '.' RelativeNamespaceName
;
RelativeNamespaceName
: Identifier ( Period IdentifierOrKeyword )*
;
La primera forma comienza con la palabra clave Namespace seguida de un nombre de espacio de nombres
relativo. Si el nombre de espacio de nombres relativo es Qualified, la declaración de espacio de nombres se trata
como si estuviera anidada léxicamente dentro de las declaraciones de espacio de nombres correspondientes a
cada nombre del nombre completo. Por ejemplo, los dos espacios de nombres siguientes son semánticamente
equivalentes:
Namespace N1.N2
Class A
End Class
Class B
End Class
End Namespace
Namespace N1
Namespace N2
Class A
End Class
Class B
End Class
End Namespace
End Namespace
La segunda forma comienza con las palabras clave Namespace Global . Se trata como si todas sus declaraciones
de miembros se colocaran léxicamente en el espacio de nombres sin nombre global, independientemente de los
valores predeterminados proporcionados por el entorno de compilación. Esta forma de declaración de espacio
de nombres puede no estar anidada léxicamente en ninguna otra declaración de espacio de nombres.
El tercer formulario comienza con las palabras clave Namespace Global seguidos de un identificador calificado
N . Se trata como si fuera una declaración de espacio de nombres del primer formulario " Namespace N " que se
colocó léxicamente en el espacio de nombres sin nombre global, independientemente de los valores
predeterminados proporcionados por el entorno de compilación. Esta forma de declaración de espacio de
nombres puede no estar anidada léxicamente en ninguna otra declaración de espacio de nombres.
Cuando se trabaja con los miembros de un espacio de nombres, no es importante que se declare un miembro
determinado. Si dos programas definen una entidad con el mismo nombre en el mismo espacio de nombres, al
intentar resolver el nombre en el espacio de nombres se produce un error de ambigüedad.
Los espacios de nombres son por definición Public , por lo que una declaración de espacio de nombres no
puede incluir modificadores de acceso.
Miembros de espacio de nombres
Los miembros de espacio de nombres solo pueden ser declaraciones de espacio de nombres y declaraciones de
tipos. Las declaraciones de tipos pueden Public tener Friend acceso o. El acceso predeterminado para los
tipos es Friend Access.
NamespaceMemberDeclaration
: NamespaceDeclaration
| TypeDeclaration
;
TypeDeclaration
: ModuleDeclaration
| NonModuleDeclaration
;
NonModuleDeclaration
: EnumDeclaration
| StructureDeclaration
| InterfaceDeclaration
| ClassDeclaration
| DelegateDeclaration
;
Tipos
15/11/2021 • 69 minutes to read
Las dos categorías fundamentales de tipos de Visual Basic son tipos de valor y tipos de referencia. Los tipos
primitivos (excepto las cadenas), las enumeraciones y las estructuras son tipos de valor. Las clases, las cadenas,
los módulos estándar, las interfaces, las matrices y los delegados son tipos de referencia.
Cada tipo tiene un valor predeterminado, que es el valor que se asigna a las variables de ese tipo al inicializarse.
TypeName
: ArrayTypeName
| NonArrayTypeName
;
NonArrayTypeName
: SimpleTypeName
| NullableTypeName
;
SimpleTypeName
: QualifiedTypeName
| BuiltInTypeName
;
QualifiedTypeName
: Identifier TypeArguments? (Period IdentifierOrKeyword TypeArguments?)*
| 'Global' Period IdentifierOrKeyword TypeArguments?
(Period IdentifierOrKeyword TypeArguments?)*
;
TypeArguments
: OpenParenthesis 'Of' TypeArgumentList CloseParenthesis
;
TypeArgumentList
: TypeName ( Comma TypeName )*
;
BuiltInTypeName
: 'Object'
| PrimitiveTypeName
;
TypeModifier
: AccessModifier
| 'Shadows'
;
IdentifierModifiers
: NullableNameModifier? ArrayNameModifier?
;
Class Class1
Public Value As Integer = 0
End Class
Module Test
Sub Main()
Dim val1 As Integer = 0
Dim val2 As Integer = val1
val2 = 123
Dim ref1 As Class1 = New Class1()
Dim ref2 As Class1 = ref1
ref2.Value = 123
Console.WriteLine("Values: " & val1 & ", " & val2)
Console.WriteLine("Refs: " & ref1.Value & ", " & ref2.Value)
End Sub
End Module
Values: 0, 123
Refs: 123, 123
La asignación a la variable local no val2 afecta a la variable local val1 porque ambas variables locales son de
un tipo de valor (el tipo Integer ) y cada variable local de un tipo de valor tiene su propio almacenamiento. En
cambio, la asignación ref2.Value = 123; afecta al objeto que ref1 y ref2 hacen referencia a.
Una cuestión que hay que tener en cuenta sobre el sistema de tipos .NET Framework es que, aunque las
estructuras, las enumeraciones y los tipos primitivos (excepto para String ) son tipos de valor, todos heredan
de los tipos de referencia. Las estructuras y los tipos primitivos heredan del tipo de referencia System.ValueType
, que hereda de Object . Los tipos enumerados heredan del tipo de referencia System.Enum , que hereda de
System.ValueType .
NullableTypeName
: NonArrayTypeName '?'
;
NullableNameModifier
: '?'
;
Un tipo de valor que acepta valores NULL puede contener los mismos valores que la versión que no acepta
valores NULL del tipo, así como el valor null. Por lo tanto, para un tipo de valor que acepta valores NULL, si
Nothing se asigna a una variable del tipo, el valor de la variable se establece en el valor null, no en el valor de
cero del tipo de valor. Por ejemplo:
Una variable también puede declararse como un tipo de valor que acepta valores NULL si se coloca un
modificador de tipo que acepta valores NULL en el nombre de la variable. Para mayor claridad, no es válido
tener un modificador de tipo que acepta valores NULL en un nombre de variable y un nombre de tipo en la
misma declaración. Dado que los tipos que aceptan valores NULL se implementan utilizando el tipo
System.Nullable(Of T) , el tipo T? es sinónimo del tipo System.Nullable(Of T) y los dos nombres se pueden
usar indistintamente. El ? modificador no se puede colocar en un tipo que ya acepte valores NULL; por lo tanto,
no es posible declarar el tipo Integer?? o System.Nullable(Of Integer)? .
Un tipo de valor que acepta valores NULL T? tiene los miembros de System.Nullable(Of T) , así como
cualquier operador o conversión que se eleve del tipo subyacente T al tipo T? . El levantamiento de copias
operadores y conversiones del tipo subyacente, en la mayoría de los casos, sustituyendo los tipos de valor que
aceptan valores NULL por tipos de valor que no aceptan valores NULL. Esto permite que muchas de las mismas
conversiones y operaciones que se aplican a T también se apliquen a T? .
Implementación de interfaz
Las declaraciones de estructura y clase pueden declarar que implementan un conjunto de tipos de interfaz a
través de una o más Implements cláusulas.
TypeImplementsClause
: 'Implements' TypeImplements StatementTerminator
;
TypeImplements
: NonArrayTypeName ( Comma NonArrayTypeName )*
;
Todos los tipos especificados en la Implements cláusula deben ser interfaces y el tipo debe implementar todos
los miembros de las interfaces. Por ejemplo:
Interface ICloneable
Function Clone() As Object
End Interface
Interface IComparable
Function CompareTo(other As Object) As Integer
End Interface
Structure ListEntry
Implements ICloneable, IComparable
...
Un tipo que implementa una interfaz también implementa implícitamente todas las interfaces base de la
interfaz. Esto es así incluso si el tipo no enumera explícitamente todas las interfaces base de la Implements
cláusula. En este ejemplo, la TextBox estructura implementa IControl y ITextBox .
Interface IControl
Sub Paint()
End Interface
Interface ITextBox
Inherits IControl
Structure TextBox
Implements ITextBox
...
Declarar que un tipo implementa una interfaz en y de sí mismo no declara nada en el espacio de declaración del
tipo. Por lo tanto, es válido implementar dos interfaces con un método con el mismo nombre.
Los tipos no pueden implementar un parámetro de tipo por su cuenta, aunque puede incluir los parámetros de
tipo que se encuentran en el ámbito.
Class C1(Of V)
Implements V ' Error, can't implement type parameter directly
Implements IEnumerable(Of V) ' OK, not directly implementing
...
End Class
Las interfaces genéricas se pueden implementar varias veces mediante argumentos de tipo diferentes. Sin
embargo, un tipo genérico no puede implementar una interfaz genérica mediante un parámetro de tipo si el
parámetro de tipo proporcionado (independientemente de las restricciones de tipo) podría superponerse a otra
implementación de esa interfaz. Por ejemplo:
Interface I1(Of T)
End Interface
Class C1
Implements I1(Of Integer)
Implements I1(Of Double) ' OK, no overlap
End Class
Class C2(Of T)
Implements I1(Of Integer)
Implements I1(Of T) ' Error, T could be Integer
End Class
Tipos primitivos
Los tipos primitivos se identifican mediante palabras clave, que son alias para los tipos predefinidos en el
System espacio de nombres. Un tipo primitivo no es totalmente distinguible del tipo al que se escribe alias:
escribir la palabra reservada Byte es exactamente igual que escribir System.Byte . Los tipos primitivos también
se conocen como tipos intrínsecos.
PrimitiveTypeName
: NumericTypeName
| 'Boolean'
| 'Date'
| 'Char'
| 'String'
;
NumericTypeName
: IntegralTypeName
| FloatingPointTypeName
| 'Decimal'
;
IntegralTypeName
: 'Byte' | 'SByte' | 'UShort' | 'Short' | 'UInteger'
| 'Integer' | 'ULong' | 'Long'
;
FloatingPointTypeName
: 'Single' | 'Double'
;
Dado que un tipo primitivo incluye un alias de un tipo normal, cada tipo primitivo tiene miembros. Por ejemplo,
Integer tiene los miembros declarados en System.Int32 . Los literales se pueden tratar como instancias de sus
tipos correspondientes.
Los tipos primitivos difieren de otros tipos de estructura en que permiten ciertas operaciones adicionales:
Los tipos primitivos permiten crear valores escribiendo literales. Por ejemplo, 123I es un literal de tipo
Integer .
Enumeraciones
Las enumeraciones son tipos de valor que heredan de System.Enum y representan simbólicamente un conjunto
de valores de uno de los tipos enteros primitivos.
EnumDeclaration
: Attributes? TypeModifier* 'Enum' Identifier
( 'As' NonArrayTypeName )? StatementTerminator
EnumMemberDeclaration+
'End' 'Enum' StatementTerminator
;
Para un tipo de enumeración E , el valor predeterminado es el valor generado por la expresión CType(0, E) .
El tipo subyacente de una enumeración debe ser un tipo entero que puede representar todos los valores de
enumerador definidos en la enumeración. Si se especifica un tipo subyacente, debe ser Byte , SByte , UShort ,
Short , UInteger , Integer , ULong , Long o uno de sus tipos correspondientes en el espacio de System
nombres. Si no se especifica ningún tipo subyacente explícitamente, el valor predeterminado es Integer .
En el ejemplo siguiente se declara una enumeración con un tipo subyacente de Long :
Un programador puede optar por usar un tipo subyacente de Long , como en el ejemplo, para habilitar el uso
de valores que se encuentran en el intervalo de, Long pero no en el intervalo de Integer , o para conservar
esta opción en el futuro.
Miembros de enumeración
Los miembros de una enumeración son los valores enumerados declarados en la enumeración y los miembros
heredados de la clase System.Enum .
El ámbito de un miembro de enumeración es el cuerpo de la declaración de enumeración. Esto significa que
fuera de una declaración de enumeración, un miembro de enumeración siempre debe estar calificado (a menos
que el tipo se importe específicamente en un espacio de nombres a través de una importación de espacio de
nombres).
El orden de declaración para las declaraciones de miembros de enumeración es importante cuando se omiten
los valores de expresión constante. Los miembros de enumeración Public solo tienen acceso de forma
implícita; no se permiten modificadores de acceso en las declaraciones de miembros de enumeración.
EnumMemberDeclaration
: Attributes? Identifier ( Equals ConstantExpression )? StatementTerminator
;
Valores de enumeración
Los valores enumerados en una lista de miembros de enumeración se declaran como constantes con el tipo de
enumeración subyacente y pueden aparecer siempre que se requieran constantes. Una definición de miembro
de enumeración con = asigna al miembro asociado el valor indicado por la expresión constante. La expresión
constante se debe evaluar como un tipo entero que se pueda convertir implícitamente al tipo subyacente y debe
estar dentro del intervalo de valores que se puede representar mediante el tipo subyacente. El ejemplo siguiente
es erróneo porque los valores constantes 1.5 , 2.3 y 3.3 no se pueden convertir implícitamente al tipo
entero subyacente Long con semántica estricta.
Option Strict On
Varios miembros de enumeración pueden compartir el mismo valor asociado, como se muestra a continuación:
Enum Color
Red
Green
Blue
Max = Blue
End Enum
En el ejemplo se muestra una enumeración que tiene dos miembros de enumeración, Blue y Max --que tienen
el mismo valor asociado.
Si la primera definición de valor de enumerador en la enumeración no tiene inicializador, el valor de la constante
correspondiente es 0 . Una definición de valor de enumeración sin inicializador proporciona al enumerador el
valor obtenido al aumentar el valor del valor de enumeración anterior por 1 . Este aumento de valor debe estar
dentro del intervalo de valores que se puede representar mediante el tipo subyacente.
Enum Color
Red
Green = 10
Blue
End Enum
Module Test
Sub Main()
Console.WriteLine(StringFromColor(Color.Red))
Console.WriteLine(StringFromColor(Color.Green))
Console.WriteLine(StringFromColor(Color.Blue))
End Sub
Case Color.Green
Return String.Format("Green = " & CInt(c))
Case Color.Blue
Return String.Format("Blue = " & CInt(c))
Case Else
Return "Invalid color"
End Select
End Function
End Module
En el ejemplo anterior se imprimen los valores de enumeración y sus valores asociados. La salida es la siguiente:
Red = 0
Green = 10
Blue = 11
Enum Circular
A = B
B
End Enum
Clases
Una clase es una estructura de datos que puede contener miembros de datos (constantes, variables y eventos),
miembros de función (métodos, propiedades, indizadores, operadores y constructores) y tipos anidados. Las
clases son tipos de referencia.
ClassDeclaration
: Attributes? ClassModifier* 'Class' Identifier TypeParameterList? StatementTerminator
ClassBase?
TypeImplementsClause*
ClassMemberDeclaration*
'End' 'Class' StatementTerminator
;
ClassModifier
: TypeModifier
| 'MustInherit'
| 'NotInheritable'
| 'Partial'
;
En el ejemplo siguiente se muestra una clase que contiene cada tipo de miembro:
Class AClass
Public Sub New()
Console.WriteLine("Constructor")
End Sub
Sub Main()
' Constructor usage.
Dim a As AClass = New AClass()
Dim b As AClass = New AClass(123)
Hay dos modificadores específicos de la clase, MustInherit y NotInheritable . No es válido especificar ambos.
Especificación de clase base
Una declaración de clase puede incluir una especificación de tipo base que define el tipo base directo de la clase.
ClassBase
: 'Inherits' NonArrayTypeName StatementTerminator
;
Si una declaración de clase no tiene ningún tipo base explícito, el tipo base directo es implícitamente Object .
Por ejemplo:
Class Base
End Class
Class Derived
Inherits Base
End Class
Los tipos base no pueden ser un parámetro de tipo por su cuenta, aunque puede incluir los parámetros de tipo
que se encuentran en el ámbito.
Class C1(Of V)
End Class
Class C2(Of V)
Inherits V ' Error, type parameter used as base class
End Class
Class C3(Of V)
Inherits C1(Of V) ' OK: not directly inheriting from V.
End Class
Las clases solo se pueden derivar de Object las clases y. No es válido que una clase derive de System.ValueType
, System.Enum , System.Array System.MulticastDelegate o System.Delegate . Una clase genérica no puede
derivar de System.Attribute o de una clase que deriva de ella.
Cada clase tiene exactamente una clase base directa y se prohíbe la circularidad en la derivación. No es posible
derivar de una NotInheritable clase y el dominio de accesibilidad de la clase base debe ser el mismo que el
dominio de accesibilidad de la propia clase.
Miembros de la clase
Los miembros de una clase se componen de los miembros introducidos por sus declaraciones de miembro de
clase y los miembros heredados de su clase base directa.
ClassMemberDeclaration
: NonModuleDeclaration
| EventMemberDeclaration
| VariableMemberDeclaration
| ConstantMemberDeclaration
| MethodMemberDeclaration
| PropertyMemberDeclaration
| ConstructorMemberDeclaration
| OperatorDeclaration
;
Una declaración de miembro de clase puede tener Public Protected Friend acceso a,,, Protected Friend o
Private . Cuando una declaración de miembro de clase no incluye un modificador de acceso, la declaración
tiene como valor predeterminado el Public acceso, a menos que sea una declaración de variable; en ese caso,
tiene como valor predeterminado el Private acceso.
El ámbito de un miembro de clase es el cuerpo de clase en el que se produce la declaración de miembro,
además de la lista de restricciones de esa clase (si es genérica y tiene restricciones). Si el miembro tiene Friend
acceso, su ámbito se extiende al cuerpo de la clase de cualquier clase derivada en el mismo programa o a
cualquier ensamblado al que se haya concedido Friend acceso, y si el miembro tiene Public Protected
acceso, o Protected Friend , su ámbito se extiende al cuerpo de la clase de cualquier clase derivada de cualquier
programa.
Estructuras
Las estructuras son tipos de valor que heredan de System.ValueType . Las estructuras son similares a las clases
en que representan las estructuras de datos que pueden contener miembros de datos y miembros de función.
Sin embargo, a diferencia de las clases, las estructuras no requieren la asignación del montón.
StructureDeclaration
: Attributes? StructureModifier* 'Structure' Identifier
TypeParameterList? StatementTerminator
TypeImplementsClause*
StructMemberDeclaration*
'End' 'Structure' StatementTerminator
;
StructureModifier
: TypeModifier
| 'Partial'
;
En el caso de las clases, es posible que dos variables hagan referencia al mismo objeto y, por lo tanto, las
operaciones en una variable afecten al objeto al que hace referencia la otra variable. Con las estructuras, cada
variable tiene su propia copia de los datos que no son de Shared , por lo que no es posible que las operaciones
en una afecten a la otra, como se muestra en el ejemplo siguiente:
Structure Point
Public x, y As Integer
Module Test
Sub Main()
Dim a As New Point(10, 10)
Dim b As Point = a
a.x = 100
Console.WriteLine(b.x)
End Sub
End Module
La asignación de a para b crea una copia del valor y, b por tanto, no se ve afectada por la asignación a a.x .
Point En su lugar se ha declarado como una clase, el resultado sería 100 porque a y b haría referencia al
mismo objeto.
Miembros de estructura
Los miembros de una estructura son los miembros introducidos por sus declaraciones de miembros de
estructura y los miembros heredados de System.ValueType .
StructMemberDeclaration
: NonModuleDeclaration
| VariableMemberDeclaration
| ConstantMemberDeclaration
| EventMemberDeclaration
| MethodMemberDeclaration
| PropertyMemberDeclaration
| ConstructorMemberDeclaration
| OperatorDeclaration
;
Cada estructura tiene implícitamente un Public constructor de instancia sin parámetros que genera el valor
predeterminado de la estructura. Como resultado, no es posible que una declaración de tipo de estructura
declare un constructor de instancia sin parámetros. Sin embargo, un tipo de estructura permite declarar
constructores de instancia con parámetros , como en el ejemplo siguiente:
Structure Point
Private x, y As Integer
Dada la declaración anterior, las siguientes instrucciones crean un Point con x y y se inicializan en cero.
Dado que las estructuras contienen directamente sus valores de campo (en lugar de referencias a esos valores),
las estructuras no pueden contener campos que hagan referencia a sí mismos directa o indirectamente. Por
ejemplo, el código siguiente no es válido:
Structure S1
Dim f1 As S2
End Structure
Structure S2
' This would require S1 to contain itself.
Dim f1 As S1
End Structure
Normalmente, una declaración de miembro de estructura solo puede tener Public Friend acceso, o Private ,
pero al reemplazar los miembros heredados de Object , Protected Protected Friend también se puede usar
el acceso. Cuando una declaración de miembro de estructura no incluye un modificador de acceso, la
declaración tiene como valor predeterminado el Public acceso. El ámbito de un miembro declarado por una
estructura es el cuerpo de la estructura en el que se produce la declaración, además de las restricciones de esa
estructura (si era genérica y tiene restricciones).
Módulos estándar
Un módulo estándar es un tipo cuyos miembros están implícitamente Shared y se limitan al espacio de
declaración del espacio de nombres contenedor del módulo estándar, en lugar de solo a la declaración del
módulo estándar. Nunca se pueden crear instancias de los módulos estándar. Es un error declarar una variable
de un tipo de módulo estándar.
ModuleDeclaration
: Attributes? TypeModifier* 'Module' Identifier StatementTerminator
ModuleMemberDeclaration*
'End' 'Module' StatementTerminator
;
Un miembro de un módulo estándar tiene dos nombres completos, uno sin el nombre del módulo estándar y
otro con el nombre del módulo estándar. Más de un módulo estándar en un espacio de nombres puede definir
un miembro con un nombre determinado; las referencias no calificadas al nombre fuera de cualquier módulo
son ambiguas. Por ejemplo:
Namespace N1
Module M1
Sub S1()
End Sub
Sub S2()
End Sub
End Module
Module M2
Sub S2()
End Sub
End Module
Module M3
Sub Main()
S1() ' Valid: Calls N1.M1.S1.
N1.S1() ' Valid: Calls N1.M1.S1.
S2() ' Not valid: ambiguous.
N1.S2() ' Not valid: ambiguous.
N1.M2.S2() ' Valid: Calls N1.M2.S2.
End Sub
End Module
End Namespace
Un módulo solo se puede declarar en un espacio de nombres y no puede estar anidado en otro tipo. Los
módulos estándar no implementan interfaces, se derivan implícitamente de Object y solo tienen Shared
constructores.
Miembros del módulo estándar
Los miembros de un módulo estándar son los miembros introducidos por sus declaraciones de miembro y los
miembros heredados de Object . Los módulos estándar pueden tener cualquier tipo de miembro excepto
constructores de instancia. Todos los miembros de tipo de módulo estándar son implícitamente Shared .
ModuleMemberDeclaration
: NonModuleDeclaration
| VariableMemberDeclaration
| ConstantMemberDeclaration
| EventMemberDeclaration
| MethodMemberDeclaration
| PropertyMemberDeclaration
| ConstructorMemberDeclaration
;
Normalmente, una declaración de miembro de módulo estándar solo puede tener Public Friend acceso, o
Private , pero al reemplazar los miembros heredados de Object , Protected Protected Friend se pueden
especificar los modificadores de acceso y. Cuando una declaración de miembro de módulo estándar no incluye
un modificador de acceso, la declaración tiene como valor predeterminado el Public acceso, a menos que sea
una variable, que tiene como valor predeterminado el Private acceso.
Como se indicó anteriormente, el ámbito de un miembro de módulo estándar es la declaración que contiene la
declaración de módulo estándar. Los miembros heredados de Object no se incluyen en este ámbito especial;
esos miembros no tienen ámbito y siempre deben estar calificados con el nombre del módulo. Si el miembro
tiene Friend acceso, su ámbito solo se extiende a los miembros del espacio de nombres declarados en el
mismo programa o ensamblados a los que se ha concedido Friend acceso.
Interfaces
Las interfaces son tipos de referencia que otros tipos implementan para garantizar que admiten ciertos
métodos. Una interfaz nunca se crea directamente y no tiene ninguna representación real; otros tipos se deben
convertir en un tipo de interfaz. Una interfaz define un contrato. Una clase o estructura que implementa una
interfaz debe adherirse a su contrato.
InterfaceDeclaration
: Attributes? TypeModifier* 'Interface' Identifier
TypeParameterList? StatementTerminator
InterfaceBase*
InterfaceMemberDeclaration*
'End' 'Interface' StatementTerminator
;
En el ejemplo siguiente se muestra una interfaz que contiene una propiedad predeterminada Item , un evento
E , un método F y una propiedad P :
Interface IExample
Default Property Item(index As Integer) As String
Event E()
Las interfaces pueden usar la herencia múltiple. En el ejemplo siguiente, la interfaz IComboBox hereda de
ITextBox y IListBox :
Interface IControl
Sub Paint()
End Interface
Interface ITextBox
Inherits IControl
Interface IListBox
Inherits IControl
Interface IComboBox
Inherits ITextBox, IListBox
End Interface
Las clases y estructuras pueden implementar varias interfaces. En el ejemplo siguiente, la clase se EditBox
deriva de la clase Control e implementa IControl y IDataBound :
Interface IDataBound
Sub Bind(b As Binder)
End Interface
Herencia de interfaz
Las interfaces base de una interfaz son las interfaces base explícitas y sus interfaces base. En otras palabras, el
conjunto de interfaces base es el cierre transitivo completo de las interfaces base explícitas, sus interfaces base
explícitas, etc. Si una declaración de interfaz no tiene una base de interfaz explícita, no hay ninguna interfaz base
para el tipo--las interfaces no heredan de Object (aunque tienen una conversión de ampliación a Object ).
InterfaceBase
: 'Inherits' InterfaceBases StatementTerminator
;
InterfaceBases
: NonArrayTypeName ( Comma NonArrayTypeName )*
;
En el ejemplo siguiente, las interfaces base de IComboBox son IControl , ITextBox y IListBox .
Interface IControl
Sub Paint()
End Interface
Interface ITextBox
Inherits IControl
Interface IListBox
Inherits IControl
Interface IComboBox
Inherits ITextBox, IListBox
End Interface
Una interfaz hereda todos los miembros de sus interfaces base. En otras palabras, la IComboBox interfaz anterior
hereda miembros y, así SetText SetItems como Paint .
Una clase o estructura que implementa una interfaz también implementa implícitamente todas las interfaces
base de la interfaz.
Si una interfaz aparece más de una vez en el cierre transitivo de las interfaces base, solo contribuye una vez a
sus miembros a la interfaz derivada. Un tipo que implementa la interfaz derivada solo tiene que implementar
una vez los métodos de la interfaz base definida por multiplicar. En el ejemplo siguiente, Paint solo debe
implementarse una vez, aunque la clase implemente IComboBox y IControl .
Class ComboBox
Implements IControl, IComboBox
Una Inherits cláusula no tiene ningún efecto en otras Inherits cláusulas. En el ejemplo siguiente, IDerived
debe calificar el nombre de INested con IBase .
Interface IBase
Interface INested
Sub Nested()
End Interface
Sub Base()
End Interface
Interface IDerived
Inherits IBase, INested ' Error: Must specify IBase.INested.
End Interface
El dominio de accesibilidad de una interfaz base debe ser el mismo que o un superconjunto del dominio de
accesibilidad de la propia interfaz.
Miembros de interfaz
Los miembros de una interfaz se componen de los miembros introducidos por sus declaraciones de miembro y
los miembros heredados de sus interfaces base.
InterfaceMemberDeclaration
: NonModuleDeclaration
| InterfaceEventMemberDeclaration
| InterfaceMethodMemberDeclaration
| InterfacePropertyMemberDeclaration
;
Aunque las interfaces no heredan miembros de Object , dado que cada clase o estructura que implementa una
interfaz hereda de Object , los miembros de Object , incluidos los métodos de extensión, se consideran
miembros de una interfaz y se puede llamar a en una interfaz directamente sin requerir una conversión a
Object . Por ejemplo:
Interface I1
End Interface
Class C1
Implements I1
End Class
Module Test
Sub Main()
Dim i As I1 = New C1()
Dim h As Integer = i.GetHashCode()
End Sub
End Module
Los miembros de una interfaz con el mismo nombre que los miembros de la Object instantánea implícita
Object . Solo los eventos, las propiedades, los métodos y los tipos anidados pueden ser miembros de una
interfaz. Los métodos y las propiedades no pueden tener un cuerpo. Los miembros de interfaz son
implícitamente Public y no pueden especificar un modificador de acceso. El ámbito de un miembro declarado
en una interfaz es el cuerpo de la interfaz en el que se produce la declaración, además de la lista de restricciones
de esa interfaz (si es genérica y tiene restricciones).
Matrices
Una matriz es un tipo de referencia que contiene las variables a las que se tiene acceso a través de los índices
correspondientes en un modo uno a uno con el orden de las variables de la matriz. Las variables contenidas en
una matriz, también denominadas elementos de la matriz, deben ser del mismo tipo y este tipo se denomina
tipo de elemento de la matriz.
ArrayTypeName
: NonArrayTypeName ArrayTypeModifiers
;
ArrayTypeModifiers
: ArrayTypeModifier+
;
ArrayTypeModifier
: OpenParenthesis RankList? CloseParenthesis
;
RankList
: Comma*
;
ArrayNameModifier
: ArrayTypeModifiers
| ArraySizeInitializationModifier
;
Los elementos de una matriz se componen cuando se crea una instancia de matriz y dejan de existir cuando se
destruye la instancia de la matriz. Cada elemento de una matriz se inicializa con el valor predeterminado de su
tipo. El tipo System.Array es el tipo base de todos los tipos de matriz y no se pueden crear instancias de él. Cada
tipo de matriz hereda los miembros declarados por el System.Array tipo y se pueden convertir en él (y Object
). Un tipo de matriz unidimensional con elemento T también implementa las interfaces
System.Collections.Generic.IList(Of T) y IReadOnlyList(Of T) ; si T es un tipo de referencia, el tipo de matriz
también implementa IList(Of U) y IReadOnlyList(Of U) para cualquier U que tenga una conversión de
referencia de ampliación desde T .
Una matriz tiene un rango que determina el número de índices asociados a cada elemento de la matriz. El rango
de una matriz determina el número de dimensiones de la matriz. Por ejemplo, una matriz con un rango de uno
se denomina matriz unidimensional y una matriz con un rango mayor que uno se denomina matriz
multidimensional.
En el ejemplo siguiente se crea una matriz unidimensional de valores enteros, se inicializan los elementos de la
matriz y, a continuación, se imprime cada uno de ellos:
Module Test
Sub Main()
Dim arr(5) As Integer
Dim i As Integer
For i = 0 To arr.Length - 1
arr(i) = i * i
Next i
For i = 0 To arr.Length - 1
Console.WriteLine("arr(" & i & ") = " & arr(i))
Next i
End Sub
End Module
arr(0) = 0
arr(1) = 1
arr(2) = 4
arr(3) = 9
arr(4) = 16
arr(5) = 25
Cada dimensión de una matriz tiene una longitud asociada. Las longitudes de las dimensiones no forman parte
del tipo de la matriz, sino que se establecen cuando se crea una instancia del tipo de matriz en tiempo de
ejecución. La longitud de una dimensión determina el intervalo válido de índices de esa dimensión: para una
dimensión de longitud, los N índices pueden oscilar entre cero y N-1 . Si una dimensión es de longitud cero,
no hay índices válidos para esa dimensión. El número total de elementos de una matriz es el producto de las
longitudes de cada dimensión de la matriz. Si alguna de las dimensiones de una matriz tiene una longitud de
cero, se dice que la matriz está vacía. El tipo de elemento de una matriz puede ser cualquier tipo.
Los tipos de matriz se especifican agregando un modificador a un nombre de tipo existente. El modificador se
compone de un paréntesis de apertura, un conjunto de cero o más comas y un paréntesis de cierre. El tipo
modificado es el tipo de elemento de la matriz y el número de dimensiones es el número de comas más uno. Si
se especifica más de un modificador, el tipo de elemento de la matriz es una matriz. Los modificadores se leen
de izquierda a derecha, donde el modificador situado más a la izquierda es la matriz más externa. En el ejemplo
Module Test
Dim arr As Integer(,)(,,)()
End Module
Module Test
Sub Main()
Dim a1() As Integer ' Declares 1-dimensional array of integers.
Dim a2(,) As Integer ' Declares 2-dimensional array of integers.
Dim a3(,,) As Integer ' Declares 3-dimensional array of integers.
Un modificador de nombre de tipo de matriz se extiende a todos los conjuntos de paréntesis que lo siguen. Esto
significa que en las situaciones en las que se permite un conjunto de argumentos entre paréntesis después de
un nombre de tipo, no es posible especificar los argumentos de un nombre de tipo de matriz. Por ejemplo:
Module Test
Sub Main()
' This calls the Integer constructor.
Dim x As New Integer(3)
En el último caso, (3) se interpreta como parte del nombre de tipo en lugar de como un conjunto de
argumentos de constructor.
Delegados
Un delegado es un tipo de referencia que hace referencia a un Shared método de un tipo o a un método de
instancia de un objeto.
DelegateDeclaration
: Attributes? TypeModifier* 'Delegate' MethodSignature StatementTerminator
;
MethodSignature
: SubSignature
| FunctionSignature
;
El equivalente más cercano de un delegado en otros lenguajes es un puntero de función, pero mientras que un
puntero de función solo puede hacer referencia a Shared funciones, un delegado puede hacer referencia a los
Shared métodos de instancia y. En el último caso, el delegado almacena no solo una referencia al punto de
entrada del método, sino también una referencia a la instancia del objeto con el que se invoca el método.
La declaración de delegado no puede tener una Handles cláusula, una Implements cláusula, un cuerpo de
método o una End construcción. La lista de parámetros de la declaración de delegado no puede contener
Optional ParamArray parámetros o. El dominio de accesibilidad del tipo de valor devuelto y de los tipos de
parámetro debe ser el mismo que o un supraconjunto del dominio de accesibilidad del delegado.
Los miembros de un delegado son los miembros heredados de la clase System.Delegate . Un delegado también
define los siguientes métodos:
Constructor que toma dos parámetros, uno de tipo Object y otro de tipo System.IntPtr .
Un Invoke método que tiene la misma firma que el delegado.
Un BeginInvoke método cuya firma es la firma de delegado, con tres diferencias. En primer lugar, el tipo
de valor devuelto se cambia a System.IAsyncResult . En segundo lugar, se agregan dos parámetros
adicionales al final de la lista de parámetros: el primero de tipo System.AsyncCallback y el segundo de
tipo Object . Y, por último, todos los ByRef parámetros se cambian a ByVal .
Un EndInvoke método cuyo tipo de valor devuelto es el mismo que el delegado. Los parámetros del
método son solo los parámetros de delegado exactamente que son ByRef parámetros, en el mismo
orden en que aparecen en la firma de delegado. Además de esos parámetros, hay un parámetro adicional
de tipo System.IAsyncResult al final de la lista de parámetros.
Hay tres pasos para definir y usar delegados: declaración, creación de instancias e invocación.
Los delegados se declaran mediante la sintaxis de declaración de delegado. En el ejemplo siguiente se declara
un delegado denominado SimpleDelegate que no toma ningún argumento:
Module Test
Sub F()
System.Console.WriteLine("Test.F")
End Sub
Sub Main()
Dim d As SimpleDelegate = AddressOf F
d()
End Sub
End Module
No hay mucho punto en crear instancias de un delegado para un método y, a continuación, llamar
inmediatamente a a través del delegado, como sería más sencillo llamar al método directamente. Los delegados
muestran su utilidad cuando se usa su anonimato. En el ejemplo siguiente se muestra un MultiCall método
que llama repetidamente a una SimpleDelegate instancia de:
Sub MultiCall(d As SimpleDelegate, count As Integer)
Dim i As Integer
For i = 0 To count - 1
d()
Next i
End Sub
No es importante para el MultiCall método cuál es el método de destino para SimpleDelegate , qué
accesibilidad tiene este método o si el método es Shared o no. Lo único que importa es que la firma del método
de destino sea compatible con SimpleDelegate .
Tipos parciales
Las declaraciones de clase y estructura pueden ser declaraciones parciales . Una declaración parcial puede o no
describir por completo el tipo declarado dentro de la declaración. En su lugar, la declaración del tipo puede
distribuirse entre varias declaraciones parciales dentro del programa; los tipos parciales no se pueden declarar a
través de los límites del programa. Una declaración de tipo parcial especifica el Partial modificador de la
declaración. A continuación, cualquier otra declaración en el programa para un tipo con el mismo nombre
completo se combinará junto con la declaración parcial en tiempo de compilación para formar una única
declaración de tipo. Por ejemplo, el código siguiente declara una única clase Test con miembros Test.C1 y
Test.C2 .
a. VB:
b. VB:
Al combinar declaraciones de tipos parciales, al menos una de las declaraciones debe tener un Partial
modificador; en caso contrario, se producirá un error en tiempo de compilación.
Nota. Aunque es posible especificar Partial solo en una declaración entre muchas declaraciones parciales, es
mejor especificarla en todas las declaraciones parciales. En la situación en la que una declaración parcial está
visible pero una o varias declaraciones parciales están ocultas (como el caso de la extensión del código
generado por la herramienta), es aceptable dejar el Partial modificador fuera de la declaración visible, pero
especificarlo en las declaraciones ocultas.
Solo se pueden declarar clases y estructuras mediante declaraciones parciales. La aridad de un tipo se tiene en
cuenta al buscar coincidencias de declaraciones parciales juntas: dos clases con el mismo nombre pero distintos
números de parámetros de tipo no se consideran declaraciones parciales de la misma hora. Las declaraciones
parciales pueden especificar atributos, modificadores de clase, Inherits instrucción o Implements instrucción.
En tiempo de compilación, todas las partes de las declaraciones parciales se combinan y se utilizan como parte
de la declaración de tipos. Si hay conflictos entre los atributos, los modificadores, las bases, las interfaces o los
miembros de tipo, se producirá un error en tiempo de compilación. Por ejemplo:
Public Partial Class Test1
Implements IDisposable
End Class
Class Test1
Inherits Object
Implements IComparable
End Class
En el ejemplo anterior se declara un tipo Test1 que es Public , hereda de Object e implementa
System.IDisposable y System.IComparable . Las declaraciones parciales de producirán Test2 un error en
tiempo de compilación debido a que una de las declaraciones indica que Test2 es Public y otro indica que
Test2 es Private .
Los tipos parciales con parámetros de tipo pueden declarar restricciones y varianza para los parámetros de tipo,
pero las restricciones y la varianza de cada declaración parcial deben coincidir. Por lo tanto, las restricciones y la
varianza son especiales, ya que no se combinan automáticamente como otros modificadores:
El hecho de que un tipo se declare utilizando varias declaraciones parciales no afecta a las reglas de búsqueda
de nombres dentro del tipo. Como resultado, una declaración de tipos parciales puede usar miembros
declarados en otras declaraciones de tipos parciales o puede implementar métodos en interfaces declaradas en
otras declaraciones de tipos parciales. Por ejemplo:
Class Test1
Private Sub Dispose() Implements IDisposable.Dispose
If Not IsDisposed Then
...
End If
End Sub
End Class
Los tipos anidados también pueden tener declaraciones parciales. Por ejemplo:
Public Partial Class Test
Public Partial Class NestedTest
Public Sub S1()
End Sub
End Class
End Class
Los inicializadores de una declaración parcial se seguirán ejecutando en el orden de declaración; sin embargo,
no hay ningún orden garantizado de ejecución para los inicializadores que se producen en declaraciones
parciales independientes.
Tipos construidos
Una declaración de tipo genérico, por sí sola, no denota un tipo. En su lugar, se puede usar una declaración de
tipo genérico como "Blueprint" para formar muchos tipos diferentes mediante la aplicación de argumentos de
tipo. Un tipo genérico que tiene argumentos de tipo aplicados se denomina tipo construido. Los argumentos de
tipo de un tipo construido siempre deben cumplir las restricciones colocadas en los parámetros de tipo con los
que coinciden.
Un nombre de tipo puede identificar un tipo construido aunque no especifique directamente los parámetros de
tipo. Esto puede ocurrir cuando un tipo está anidado dentro de una declaración de clase genérica y el tipo de
instancia de la declaración contenedora se usa implícitamente para la búsqueda de nombres:
Class Outer(Of T)
Public Class Inner
End Class
Un tipo construido C(Of T1,...,Tn) es accesible cuando se puede tener acceso al tipo genérico y a todos los
argumentos de tipo. Por ejemplo, si el tipo genérico C es Public y todos los argumentos de tipo T1,...,Tn
son Public , el tipo construido es Public . Sin embargo, si el nombre de tipo o uno de los argumentos de tipo
es, Private la accesibilidad del tipo construido es Private . Si un argumento de tipo del tipo construido es
Protected y otro argumento de tipo es Friend , el tipo construido solo es accesible en la clase y sus subclases
en este ensamblado o en cualquier ensamblado al que se haya concedido Friend acceso. En otras palabras, el
dominio de accesibilidad de un tipo construido es la intersección de los dominios de accesibilidad de sus partes
constituyentes.
Nota. El hecho de que el dominio de accesibilidad del tipo construido sea la intersección de sus partes
contratadas tiene el efecto secundario interesante de definir un nuevo nivel de accesibilidad. Tipo construido
que contiene un elemento que es Protected y un elemento Friend al que solo se puede tener acceso en
contextos que pueden tener acceso a Friend Protected los miembros y. Sin embargo, no hay ninguna manera
de expresar este nivel de accesibilidad en el lenguaje, ya que la accesibilidad Protected Friend significa que se
puede tener acceso a una entidad en un contexto que puede tener acceso a Friend Protected los miembros o.
La base, las interfaces implementadas y los miembros de los tipos construidos se determinan sustituyendo los
argumentos de tipo proporcionados por cada aparición del parámetro de tipo en el tipo genérico.
Tipos abiertos y tipos cerrados
Un tipo construido para el que uno o más argumentos de tipo son parámetros de tipo de un tipo o método
contenedor se denomina tipo abierto. Esto se debe a que no se conocen algunos de los parámetros de tipo del
tipo, por lo que la forma real del tipo todavía no es totalmente conocida. En cambio, un tipo genérico cuyos
argumentos de tipo son todos los parámetros que no son de tipo se denomina tipo cerrado. La forma de un tipo
cerrado siempre es totalmente conocida. Por ejemplo:
Class Base(Of T, V)
End Class
Class Derived(Of V)
Inherits Base(Of Integer, V)
End Class
Class MoreDerived
Inherits Derived(Of Double)
End Class
El tipo construido Base(Of Integer, V) es un tipo abierto porque, aunque se ha proporcionado el parámetro de
tipo, se ha T proporcionado otro parámetro de tipo al parámetro de tipo U . Por lo tanto, la forma completa
del tipo todavía no se conoce. El tipo construido Derived(Of Double) , sin embargo, es un tipo cerrado porque se
han proporcionado todos los parámetros de tipo en la jerarquía de herencia.
Los tipos abiertos se definen de la siguiente manera:
Un parámetro de tipo es un tipo abierto.
Un tipo de matriz es un tipo abierto si su tipo de elemento es un tipo abierto.
Un tipo construido es un tipo abierto si uno o varios de sus argumentos de tipo son un tipo abierto.
Un tipo cerrado es un tipo que no es un tipo abierto.
Dado que el punto de entrada del programa no puede estar en un tipo genérico, todos los tipos usados en
tiempo de ejecución serán tipos cerrados.
Tipos especiales
El .NET Framework contiene varias clases que el .NET Framework y el lenguaje de Visual Basic tratan de forma
especial:
El tipo System.Void , que representa un tipo void en el .NET Framework, solo se puede hacer referencia
directamente en GetType expresiones.
Los tipos System.RuntimeArgumentHandle System.ArgIterator y System.TypedReference todos pueden contener
punteros en la pila y, por tanto, no pueden aparecer en el montón de .NET Framework. Por lo tanto, no se
pueden usar como tipos de elemento de matriz, tipos de valor devuelto, tipos de campo, argumentos de tipo
genérico, tipos que aceptan valores NULL, ByRef tipos de parámetro, el tipo de un valor que se convierte en
Object o System.ValueType , el destino de una llamada a miembros de instancia de Object o
System.ValueType , o bien se eleva en un cierre.
Conversiones
15/11/2021 • 77 minutes to read
La conversión es el proceso de cambiar un valor de un tipo a otro. Por ejemplo, un valor de tipo Integer se
puede convertir en un valor de tipo Double , o un valor de tipo Derived se puede convertir en un valor de tipo
Base , suponiendo que Base y Derived son ambas clases y Derived hereda de Base . Es posible que las
conversiones no requieran que el propio valor cambie (como en el segundo ejemplo), o que requieran cambios
significativos en la representación del valor (como en el ejemplo anterior).
Las conversiones pueden ser de ampliación o de restricción. Una conversión de ampliación es una conversión
de un tipo a otro tipo cuyo dominio de valor es al menos tan grande, si no mayor, que el dominio del valor del
tipo original. Nunca se producirá un error en las conversiones de ampliación. Una conversión de restricción es
una conversión de un tipo a otro tipo cuyo dominio del valor es menor que el dominio del valor del tipo original
o lo suficientemente no relacionado que se debe tener en cuenta cuando se realiza la conversión (por ejemplo,
al convertir de Integer a String ). Se puede producir un error en las conversiones de restricción, que pueden
implicar la pérdida de información.
La conversión de identidad (es decir, una conversión de un tipo a sí misma) y la conversión de valores
predeterminados (es decir, una conversión de Nothing ) se definen para todos los tipos.
Module Test
Sub Main()
Dim intValue As Integer = 123
Dim longValue As Long = intValue
Por otro lado, las conversiones explícitas requieren operadores de conversión. Al intentar realizar una
conversión explícita en un valor sin un operador de conversión, se produce un error en tiempo de compilación.
En el ejemplo siguiente se usa una conversión explícita para convertir un Long valor en un Integer valor.
Module Test
Sub Main()
Dim longValue As Long = 134
Dim intValue As Integer = CInt(longValue)
El conjunto de conversiones implícitas depende del entorno de compilación y de la Option Strict instrucción.
Si se usan semánticas estrictas, solo se pueden producir implícitamente las conversiones de ampliación. Si se
utiliza la semántica permisiva, todas las conversiones de ampliación y de restricción (es decir, todas las
conversiones) se pueden producir implícitamente.
Conversiones booleanas
Aunque Boolean no es un tipo numérico, tiene conversiones de restricción a y desde los tipos numéricos como
si fuera un tipo enumerado. El literal True convierte al literal para, para, para y 255 Byte 65535 UShort
4294967295 UInteger 18446744073709551615 ULong a la expresión -1 para SByte ,, Short Integer , Long ,,
Decimal Single y Double . El literal False convierte al literal 0 . Un valor numérico cero se convierte en el
literal False . Todos los demás valores numéricos se convierten en el literal True .
Hay una conversión de restricción de booleano a cadena, que se convierte en System.Boolean.TrueString o
System.Boolean.FalseString . También hay una conversión de restricción de String a Boolean : Si la cadena es
igual a TrueString o FalseString (en la referencia cultural actual, sin distinción de mayúsculas y minúsculas),
utiliza el valor adecuado; de lo contrario, intenta analizar la cadena como un tipo numérico (en Hex o octal si es
posible, en caso contrario, como float) y utiliza las reglas anteriores; de lo contrario, produce
System.InvalidCastException .
Conversiones numéricas
Existen Conversiones numéricas entre los tipos Byte , SByte , UShort ,, Short ,, UInteger Integer ULong ,
Long , Decimal , Single y Double , y todos los tipos enumerados. Cuando se convierten, los tipos
enumerados se tratan como si fueran sus tipos subyacentes. Al convertir a un tipo enumerado, no es necesario
que el valor de origen se ajuste al conjunto de valores definido en el tipo enumerado. Por ejemplo:
Enum Values
One
Two
Three
End Enum
Module Test
Sub Main()
Dim x As Integer = 5
Conversiones de referencias
Los tipos de referencia se pueden convertir a un tipo base y viceversa. Las conversiones de un tipo base a un
tipo más derivado solo se realizan correctamente en tiempo de ejecución si el valor que se está convirtiendo es
un valor null, el propio tipo derivado o un tipo más derivado.
Los tipos de clase e interfaz se pueden convertir a y desde cualquier tipo de interfaz. Las conversiones entre un
tipo y un tipo de interfaz solo se realizan correctamente en tiempo de ejecución si los tipos reales implicados
tienen una relación de herencia o de implementación. Dado que un tipo de interfaz siempre contendrá una
instancia de un tipo que se deriva de Object , un tipo de interfaz también se puede convertir siempre a y desde
Object .
Nota. No es un error convertir una NotInheritable clase a y desde interfaces que no implementa, ya que las
clases que representan clases com pueden tener implementaciones de interfaz que no se conocen hasta el
tiempo de ejecución.
Si se produce un error en la conversión de referencia en tiempo de ejecución, System.InvalidCastException se
produce una excepción.
Conversiones de varianza de referencia
Las interfaces o los delegados genéricos pueden tener parámetros de tipo variante que permiten conversiones
entre variantes compatibles del tipo. Por consiguiente, en el tiempo de ejecución una conversión de un tipo de
clase o de un tipo de interfaz a un tipo de interfaz que es variante compatible con un tipo de interfaz que hereda
de o implementa, se realizará correctamente. Del mismo modo, los tipos de delegado se pueden convertir a
tipos de delegado compatibles con variantes y desde ellos. Por ejemplo, el tipo de delegado
Delegate Function F(Of In A, Out R)(a As A) As R
permitiría una conversión de F(Of Object, Integer) a F(Of String, Integer) . Es decir, un delegado F que
toma Object puede usarse de forma segura como delegado F que toma String . Cuando se invoca el
delegado, el método de destino espera un objeto y una cadena es un objeto.
Se dice que un tipo de interfaz o delegado genérico S(Of S1,...,Sn) es compatible con una interfaz genérica o
un tipo de delegado T(Of T1,...,Tn) si:
S y T se construyen a partir del mismo tipo genérico U(Of U1,...,Un) .
Para cada parámetro de tipo Ux :
Si el parámetro de tipo se declaró sin varianza, Sx y Tx debe ser del mismo tipo.
Si se declaró el parámetro de tipo In , debe haber una conversión de identidad, valor
predeterminado, referencia, matriz o parámetro de tipo de ampliación de Sx a Tx .
Si se declaró el parámetro de tipo Out , debe haber una conversión de identidad, valor
predeterminado, referencia, matriz o parámetro de tipo de ampliación de Tx a Sx .
Al convertir de una clase a una interfaz genérica con parámetros de tipo variante, si la clase implementa más de
una interfaz compatible con una variante, la conversión es ambigua si no hay una conversión no variante. Por
ejemplo:
Class Base
End Class
Class Derived1
Inherits Base
End Class
Class Derived2
Inherits Base
End Class
Class OneAndTwo
Implements IEnumerable(Of Derived1)
Implements IEnumerable(Of Derived2)
End Class
Class BaseAndOneAndTwo
Implements IEnumerable(Of Base)
Implements IEnumerable(Of Derived1)
Implements IEnumerable(Of Derived2)
End Class
Module Test
Sub Main()
' Error: conversion is ambiguous
Dim x As IEnumerable(Of Base) = New OneAndTwo()
Tenga en cuenta que los tipos System.Delegate y System.MulticastDelegate no se consideran tipos de delegado
(aunque todos los tipos de delegado hereden de ellos). Además, tenga en cuenta que la conversión del tipo de
delegado anónimo a un tipo de delegado compatible no es una conversión de referencia.
Conversiones de matriz
Además de las conversiones que se definen en las matrices en virtud del hecho de que son tipos de referencia,
existen varias conversiones especiales para las matrices.
Para dos tipos cualesquiera A y B , si son tipos de referencia o parámetros de tipo a los que no se sabe que
son tipos de valor, y si A tiene una referencia, una matriz o una conversión de parámetro de tipo en B , existe
una conversión de una matriz de tipo A a una matriz de tipo B con el mismo rango. Esta relación se conoce
como covarianza de matriz. En particular, la covarianza de matrices significa que un elemento de una matriz
cuyo tipo de elemento es B realmente puede ser un elemento de una matriz cuyo tipo de elemento es A ,
siempre que tanto A como B sean tipos de referencia y que B tengan una conversión de referencia o una
conversión de matriz en A . En el ejemplo siguiente, la segunda invocación de F provoca que
System.ArrayTypeMismatchException se produzca una excepción porque el tipo de elemento real de b es String
, no Object :
Module Test
Sub F(ByRef x As Object)
End Sub
Sub Main()
Dim a(10) As Object
Dim b() As Object = New String(10) {}
F(a(0)) ' OK.
F(b(1)) ' Not allowed: System.ArrayTypeMismatchException.
End Sub
End Module
Debido a la covarianza de matriz, las asignaciones a elementos de matrices de tipos de referencia incluyen una
comprobación en tiempo de ejecución que garantiza que el valor que se asigna al elemento de matriz es
realmente de un tipo permitido.
Module Test
Sub Fill(array() As Object, index As Integer, count As Integer, _
value As Object)
Dim i As Integer
Sub Main()
Dim strings(100) As String
En este ejemplo, la asignación a array(i) in Method Fill incluye implícitamente una comprobación en
tiempo de ejecución que garantiza que el objeto al que hace referencia la variable value sea Nothing o una
instancia de un tipo que sea compatible con el tipo de elemento real de array array . En Main el método, las
dos primeras invocaciones del método Fill se realizan correctamente, pero la tercera invocación hace que
System.ArrayTypeMismatchException se produzca una excepción al ejecutar la primera asignación a array(i) . La
excepción se produce porque Integer no se puede almacenar en una String matriz.
Si uno de los tipos de elemento de matriz es un parámetro de tipo cuyo tipo se convierte en un tipo de valor en
tiempo de ejecución, se System.InvalidCastException producirá una excepción. Por ejemplo:
Module Test
Sub F(Of T As U, U)(x() As T)
Dim y() As U = x
End Sub
Sub Main()
' F will throw an exception because Integer() cannot be
' converted to Object()
F(New Integer() { 1, 2, 3 })
End Sub
End Module
También existen conversiones entre una matriz de un tipo enumerado y una matriz del tipo subyacente del tipo
enumerado o una matriz de otro tipo enumerado con el mismo tipo subyacente, siempre que las matrices
tengan el mismo rango.
Enum Color As Byte
Red
Green
Blue
End Enum
Module Test
Sub Main()
Dim a(10) As Color
Dim b() As Integer
Dim c() As Byte
En este ejemplo, una matriz de Color se convierte en una matriz del tipo subyacente de, y desde ella Byte
Color . La conversión a una matriz de Integer , sin embargo, será un error porque Integer no es el tipo
subyacente de Color .
Una matriz de rango 1 de tipo A() también tiene una conversión de matriz a los tipos de interfaz de colección
IList(Of B) , IReadOnlyList(Of B) , ICollection(Of B) IReadOnlyCollection(Of B) y IEnumerable(Of B) se
encuentra en System.Collections.Generic , siempre que se cumpla una de las siguientes condiciones:
A y B son tipos de referencia o parámetros de tipo a los que no se sabe que son tipos de valor; y A tienen
una conversión de ampliación, una matriz o un parámetro de tipo a B ; o
A y B son tipos enumerados del mismo tipo subyacente; o
uno de A y B es un tipo enumerado y el otro es su tipo subyacente.
Cualquier matriz de tipo A con cualquier rango también tiene una conversión de matriz a los tipos de interfaz de
colección no genérica IList ICollection y IEnumerable se encuentra en System.Collections .
Es posible recorrer en iteración las interfaces resultantes mediante For Each o invocando directamente los
GetEnumerator métodos. En el caso de las matrices Rank-1 convertidas en formas genéricas o no genéricas de
IList o ICollection , también es posible obtener elementos por índice. En el caso de las matrices Rank-1
convertidas en formas genéricas o no genéricas de IList , también es posible establecer elementos por índice,
sujetos a las mismas comprobaciones de covarianza de matriz en tiempo de ejecución, como se ha descrito
anteriormente. El comportamiento de todos los demás métodos de interfaz no está definido por la
especificación del lenguaje VB; depende del tiempo de ejecución subyacente.
Class Class1
Public Value As Integer = 0
End Class
Structure Struct1
Public Value As Integer
End Structure
Module Test
Sub Main()
Dim val1 As Object = New Struct1()
Dim val2 As Object = val1
val2.Value = 123
ref2.Value = 123
Values: 0, 123
Refs: 123, 123
La asignación al campo de la variable local no val2 afecta al campo de la variable local val1 porque cuando
Struct1 se asignó a la conversión boxing a val2 , se realizó una copia del valor. En cambio, la asignación
ref2.Value = 123 afecta al objeto al que ref1 ref2 hacen referencia y.
Nota. La copia de la estructura no se realiza para las estructuras de conversión boxing escritas como
System.ValueType porque no es posible enlazar en tiempo de ejecución System.ValueType .
Existe una excepción a la regla de que los tipos de valor con conversión Boxing se copiarán en la asignación. Si
una referencia de tipo de valor con conversión Boxing se almacena dentro de otro tipo, la referencia interna no
se copiará. Por ejemplo:
Structure Struct1
Public Value As Object
End Structure
Module Test
Sub Main()
Dim val1 As Struct1
Dim val2 As Struct1
val2 = val1
val2.Value.Value = 123
Console.WriteLine("Values: " & val1.Value.Value & ", " & _
val2.Value.Value)
End Sub
End Module
Esto se debe a que el valor interno de la conversión boxing no se copia cuando se copia el valor. Por lo tanto,
val1.Value y val2.Value tienen una referencia al mismo tipo de valor con conversión boxing.
Nota. El hecho de que los tipos de valor de conversión boxing internos no se copien es una limitación del
sistema de tipos de .NET, para asegurarse de que todos los tipos de valores de conversión boxing internos se
copian cada vez que se copia un valor de tipo, Object serían muy caros.
Como se ha descrito anteriormente, solo se puede aplicar la conversión unboxing a los tipos de valor con
conversión boxing a su tipo original. Sin embargo, los tipos primitivos con conversión Boxing se tratan de forma
especial cuando se escriben como Object . Se pueden convertir en cualquier otro tipo primitivo al que tengan
una conversión. Por ejemplo:
Module Test
Sub Main()
Dim o As Object = 5
Dim b As Byte = CByte(o) ' Legal
Console.WriteLine(b) ' Prints 5
End Sub
End Module
Normalmente, el valor al que se aplicó la conversión boxing Integer 5 no se podía desempaquetar en una
Byte variable. Sin embargo, Integer dado Byte que y son tipos primitivos y tienen una conversión, se
permite la conversión.
Es importante tener en cuenta que la conversión de un tipo de valor en una interfaz es diferente de un
argumento genérico restringido a una interfaz. Al tener acceso a los miembros de la interfaz en un parámetro de
tipo restringido (o llamando a los métodos en Object ), la conversión boxing no se produce como cuando un
tipo de valor se convierte en una interfaz y se tiene acceso a un miembro de interfaz. Por ejemplo, supongamos
ICounter que una interfaz contiene un método Increment que se puede utilizar para modificar un valor. Si
ICounter se utiliza como una restricción, Increment se llama a la implementación del método con una
referencia a la variable a la que Increment se llamó, no una copia con conversión boxing:
Interface ICounter
Sub Increment()
ReadOnly Property Value() As Integer
End Interface
Structure Counter
Implements ICounter
Module Test
Sub Test(Of T As ICounter)(x As T)
Console.WriteLine(x.value)
x.Increment() ' Modify x
Console.WriteLine(x.value)
CType(x, ICounter).Increment() ' Modify boxed copy of x
Console.WriteLine(x.value)
End Sub
Sub Main()
Dim x As Counter
Test(x)
End Sub
End Module
0
1
1
Por ejemplo, existe una conversión de ampliación intrínseca de Integer? a Long? porque existe una conversión
de ampliación intrínseca de Integer a Long :
Dim i As Integer? = 10
Dim l As Long? = i
Debido al comportamiento del tipo subyacente System.Nullable(Of T) , cuando se aplica la conversión boxing a
un tipo de valor que acepta valores NULL T? , el resultado es un valor de conversión boxing de tipo T , no un
valor de conversión boxing de tipo T? . Y, a la inversa, cuando se aplica la conversión unboxing a un tipo de
valor que acepta valores NULL T? , el valor se ajustará mediante System.Nullable(Of T) y se Nothing le
aplicará la conversión unboxing a un valor null de tipo T? . Por ejemplo:
Un efecto secundario de este comportamiento es que un tipo de valor que acepta valores NULL T? parece
implementar todas las interfaces de T , ya que la conversión de un tipo de valor en una interfaz requiere que se
aplique la conversión boxing al tipo. Como resultado, T? se pueden convertir en todas las interfaces que T se
pueden convertir en. Sin embargo, es importante tener en cuenta que un tipo de valor que acepta valores NULL
no T? implementa realmente las interfaces de T para los fines de la comprobación o reflexión de restricciones
genéricas. Por ejemplo:
Interface I1
End Interface
Structure T1
Implements I1
...
End Structure
Module Test
Sub M1(Of T As I1)(ByVal x As T)
End Sub
Sub Main()
Dim x? As T1 = Nothing
Dim y As I1 = x ' Valid
M1(x) ' Error: x? does not satisfy I1 constraint
End Sub
End Module
Conversiones de cadenas
Char Al convertir en, String se obtiene una cadena cuyo primer carácter es el valor del carácter. String Al
convertir en, Char se obtiene un carácter cuyo valor es el primer carácter de la cadena. Al convertir una matriz
de Char en, String se obtiene una cadena cuyos caracteres son los elementos de la matriz. Al convertir
String en una matriz de Char se obtiene una matriz de caracteres cuyos elementos son los caracteres de la
cadena.
Las conversiones exactas entre y Boolean , Byte , SByte , UShort , Short ,,,,,,, UInteger Integer
String
ULong Long Decimal Single Double , Date y viceversa, quedan fuera del ámbito de esta especificación y
dependen de la implementación con la excepción de un detalle. Las conversiones de cadena siempre consideran
la referencia cultural actual del entorno en tiempo de ejecución. Como tal, deben realizarse en tiempo de
ejecución.
conversiones de ampliación
Las conversiones de ampliación nunca se desbordan, pero pueden suponer una pérdida de precisión. Las
conversiones siguientes son conversiones de ampliación:
Conversiones de identidad/predeterminadas
De un tipo a sí mismo.
Desde un tipo de delegado anónimo generado para una reclasificación de método lambda a cualquier
tipo de delegado con una firma idéntica.
Del literal Nothing a un tipo.
Conversiones numéricas
De Byte a UShort , Short , UInteger , Integer , ULong , Long , Decimal , Single o Double .
De SByte a Short , Integer ,,, Long Decimal Single o Double .
De UShort a UInteger , Integer , ULong , Long , Decimal , Single o Double .
De Short a Integer , Long , Decimal Single o Double .
De UInteger a ULong , Long , Decimal , Single o Double .
De Integer a Long , Decimal Single o Double .
De ULong a Decimal , Single o Double .
De Long a Decimal , Single o Double .
De Decimal a Single o Double .
De Single a Double .
Del literal 0 a un tipo enumerado. (Nota. La conversión de 0 a cualquier tipo enumerado es ampliable
para simplificar las marcas de prueba. Por ejemplo, si Values es un tipo enumerado con un valor One ,
puede probar una variable v de tipo Values diciendo (v And Values.One) = 0 ).
De un tipo enumerado a su tipo numérico subyacente, o a un tipo numérico en el que su tipo numérico
subyacente tiene una conversión de ampliación a.
Desde una expresión constante de tipo ULong , Long , UInteger , Integer , UShort , Short , Byte o
SByte a un tipo más estrecho, siempre que el valor de la expresión constante esté dentro del intervalo
del tipo de destino. (Nota. Las conversiones de UInteger o Integer a Single , ULong o Long a
Single o Double , o Decimal a Single o Double pueden provocar una pérdida de precisión, pero
nunca producirán una pérdida de magnitud. Las demás Conversiones numéricas de ampliación nunca
pierden información).
Conversiones de referencia
Desde un tipo de referencia a un tipo base.
Desde un tipo de referencia a un tipo de interfaz, siempre que el tipo implemente la interfaz o una
interfaz compatible con una variante.
Desde un tipo de interfaz a Object .
Desde un tipo de interfaz a un tipo de interfaz compatible con Variant.
Desde un tipo de delegado hasta un tipo de delegado compatible con Variant. (Nota. Estas reglas
implican muchas otras conversiones de referencia. Por ejemplo, los delegados anónimos son tipos de
referencia que heredan de; los tipos de System.MulticastDelegate matriz son tipos de referencia que
heredan de System.Array ; los tipos anónimos son tipos de referencia que heredan de System.Object .)
conversiones de restricción
Las conversiones de restricción son conversiones que no se pueden demostrar que siempre se realizan
correctamente, las conversiones que pueden perder información y las conversiones entre dominios de tipos lo
suficientemente diferentes como para merecer la notación de restricción. Las conversiones siguientes se
clasifican como conversiones de restricción:
Conversiones booleanas
De Boolean a Byte , SByte , UShort , Short , UInteger , Integer , ULong , Long , Decimal , Single
o Double .
De Byte , SByte , UShort , Short , UInteger , Integer , ULong , Long , Decimal , Single o Double a
Boolean .
Conversiones numéricas
De Byte a SByte .
De SByte a Byte , UShort , UInteger o ULong .
De UShort a Byte , SByte o Short .
De Short a Byte , SByte , UShort , UInteger o ULong .
De UInteger a Byte , SByte , UShort , Short o Integer .
De Integer a Byte , SByte ,,, UShort Short UInteger o ULong .
De ULong a Byte , SByte , UShort , Short , UInteger , Integer o Long .
De Long a Byte , SByte , UShort , Short , UInteger , Integer o ULong .
De Decimal a Byte , SByte , UShort , Short , UInteger , Integer , ULong o Long .
De Single a Byte , SByte , UShort , Short , UInteger , Integer , ULong , Long o Decimal .
De Double a Byte , SByte , UShort , Short , UInteger , Integer , ULong , Long , Decimal o Single .
De un tipo numérico a un tipo enumerado.
De un tipo enumerado a un tipo numérico, su tipo numérico subyacente tiene una conversión de
restricción a.
De un tipo enumerado a otro tipo enumerado.
Conversiones de referencia
Desde un tipo de referencia a un tipo más derivado.
Desde un tipo de clase a un tipo de interfaz, siempre que el tipo de clase no implemente el tipo de
interfaz o una variante de tipo de interfaz compatible con él.
De un tipo de interfaz a un tipo de clase.
Desde un tipo de interfaz a otro tipo de interfaz, siempre que no exista una relación de herencia entre los
dos tipos y siempre que no sea compatible con variantes.
Conversiones de delegado anónimas
Desde un tipo de delegado anónimo generado para una reclasificación de método lambda a cualquier tipo
de delegado más estrecho.
Conversiones de matriz
Desde un tipo de matriz S con un tipo de elemento Se , a un tipo de matriz T con un tipo de elemento
Te , siempre que se cumplan todas las condiciones siguientes:
Las reglas de conversiones anteriores no permiten conversiones de parámetros de tipo sin restricciones a tipos
que no son de interfaz, lo que puede resultar sorprendente. El motivo es evitar la confusión sobre la semántica
de dichas conversiones. Por ejemplo, consideremos la siguiente declaración:
Class X(Of T)
Public Shared Function F(t As T) As Long
Return CLng(t) ' Error, explicit conversion not permitted
End Function
End Class
Si se permite la conversión de T a Integer , es posible que cabría esperar que X(Of Integer).F(7) devolvería
7L . Sin embargo, no lo haría, porque las conversiones numéricas solo se tienen en cuenta cuando se sabe que
los tipos son numéricos en tiempo de compilación. Para que la semántica esté clara, en su lugar se debe escribir
en el ejemplo anterior:
Class X(Of T)
Public Shared Function F(t As T) As Long
Return CLng(CObj(t)) ' OK, conversions permitted
End Function
End Class
Structure T
Public Shared Widening Operator CType(ByVal v As T) As S
...
End Operator
End Structure
Module Test
Sub Main()
Dim x As T?
Dim y As S?
Cuando se resuelven las conversiones, siempre se prefieren los operadores de conversiones definidos por el
usuario a los operadores de conversión de elevación. Por ejemplo:
Structure S
...
End Structure
Structure T
Public Shared Widening Operator CType(ByVal v As T) As S
...
End Operator
Module Test
Sub Main()
Dim x As T?
Dim y As S?
En tiempo de ejecución, la evaluación de una conversión definida por el usuario puede implicar hasta tres pasos:
1. En primer lugar, el valor se convierte del tipo de origen al tipo de operando mediante una conversión
intrínseca, si es necesario.
2. A continuación, se invoca la conversión definida por el usuario.
3. Por último, el resultado de la conversión definida por el usuario se convierte al tipo de destino mediante
una conversión intrínseca, si es necesario.
Es importante tener en cuenta que la evaluación de una conversión definida por el usuario nunca implicará a
más de un operador de conversión definido por el usuario.
Conversión de ampliación más específica
La determinación del operador de conversión de ampliación definido por el usuario más específico entre dos
tipos se realiza mediante los pasos siguientes:
1. En primer lugar, se recopilan todos los operadores de conversión candidatas. Los operadores de
conversión candidata son todos los operadores de conversión de ampliación definidos por el usuario en
el tipo de origen y todos los operadores de conversión de ampliación definidos por el usuario en el tipo
de destino.
2. A continuación, se quitan del conjunto todos los operadores de conversión no aplicables. Un operador de
conversión es aplicable a un tipo de origen y a un tipo de destino si hay un operador de conversión de
ampliación intrínseco del tipo de origen al tipo de operando y hay un operador de conversión de
ampliación intrínseco del resultado del operador al tipo de destino. Si no hay ningún operador de
conversión aplicable, no hay ninguna conversión de ampliación más específica.
3. A continuación, se determina el tipo de origen más específico de los operadores de conversión aplicables:
Si alguno de los operadores de conversión convierte directamente del tipo de origen, el tipo de
origen es el tipo de origen más específico.
De lo contrario, el tipo de origen más específico es el tipo más abarcado en el conjunto combinado
de tipos de origen de los operadores de conversión. Si no se puede encontrar ningún tipo más
abarcado, no hay ninguna conversión de ampliación más específica.
4. A continuación, se determina el tipo de destino más específico de los operadores de conversión
aplicables:
Si alguno de los operadores de conversión convierte directamente en el tipo de destino, el tipo de
destino es el tipo de destino más específico.
De lo contrario, el tipo de destino más específico es el tipo más abarcado en el conjunto
combinado de tipos de destino de los operadores de conversión. Si no se puede encontrar ningún
tipo más englobado, no hay ninguna conversión de ampliación más específica.
5. A continuación, si exactamente un operador de conversión convierte el tipo de origen más específico al
tipo de destino más específico, éste es el operador de conversión más específico. Si existe más de un
operador de este tipo, no hay una conversión de ampliación más específica.
Conversión de restricción más específica
La determinación del operador de conversión de restricción definido por el usuario más específico entre dos
tipos se realiza mediante los pasos siguientes:
1. En primer lugar, se recopilan todos los operadores de conversión candidatas. Los operadores de
conversión candidata son todos los operadores de conversión definidos por el usuario en el tipo de
origen y todos los operadores de conversión definidos por el usuario en el tipo de destino.
2. A continuación, se quitan del conjunto todos los operadores de conversión no aplicables. Un operador de
conversión es aplicable a un tipo de origen y a un tipo de destino si hay un operador de conversión
intrínseco del tipo de origen al tipo de operando y hay un operador de conversión intrínseco del
resultado del operador al tipo de destino. Si no hay ningún operador de conversión aplicable, no hay
ninguna conversión de restricción más específica.
3. A continuación, se determina el tipo de origen más específico de los operadores de conversión aplicables:
Si alguno de los operadores de conversión convierte directamente del tipo de origen, el tipo de
origen es el tipo de origen más específico.
De lo contrario, si alguno de los operadores de conversión convierte de tipos que abarcan el tipo
de origen, el tipo de origen más específico es el tipo más abarcado en el conjunto combinado de
tipos de origen de esos operadores de conversión. Si no se puede encontrar ningún tipo más
abarcado, no hay ninguna conversión de restricción más específica.
De lo contrario, el tipo de origen más específico es el tipo más abarcado en el conjunto combinado
de tipos de origen de los operadores de conversión. Si no se puede encontrar ningún tipo más
englobado, no hay ninguna conversión de restricción más específica.
4. A continuación, se determina el tipo de destino más específico de los operadores de conversión
aplicables:
Si alguno de los operadores de conversión convierte directamente en el tipo de destino, el tipo de
destino es el tipo de destino más específico.
De lo contrario, si cualquiera de los operadores de conversión convierte en tipos que están
englobados por el tipo de destino, el tipo de destino más específico es el tipo más abarcado en el
conjunto combinado de tipos de origen de esos operadores de conversión. Si no se puede
encontrar ningún tipo más englobado, no hay ninguna conversión de restricción más específica.
De lo contrario, el tipo de destino más específico es el tipo más abarcado en el conjunto
combinado de tipos de destino de los operadores de conversión. Si no se puede encontrar ningún
tipo más abarcado, no hay ninguna conversión de restricción más específica.
5. A continuación, si exactamente un operador de conversión convierte el tipo de origen más específico al
tipo de destino más específico, éste es el operador de conversión más específico. Si existe más de un
operador de este tipo, no hay ninguna conversión de restricción más específica.
Conversiones nativas
Algunas de las conversiones se clasifican como conversiones nativas porque son compatibles de forma nativa
con el .NET Framework. Estas conversiones son aquellas que se pueden optimizar mediante el uso de los
DirectCast operadores de TryCast conversión y, así como otros comportamientos especiales. Las
conversiones clasificadas como conversiones nativas son: conversiones de identidad, conversiones
predeterminadas, conversiones de referencia, conversiones de matriz, conversiones de tipo de valor y
conversiones de parámetros de tipo.
Tipo dominante
Dado un conjunto de tipos, a menudo es necesario en situaciones como la inferencia de tipos para determinar el
tipo dominante del conjunto. El tipo dominante de un conjunto de tipos se determina quitando primero los tipos
que no tienen una conversión implícita en uno o varios tipos. Si no hay ningún tipo restante en este punto, no
hay ningún tipo dominante. El tipo dominante es el más abarcado de los tipos restantes. Si hay más de un tipo
que está más englobado, no hay ningún tipo dominante.
Miembros de tipos
15/11/2021 • 157 minutes to read
Los miembros de tipo definen las ubicaciones de almacenamiento y el código ejecutable. Pueden ser métodos,
constructores, eventos, constantes, variables y propiedades.
ImplementsClause
: ( 'Implements' ImplementsList )?
;
ImplementsList
: InterfaceMemberSpecifier ( Comma InterfaceMemberSpecifier )*
;
InterfaceMemberSpecifier
: NonArrayTypeName Period IdentifierOrKeyword
;
Los métodos y las propiedades que implementan miembros de interfaz son implícitamente NotOverridable a
menos que se declaren MustOverride , Overridable o invalide otro miembro. Es un error que un miembro que
implementa un miembro de interfaz sea Shared . La accesibilidad de un miembro no tiene ningún efecto en su
capacidad para implementar miembros de interfaz.
Para que una implementación de interfaz sea válida, la lista de implementaciones del tipo contenedor debe
nombrar una interfaz que contenga un miembro compatible. Un miembro compatible es aquél cuya firma
coincide con la firma del miembro que implementa. 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)
Class C2(Of U)
Implements I1(Of U)
Si un evento declarado mediante un tipo de delegado está implementando un evento de interfaz, un evento
compatible es aquél cuyo tipo de delegado subyacente es el mismo tipo. De lo contrario, el evento usa el tipo de
delegado desde el 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
Class Label
Implements ClickEvents
Interface ILeft
Sub F()
End Interface
Interface IRight
Sub F()
End Interface
Class Test
Implements ILeft, IRight
Si el miembro de interfaz que se está implementando no está disponible en todas las interfaces implementadas
explícitamente debido a la herencia de varias interfaces, el miembro que implementa debe hacer referencia
explícita a una interfaz base en la que el miembro esté disponible. Por ejemplo, si I1 y I2 contienen un
miembro M y I3 hereda de I1 y I2 , un tipo que implementa I3 implementará I1.M y I2.M . Si una
interfaz oculta los miembros heredados, un tipo de implementación tendrá que implementar los miembros
heredados y los miembros que los sombrean.
Interface ILeft
Sub F()
End Interface
Interface IRight
Sub F()
End Interface
Interface ILeftRight
Inherits ILeft, IRight
Class Test
Implements ILeftRight
Si la interfaz contenedora del miembro de interfaz se va a implementar 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)
Class C2(Of U)
Implements I1(Of U)
Métodos
Los métodos contienen las instrucciones ejecutables de un programa.
MethodMemberDeclaration
: MethodDeclaration
| ExternalMethodDeclaration
| 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'
| 'MustOverride'
;
InterfaceProcedureModifier
: 'Shadows' | 'Overloads'
;
HandlesOrImplements
: HandlesClause
| ImplementsClause
;
Los métodos, que tienen una lista opcional de parámetros y un valor devuelto opcional, son compartidos o no
compartidos. Se tiene acceso a los métodos compartidos a través de la clase o instancias de la clase. Se obtiene
acceso 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 ):
Module Test
Sub Main()
Dim s As Stack = New Stack()
Dim i As Integer
While i < 10
s.Push(i)
End While
Los métodos se pueden sobrecargar, lo que significa que varios métodos pueden tener el mismo nombre
siempre y cuando tengan firmas únicas. La signatura de un método consta del número y los tipos de sus
parámetros. La firma de un método no incluye específicamente los modificadores de parámetro o tipo de valor
devuelto, como opcional, ByRef o ParamArray. En el ejemplo siguiente se muestra una clase con varias
sobrecargas:
Module Test
Sub F()
Console.WriteLine("F()")
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
F()
F(Integer)
F(Object)
F(Integer, Integer)
F(Integer())
G(String)
G(String, Optional String)
Las sobrecargas que difieren solo en 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:
A continuación, V2 de la biblioteca desea agregar otro parámetro opcional "Password" y desea hacerlo sin
interrumpir la compatibilidad con el origen (por lo que las aplicaciones que se usan para el destino v1 se
pueden volver a compilar) y sin interrumpir la compatibilidad binaria (por lo que las aplicaciones que solían
hacer referencia a v1 ahora pueden hacer referencia a V2 sin recompilación). Este es 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 en una API pública no son conformes a CLS. Sin embargo, se
pueden usar al menos Visual Basic y C# 4 y F #.
Declaraciones de métodos regulares, asincrónicas e iteradores
Hay dos tipos de métodos: las subrutinas, que no devuelven valores, y las funciones, que sí lo hacen. El cuerpo y
la End 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 utiliza 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 el dominio de accesibilidad del propio método o un
superconjunto del mismo.
Un método normal es uno sin Async Iterator Modificadores ni ni. Puede ser una subrutina o una función.
La sección métodos regulares detalla lo que ocurre cuando se invoca un método normal.
Un método 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 , IEnumerable o IEnumerator(Of T) o IEnumerable(Of T) para
algunos T , y no debe tener ningún ByRef parámetro. Sección métodos de iterador detalla lo que ocurre
cuando se invoca un método iterador.
Un método asincrónico es aquél con el Async modificador y ningún Iterator modificador. Debe ser una
subrutina, una función con el tipo de valor devuelto Task o Task(Of T) para algunos T , y no debe tener
ByRef parámetros. Sección métodos asincrónicos detalla lo que ocurre 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 subrutinas y funciones son especiales, por lo que sus instrucciones iniciales y finales deben
comenzar al principio de una línea lógica. Además, el cuerpo de una declaración de MustOverride función o no
subrutina debe empezar 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
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 de método ni End construcción. Los métodos externos están compartidos implícitamente, es posible
que no tengan parámetros de tipo y que no controlen los eventos ni implementen los miembros de la interfaz.
Si no se especifica ningún tipo de valor devuelto en una función y se utiliza 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 de
un # carácter) o el nombre del método en el archivo externo. También se puede especificar un modificador de
juego de un solo carácter, que rige el juego de caracteres utilizado para calcular las referencias de las cadenas
durante una llamada al método externo. El Unicode modificador calcula las referencias de todas las cadenas a
valores Unicode, el Ansi modificador calcula las referencias de todas las cadenas a valores ANSI y el Auto
modificador calcula las referencias de las cadenas según las reglas de .NET Framework basadas en 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 Unicode se especifica o, se busca el nombre del método en el archivo externo sin ninguna
modificación. Si Auto se especifica, la búsqueda de nombre de método depende de la plataforma. Si se
considera que la plataforma es ANSI (por ejemplo, Windows 95, Windows 98 o Windows ME), el nombre del
método se busca sin modificaciones. Si se produce un error en la búsqueda, A se anexa un y se vuelve a
intentar la búsqueda. Si se considera que la plataforma es Unicode (por ejemplo, Windows NT, Windows 2000 y
Windows XP), W se anexa un y se busca el nombre. Si se produce un error en la búsqueda, se vuelve a intentar
la búsqueda sin el W . Por ejemplo:
Module Test
' All platforms bind to "ExternSub".
Declare Ansi Sub ExternSub Lib "ExternDLL" ()
Se calculan las referencias de los tipos de datos que se pasan a métodos externos 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 en el tipo BSTR de automatización OLE y los cambios realizados en BSTR
en el método externo se reflejan de nuevo en el argumento de cadena. Esto se debe String a que el tipo de los
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 serializan como un 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.
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 es reemplazable. 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 reemplazar más. El MustOverride modificador indica que un
método se debe invalidar en clases derivadas.
Ciertas combinaciones de estos modificadores no son válidas:
Overridable y NotOverridable se excluyen mutuamente y no se pueden combinar.
MustOverride implica Overridable (y, por tanto, no puede especificarla) y no se puede combinar con
NotOverridable .
En el ejemplo siguiente se muestran las diferencias entre los métodos reemplazables y no reemplazables:
Class Base
Public Sub F()
Console.WriteLine("Base.F")
End Sub
Class Derived
Inherits Base
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 introduce un método F y un Overridable método G . La clase Derived introduce
un nuevo método F , con lo que sombrea el heredado F y también invalida el método heredado G . El
ejemplo genera el siguiente resultado:
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 de 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. No obstante, es válido usar una instancia para calificar un método
compartido. No es válido hacer referencia a Me , MyClass o MyBase en un método compartido. Los métodos
compartidos no pueden ser Overridable , NotOverridable o MustOverride , y no pueden invalidar los métodos.
Los métodos definidos en las interfaces y los módulos estándar no pueden especificar Shared , porque 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 tener acceso 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
El método F muestra que en un miembro de función de instancia, se puede utilizar un identificador para tener
acceso a los miembros de instancia y a los miembros compartidos. G El método muestra que en un miembro
de función compartida, es un error tener acceso a un miembro de instancia a través de un identificador. Main El
método 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 tener acceso 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 utilizar 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 la 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 con 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 de valor, referencia, opcional o ParamArray por los
modificadores ByVal , ByRef , Optional y ParamArray , respectivamente. Un parámetro que no especifica o
tiene como ByRef ByVal valor predeterminado ByVal .
Los nombres de parámetro se limitan al cuerpo completo 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étodos externos y las declaraciones de delegado no tienen cuerpo, se permiten
nombres de parámetros duplicados en las listas de parámetros, pero no se recomienda.
El identificador puede ir seguido del modificador de nombre que acepta valores NULL ? para indicar que
acepta valores NULL y también de 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 existir una As cláusula.
Parámetros de valor
Un parámetro de valor se declara con un ByVal modificador explícito. Si ByVal se utiliza el modificador, ByRef
no se puede especificar el modificador. Un parámetro de valor se encuentra en existencia con la invocación del
miembro al que pertenece el parámetro y se inicializa con el valor del argumento proporcionado en la
invocación. Un parámetro de valor deja de existir cuando se devuelve el miembro.
Un método puede asignar nuevos valores a un parámetro de valor. Dichas asignaciones solo afectan a la
ubicación de almacenamiento local representada por el parámetro de valor. no tienen ningún efecto en el
argumento real proporcionado en la invocación del método.
Un parámetro de valor 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
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 ByRef se especifica el
modificador, ByVal no se puede usar el 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 del método o del constructor. Conceptualmente, el valor de un parámetro de
referencia es siempre el mismo que el de la variable subyacente.
Los parámetros de referencia actúan en dos modos, ya sea como alias o a través de la copia de seguridad de
copia.
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 de
forma directa e inmediata afectan 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
pre: x = 1, y = 2
post: x = 2, y = 1
Para la invocación del método Swap de 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 G()
F(s, s)
End Sub
End Module
En el ejemplo, la invocación del método F en G pasa una referencia a s para a y b . Por lo tanto, para esa
invocación, los nombres s , a y b hacen referencia a la misma ubicación de almacenamiento, y las tres
asignaciones modifican la variable de instancia s .
Copia: copia de seguridad. 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 se pasa una variable no variable (por ejemplo, una
propiedad) 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 volverá a copiar en la variable original (si hay
alguno y si es grabable) cuando el método vuelva. Por lo tanto, un parámetro de referencia no puede contener
necesariamente una referencia al almacenamiento exacto de la variable que se pasa y cualquier cambio en el
parámetro de referencia no se puede reflejar 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
Sub Main()
Dim d As Derived
En el caso de la primera invocación de F , se crea una variable temporal y se le asigna el valor de la propiedad
G y se pasa a F . Tras la devolución de F , el valor de la variable temporal se vuelve a asignar a la propiedad
de G . En el segundo caso, se crea otra variable temporal y se le asigna el valor de d y se pasa a F . Al
devolver desde F , el valor de la variable temporal se vuelve a convertir al tipo de la variable, Derived y se
asigna a d . Puesto que no se puede convertir el valor que se pasa a Derived , se produce una excepción en
tiempo de ejecución.
Parámetros opcionales
Un parámetro opcional se declara 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 siguientes parámetros, se desencadenará un error en tiempo de compilación. Un parámetro
opcional de algún tipo que acepte valores NULL o de un T? tipo que no acepte valores NULL T debe
especificar una expresión constante que e se usará como valor predeterminado si no se especifica ningún
argumento. Si se e evalúa como Nothing de tipo Object, se usará el valor predeterminado del tipo de
parámetro como valor predeterminado del 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 de un parámetro es válido. La
inicialización siempre se realiza como parte de la expresión de invocación, no en el 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
x = 10, y = 20
x = 30, y = 40
Como alternativa, la invocación puede especificar cero o más argumentos para ParamArray , donde cada
argumento es una expresión de un tipo que se puede convertir implícitamente al tipo de elemento de
ParamArray . En este caso, la invocación crea una instancia del ParamArray tipo con una longitud
correspondiente al número de argumentos, inicializa los elementos de la instancia de la matriz con los
valores de argumento especificados y utiliza la instancia de matriz recién creada como argumento real.
A excepción de 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
Sub Main()
Dim a As Integer() = { 1, 2, 3 }
F(a)
F(10, 20, 30, 40)
F()
End Sub
End Module
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 una variable compartida en el tipo contenedor que
especifique el WithEvents modificador o la MyBase MyClass Me palabra clave o; de lo contrario, se
producirá un error en tiempo de compilación. Esta variable contiene el objeto que generará los eventos
administrados por este método.
El segundo identificador debe especificar un miembro del tipo del primer identificador. El miembro debe
ser un evento y se puede compartir. 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. A diferencia AddHandler de una instrucción, sin embargo, los
controladores de eventos explícitos permiten controlar un evento con un método sin argumentos,
independientemente de si se utiliza o no la semántica estricta:
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 solo 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 para 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 Main()
x = New Raiser()
x.Raise()
x.Raise()
End Sub
End Module
Se imprimirá:
Raised
Raised
Un tipo hereda todos los controladores de eventos proporcionados por su tipo base. Un tipo derivado no puede
alterar 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 tipos mediante métodos de extensión.
Los métodos de extensión son métodos System.Runtime.CompilerServices.ExtensionAttribute que tienen
aplicado el atributo. 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 permitir que se declaren en otros tipos de tipos. Siempre que los métodos sigan las
otras convenciones descritas aquí y el tipo contenedor no sea un tipo genérico abierto y no se pueda crear una
instancia de él, Visual Basic reconocerá los métodos de extensión.
Cuando se invoca un método de extensión, se pasa al primer parámetro la instancia en la que se invoca. 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:
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, las interfaces se pueden extender. 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 siguen implementando solo 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
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, el argumento de tipo se puede inferir:
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 extiende:
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
En lo que respecta a la accesibilidad, los métodos de extensión también se tratan como miembros del módulo
estándar en el que se declaran: no tienen acceso adicional a los miembros del tipo que se extienden más allá del
acceso que tienen en virtud de su contexto de declaración.
Los métodos de extensión solo están disponibles cuando el método del módulo estándar está en el ámbito. De
lo contrario, el tipo original no parecerá que se haya 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()
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 se enlazan los miembros, 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
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 hagan referencia a métodos de extensión. Por lo tanto, el código:
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 System.NullReferenceException produzca una 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 Nothing .
Nota. A un tipo de valor se le aplica la conversión boxing cuando se pasa como ByVal argumento a un
parámetro con tipo de interfaz. Esto implica que los efectos secundarios del método de extensión funcionarán en
una copia de la estructura en lugar de en el original. Aunque el lenguaje no impone restricciones en el primer
argumento de un método de extensión, se recomienda que los métodos de extensión no se utilicen para
extender los tipos de valor o que al extender los tipos de valor, el primer parámetro se pase ByRef para
asegurarse de que los efectos secundarios operan en el valor original.
Métodos Partial
Un método parcial es un método que especifica una firma pero no el cuerpo del método. El cuerpo del método
puede ser proporcionado por otra declaración de método con el mismo nombre y la misma signatura,
probablemente en otra declaración parcial del tipo. Por ejemplo:
a. VB:
ValidateControls()
End Sub
End Class
b. VB:
En este ejemplo, una declaración parcial de la clase MyForm declara un método parcial sin ValidateControls
implementación. El constructor de la declaración parcial llama al método parcial, aunque no haya ningún cuerpo
proporcionado en el archivo. La otra declaración parcial de MyForm entonces proporciona la implementación del
método.
Se puede llamar a los 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
Todas las expresiones que se pasan como argumentos a una llamada de método parcial que se omite también
se omiten y no se evalúan. (Nota. Esto significa que los métodos parciales son una manera muy eficaz de
proporcionar el comportamiento que se define en dos tipos parciales, ya que los métodos parciales no tienen
ningún costo si no se usan.)
La declaración del método parcial se debe declarar 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. Se combinan los atributos del método parcial y el método que proporciona su cuerpo, al igual que
los atributos de los parámetros de los métodos. Del mismo modo, se combina la lista de eventos que
administran los métodos. Por ejemplo:
Class C1
Event E1()
Event E2()
Constructores
Los constructores son métodos especiales que permiten el control sobre la inicialización. Se ejecutan después de
que el programa se inicia o cuando se crea 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 se pueden invocar mediante expresiones de creación de objetos o mediante el .NET
Framework; nunca se pueden invocar directamente.
Nota. Los constructores tienen la misma restricción en la posició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 instancias
Los constructores de instancias inicializan las instancias de un tipo y se ejecutan en la .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 instancias se pueden sobrecargar.
Todos los constructores de los tipos de referencia deben invocar a otro constructor. Si la invocación es explícita,
debe ser la primera instrucción del cuerpo del método de constructor. La instrucción puede invocar otro de los
constructores de instancia del tipo (por ejemplo, Me.New(...) o MyClass.New(...) ), o bien, si no es una
estructura, puede invocar 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 que Me no se considera que se construya 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 a Me , MyClass o 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 variables 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 inicializan mediante sus inicializadores de variable antes de que se ejecute cualquier instrucción que
tenga acceso a la instancia. Por ejemplo:
Class A
Protected x As Integer = 1
End Class
Class B
Inherits A
Private y As Integer = x
Cuando New B() se utiliza para crear una instancia de B , se genera el siguiente resultado:
x = 1, y = 1
Class Message
Dim sender As Object
Dim text As String
End Class
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 de diseñador generada 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 por sí mismo, si así lo decide).
Constructores compartidos
Los constructores compartidos inicializan las variables compartidas de un tipo; se ejecutan después de que el
programa empiece 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 ningún parámetro y no pueden 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 de las 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
variables se ejecutan en el orden textual en el que aparecen en la declaración de tipos.
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
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 cómodo para aplicar
comprobaciones en tiempo de ejecución en el parámetro de tipo que no se puede comprobar en tiempo de
compilación a través de restricciones. Por ejemplo, el tipo siguiente 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
Class A
Shared Sub New()
Console.WriteLine("Init A")
End Sub
Class B
Shared Sub New()
Console.WriteLine("Init B")
End Sub
Init A
A.F
Init B
B.F
or
Init B
Init A
A.F
B.F
Por el contrario, en el ejemplo siguiente se generan resultados predecibles. Tenga en cuenta que el Shared
constructor de la clase A nunca se ejecuta, aunque la clase B se deriva 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
La salida es la siguiente:
Init B
B.G
También es posible crear dependencias circulares que permitan Shared observar las variables con
inicializadores 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
X = 1, Y = 2
Para ejecutar el Main método, la primera carga de la clase del sistema B . El Shared constructor de la clase B
continúa para calcular el valor inicial de Y , que hace que la clase A se cargue de forma recursiva porque A.X
se hace referencia al valor de. El Shared constructor de la clase A a su vez continúa para calcular el valor inicial
de X y, al hacerlo, recupera el valor predeterminado de Y , que es cero. A.X por tanto, se inicializa en 1 . El
proceso de carga A se completa y vuelve al cálculo del valor inicial de Y , cuyo resultado se convierte en 2 .
Main En su lugar, si el método se encontraba en la clase A , el ejemplo habría generado el siguiente resultado:
X = 2, Y = 1
Evite las referencias circulares en Shared inicializadores de variables, ya que generalmente no es posible
determinar el orden en el que se cargan las clases que contienen dichas referencias.
Events
Los eventos se usan para notificar a código de una repetición determinada. Una declaración de evento consta de
un identificador, ya sea un tipo de delegado o una lista de parámetros, y una Implements cláusula opcional.
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, el tipo de delegado no puede tener un tipo de valor devuelto. Si se
especifica una lista de parámetros, no puede contener Optional ParamArray parámetros o. El dominio de
accesibilidad de los tipos de parámetro y/o el tipo de delegado debe ser el mismo que 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 otros miembros. Dado un evento denominado X , se agregan los siguientes miembros al
espacio de declaración:
Si el formato de la declaración es una declaración de método, se introduce una clase de delegado anidada
denominada XEventHandler . La clase de delegado anidada coincide con la declaración del 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.
Una Private variable de instancia con el tipo de delegado, denominada XEvent .
Dos métodos denominados add_X y remove_X que no se pueden invocar, reemplazar 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 add_X remove_X omitirán las declaraciones y implícitas para los fines del enlace de
nombre. No es posible reemplazar 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
Class Raiser
Public Delegate Sub ConstructedEventHandler(i As Integer)
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 delegados ocultos, aunque los tres eventos tienen la misma lista de parámetros:
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 del objeto Button :
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
RemoveHandler(value as EventHandler)
Handlers = CType([Delegate].Remove(Handlers, value), _
EventHandler)
End RemoveHandler
La AddHandler RemoveHandler declaración y toman un ByVal parámetro, que debe ser del tipo delegado del
evento. Cuando AddHandler RemoveHandler se ejecuta una instrucción o (o una Handles cláusula controla
automáticamente un 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 RaiseEvent se ejecute una instrucción.
Todas las declaraciones deben proporcionarse y se consideran subrutinas.
Tenga AddHandler en cuenta RemoveHandler que RaiseEvent las declaraciones y tienen la misma restricción en
la posició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
personalizado declara implícitamente otros miembros. Dado un evento denominado X , se agregan los
siguientes miembros al espacio de declaración:
Un método denominado add_X , que corresponde a la AddHandler declaración.
Un método denominado remove_X , que corresponde a la RemoveHandler declaración.
Un método denominado fire_X , que corresponde a la RaiseEvent declaración.
Si un tipo intenta declarar un nombre que coincide con uno de los nombres anteriores, se producirá un error en
tiempo de compilación y se omitirán todas las declaraciones implícitas para los fines del enlace de nombre. No
es posible reemplazar 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 WinRT
A partir de Microsoft Visual Basic 11,0, los eventos declarados en un archivo compilado con /target:winmdobj ,
o declarados en una interfaz en un archivo de este tipo y, a continuación, implementarse en otro lugar, se tratan
de un modo ligeramente diferente.
Las herramientas externas que se usan para compilar winmd normalmente solo permiten determinados
tipos de delegado como System.EventHandler(Of T) o System.TypedEventHandle(Of T, U) , y no permitirán
a otros.
El XEvent campo tiene el tipo,
System.Runtime.InteropServices.WindowsRuntime.EventRegistrationTokenTable(Of T) donde T es el tipo de
delegado.
El descriptor de acceso AddHandler devuelve
System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken y el descriptor de acceso
RemoveHandler toma un solo parámetro del mismo tipo.
Este es un ejemplo de este tipo de evento personalizado.
Imports System.Runtime.InteropServices.WindowsRuntime
RemoveHandler(token As EventRegistrationToken)
EventRegistrationTokenTable(Of EventHandler(Of Integer)).
GetOrCreateEventRegistrationTokenTable(XEvent).
RemoveEventHandler(token)
End RemoveHandler
Constantes
Una constante es un valor constante que es 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 y no hay Object
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 de constante que declara varias constantes es equivalente a varias declaraciones de constantes
únicas. En el siguiente ejemplo 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
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 la constante o de
un tipo que se pueda convertir implícitamente al tipo de la constante. La expresión constante no puede ser
circular; es decir, una constante no se puede definir en términos de sí mismo.
El compilador evalúa automáticamente las declaraciones de constantes en el orden adecuado. En el ejemplo
siguiente, el compilador evalúa primero Y , después Z y, por último 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 de constante o cuando el valor no se puede calcular en tiempo de compilación mediante una
expresión constante, en su lugar se puede utilizar una variable de solo lectura.
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
;
Se Dim debe especificar el modificador si no se especifica ningún modificador, pero se puede omitir en caso
contrario. Una única declaración de variable puede incluir varios declaradores variables. cada declarador de
variable introduce una nueva instancia o un miembro compartido.
Si se especifica un inicializador, solo se puede declarar una variable compartida o una instancia mediante el
declarador de variable:
Class Test
Dim a, b, c, d As Integer = 10 ' Invalid: multiple initialization
End Class
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 creen. Una variable compartida se produce cuando se inicia la ejecución de un programa y deja de existir
cuando finaliza el programa.
Una variable compartida solo se comparte entre las instancias de un tipo genérico cerrado determinado. Por
ejemplo, el programa:
Class C(Of V)
Shared InstanceCount As Integer = 0
Class Application
Shared Sub Main()
Dim x1 As New C(Of Integer)()
Console.WriteLine(C(Of Integer).Count)
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. Una variable de instancia
de un tipo de referencia se encuentra en existencia cuando se crea una nueva instancia de ese tipo y deja de
existir cuando no hay ninguna referencia a esa instancia y Finalize se ha ejecutado el método. Una variable de
instancia de un tipo de valor tiene exactamente la misma duración que la variable a la que pertenece. En otras
palabras, cuando una variable de un tipo de valor entra en existencia o deja de existir, también lo 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 utiliza 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 un tipo o tipo de elemento de matriz de una instancia o una variable compartida
debe ser el mismo que el dominio de accesibilidad de la instancia o la variable compartida.
En el ejemplo siguiente se muestra una Color clase que tiene variables de instancia internas denominadas
redPart , greenPart y bluePart :
Class Color
Friend redPart As Short
Friend bluePart As Short
Friend greenPart As Short
Read-Only variables
Cuando una declaración de variable compartida o de instancia incluye un ReadOnly modificador, las
asignaciones a las variables introducidas por la declaración solo se pueden producir 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).
En el caso de 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 incompleta o a
través de Me o MyClass .
En el caso de una variable compartida, en el constructor compartido de la clase que contiene la
declaración de variable compartida.
Una variable compartida de solo lectura es útil cuando se desea un nombre simbólico para un valor constante,
pero cuando el tipo del valor no está permitido en una declaración de constante o cuando el 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, en la que se declaran variables compartidas de
color ReadOnly para evitar que otros programas las cambien:
Class Color
Friend redPart As Short
Friend bluePart As Short
Friend greenPart As Short
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. Considere la siguiente aplicación, que consta de dos
programas independientes.
archivo1. VB:
Namespace Program1
Public Class Utils
Public Shared ReadOnly X As Integer = 1
End Class
End Namespace
archivo2. 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 denotan dos programas que se compilan por separado. Dado
que la variable Program1.Utils.X se declara como Shared ReadOnly , el resultado del valor de 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 el valor de X se cambia y Program1 se vuelve a compilar, la Console.WriteLine
instrucción generará el nuevo valor aunque Program2 no se vuelva a compilar. Sin embargo, si X hubiera sido
una constante, el valor de X se habría obtenido en el momento en Program2 que se compiló y habría
permanecido no afectado por los cambios en Program1 hasta que Program2 se volvió a compilar.
Variables WithEvents
Un tipo puede declarar que controla algún conjunto de eventos generados por una de sus variables
compartidas o de instancia declarando la instancia o la variable compartida que genera los eventos con el
WithEvents modificador. Por ejemplo:
Class Raiser
Public Event E1()
Module Test
Private WithEvents x As Raiser
En este ejemplo, el método E1Handler controla el evento E1 generado por la instancia del tipo Raiser
almacenado en la variable de instancia x .
El WithEvents modificador hace que se cambie el nombre de la variable con 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 una propiedad F implícitamente. Si hay un
conflicto entre el nuevo nombre de la variable y otra declaración, se generará un error en tiempo de
compilación. Los atributos aplicados a la variable se transfieren a la variable con el nombre cambiado.
La propiedad implícita creada por una WithEvents declaración se encarga de enlazar y desenlazar 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 (desenlazando 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 estándar Test :
Module Test
Private _x As Raiser
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 tiene el tipo de
estructura. Además, no WithEvents se puede especificar en una estructura y no se WithEvents pueden
ReadOnly combinar.
Inicializadores de variables
Las declaraciones de variables compartidas y de instancia en clases y declaraciones de variables de instancia
(pero no declaraciones de variables compartidas) en estructuras pueden incluir inicializadores variables. En
Shared el caso de las variables, los inicializadores de variables corresponden a las instrucciones de asignación
que se ejecutan después de que se inicie el programa, pero antes de Shared que se haga referencia a la
variable. En el caso de las variables de instancia, los inicializadores de variables corresponden a las instrucciones
de asignación que se ejecutan cuando se crea una instancia de la clase. Las estructuras no pueden tener
inicializadores de variable 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
Una asignación se x produce cuando se carga la clase y se asigna a i y s se produce cuando se crea una
nueva instancia de la clase.
Resulta útil pensar en los inicializadores de variables como instrucciones de asignación que se insertan
automáticamente en el bloque del constructor del tipo. El ejemplo siguiente contiene varios inicializadores de
variable de instancia.
Class A
Private x As Integer = 1
Private y As Integer = -1
Private count As Integer
Class B
Inherits A
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
Class B
Inherits A
Todas las variables se inicializan en el valor predeterminado de su tipo antes de que se ejecute cualquier
inicializador de variable. 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 con su valor predeterminado cuando se carga la clase y i se
inicializa automáticamente con su valor predeterminado cuando se crea una instancia de la clase, el código
anterior genera el siguiente resultado:
b = False, i = 0
Cada inicializador de variable debe producir un valor del tipo de la variable o de un tipo que se pueda convertir
implícitamente al 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 tipo de inicializador es de valor dudoso.
Hay tres formas de inicializadores de variables: inicializadores regulares, inicializadores de tamaño de matriz e
inicializadores de objeto. Las dos primeras formas aparecen después de un signo igual que sigue el nombre del
tipo, las dos últimas forman parte de la propia declaración. Solo se puede usar una forma de inicializador en una
declaración determinada.
Inicializadores regulares
Un inicializador normal es una expresión que se convertirá implícitamente al 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
x = 10, y = 20
Si una declaración de variable tiene un inicializador normal, 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
Inicializadores de objeto
Un inicializador de objeto se especifica mediante una expresión de creación de objeto 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 objeto a la variable. Así:
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
Los paréntesis de un inicializador de objeto siempre se interpretan como la lista de argumentos para el
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 que se indican mediante expresiones.
ArraySizeInitializationModifier
: OpenParenthesis BoundList CloseParenthesis ArrayTypeModifiers?
;
BoundList
: Bound ( Comma Bound )*
;
Bound
: Expression
| '0' 'To' Expression
;
Las expresiones de límite superior se deben clasificar como valores y deben poder convertirse implícitamente a
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. Así:
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 en sí mismo 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 local x 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 utilizar 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 referencias a las clases que derivan de la clase System.MarshalByRefObject se serializan a través de los
límites de contexto mediante proxies (es decir, por referencia) en lugar de copiar (es decir, por valor). Esto
significa que una instancia de este tipo de clase puede no ser una instancia verdadera, sino que puede ser
simplemente un código auxiliar que calcula las referencias de los accesos a variables y las llamadas 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 las variables definidas
en estas clases. Esto significa que las variables escritas como clases derivadas de System.MarshalByRefObject no
se pueden pasar a parámetros de referencia, y no se puede tener acceso a los métodos y variables de las
variables escritas como tipos de valor. En su lugar, Visual Basic trata las variables definidas en esas clases como
si fueran propiedades (ya que las restricciones son las mismas en las propiedades).
Hay una excepción a esta regla: un miembro implícita o explícitamente calificado con Me está exento de las
restricciones anteriores, porque Me siempre se garantiza que sea un objeto real, no un proxy.
Propiedades
Las propiedades son una extensión natural de las variables; ambos son miembros con nombre con tipos
asociados y la sintaxis para tener acceso a variables y propiedades es la misma. Sin embargo, a diferencia de las
variables, las propiedades no denotan las ubicaciones de almacenamiento. En su lugar, las propiedades tienen
descriptores de acceso, que especifican las instrucciones que se ejecutarán para leer o escribir sus valores.
Las propiedades se definen con declaraciones de propiedad. La primera parte de una declaración de propiedad
se parece a una declaración de campo. La segunda parte incluye un Get descriptor de acceso y/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
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
;
...
End Class
Aquí, el Set descriptor de acceso se invoca mediante la asignación de un valor a la propiedad, y el Get
descriptor de acceso se invoca mediante la referencia a la propiedad en una expresión.
Si no se especifica ningún tipo para una propiedad y se utiliza 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 los métodos, una propiedad puede declararse 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 producirá 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 los 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 tener el nombre 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
descriptor de Get acceso y un Set descriptor de acceso. Se dice que la propiedad es una propiedad de
lectura y escritura.
Si la propiedad especifica el ReadOnly modificador, la propiedad debe tener un Get descriptor de acceso
y no puede tener un Set descriptor de acceso. Se dice que la propiedad es de solo lectura. Es un error en
tiempo de compilación que una propiedad de solo lectura sea el destino de una asignación.
Si la propiedad especifica el WriteOnly modificador, la propiedad debe tener un Set descriptor de
acceso y no puede tener un Get descriptor de acceso. Se dice que la propiedad es de solo escritura. Es
un error en tiempo de compilación hacer referencia a una propiedad de solo escritura en una expresión
excepto como el destino de una asignación o como un argumento de un método.
Los Get Set descriptores de acceso y 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 única
propiedad de lectura y escritura. En su lugar, declara dos propiedades con el mismo nombre, una de solo lectura
y una solo escritura:
Class A
Private nameValue As String
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 Get descriptores de acceso y de una propiedad Set es la
misma que la accesibilidad de la propia propiedad. Sin embargo, Get los Set descriptores de acceso y
también pueden especificar la accesibilidad independientemente 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, como se indica a continuación:
Private es más restrictivo que Public , Protected Friend , Protected o Friend .
Friend es más restrictiva que Protected Friend o Public .
Protected es más restrictiva que Protected Friend o Public .
Protected Friend es más restrictiva que Public .
Cuando se puede tener acceso a uno de los descriptores de acceso de una propiedad pero el otro no 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
Module Test
Sub Main()
Dim a As A = New A()
Cuando un tipo derivado prevalece sobre una propiedad, la propiedad derivada oculta la propiedad sombreada
con respecto a la lectura y la escritura. En el ejemplo siguiente, la P propiedad de B oculta la P propiedad en
A con respecto a la lectura y la escritura:
Class A
Public WriteOnly Property P() As Integer
Set (Value As Integer)
End Set
End Property
End Class
Class B
Inherits A
Module Test
Sub Main()
Dim x As B = New B
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
descriptor Set de acceso y un Get descriptor de acceso.
A excepción de las diferencias en la sintaxis de declaración e invocación, Overridable las propiedades,,,
NotOverridable y se Overrides MustOverride MustInherit comportan exactamente igual que Overridable
NotOverridable Overrides MustOverride MustInherit los métodos,,, y. Cuando se invalida una propiedad, la
propiedad de reemplazo debe ser del mismo tipo (de lectura y escritura, de solo lectura, de solo escritura). Una
Overridable propiedad no puede contener un Private descriptor de acceso.
En el ejemplo siguiente, es una propiedad X Overridable 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
Class B
Inherits A
Private _z As Integer
Aquí, las declaraciones de las propiedades X , Y y 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 acceder a las propiedades heredadas. La declaración de Z la
propiedad invalida la MustOverride propiedad; por lo tanto, no hay miembros pendientes MustOverride en la
clase B y 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 el que se hace
referencia a él por primera vez. Por ejemplo:
Imports System.IO
La ConsoleStreams clase contiene tres propiedades, In , Out y Error , que representan los dispositivos de
entrada, salida y error estándar, 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 por primera vez Out a la propiedad, como en ConsoleStreams.Out.WriteLine("hello, world") ,
TextWriter se inicializa el subyacente para el dispositivo de salida. Pero si la aplicación no hace referencia a las
In Error propiedades y, no se crea ningún objeto para esos dispositivos.
El uso de paréntesis puede producir situaciones ambiguas (como, por ejemplo, F(1) 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:
Cuando el flujo de control sale del Get cuerpo del descriptor de acceso, el valor de la variable local se devuelve
a la expresión de invocación. Dado que la invocación Get de un descriptor de acceso es conceptualmente
equivalente a leer el valor de una variable, se considera un estilo de programación incorrecto para que los
descriptores de Get acceso tengan efectos secundarios observables, como se muestra en el ejemplo siguiente:
Class Counter
Private Value As Integer
El valor de la NextValue propiedad depende del número de veces que se ha accedido previamente a la
propiedad. Por lo tanto, el acceso a la propiedad produce un efecto secundario observable y, en su lugar, la
propiedad debe implementarse como un método.
La Convención de "no hay efectos secundarios" para los Get descriptores de acceso no significa que los Get
descriptores de acceso siempre se deben escribir simplemente para devolver los valores almacenados en
variables. En realidad, Get los descriptores de acceso suelen calcular el valor de una propiedad mediante el
acceso a varias variables o la invocación de métodos. Sin embargo, un Get descriptor de acceso diseñado
correctamente no realiza ninguna acción que produzca cambios observables en el estado del objeto.
Nota. Get los descriptores de acceso tienen la misma restricción en la posició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
;
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 una propiedad Default , todas las propiedades sobrecargadas en ese nombre en la jerarquía
de herencia se convierten en la propiedad predeterminada, tanto si se han declarado Default como si no. Al
declarar una propiedad Default en una clase derivada cuando la clase base declara una propiedad
predeterminada por otro nombre, no se requiere ningún otro modificador como Shadows o Overrides . Esto se
debe a que la propiedad predeterminada no tiene ninguna identidad o 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
Module Test
Sub Main()
Dim x As MoreDerived = New MoreDerived()
Dim y As Integer
Dim z As Derived = x
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. Las propiedades predeterminadas no pueden ser 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 cualquier descriptor 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 implementada automáticamente x , incluso una invalidación de otra propiedad, introduce una
variable local privada _x con el mismo tipo que la propiedad. Si hay un conflicto entre el nombre de la variable
local y otra declaración, se generará un error en tiempo de compilación. El descriptor de acceso de la propiedad
implementada automáticamente Get devuelve el valor de la propiedad local y el Set descriptor de acceso de
la propiedad que establece el valor del local. Por ejemplo, la declaración:
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 variable, una propiedad implementada automáticamente puede incluir un
inicializador. Por ejemplo:
Nota. Cuando se inicializa una propiedad implementada automáticamente, se inicializa a través de la propiedad,
no del campo subyacente. De este modo, las propiedades de invalidación pueden 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 los límites de la matriz explícitamente. 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 de iterador
Una propiedad de iterador es una propiedad con el Iterator modificador. Se utiliza por la misma razón que se
usa un método de iterador ( métodos de iteradorde sección), como una manera cómoda de generar una
secuencia, que puede ser utilizada por la For Each instrucción. El Get descriptor de acceso de una propiedad
de iterador se interpreta de la misma manera que un método iterador.
Una propiedad de iterador debe tener un Get descriptor de acceso explícito y su tipo debe ser IEnumerator ,o
IEnumerable , o IEnumerator(Of T) o IEnumerable(Of T) para algunos T .
Class Family
Property Daughters As New List(Of String) From {"Beth", "Diane"}
Property Sons As New List(Of String) From {"Abe", "Carl"}
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. La definición de 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
Class Base
Public Shared Widening Operator CType(b As Base) As Integer
...
End Operator
Las declaraciones de operador no agregan explícitamente nombres al espacio de declaración del tipo
contenedor; sin embargo, declaran implícitamente un método correspondiente a partir de los caracteres "op_".
En las secciones siguientes se enumeran los nombres de método correspondientes con cada operador.
Hay tres clases de operadores que se pueden definir: operadores unarios, operadores binarios y operadores de
conversión. Todas las declaraciones de operador comparten ciertas restricciones:
Las declaraciones de operador siempre deben ser Public y Shared . El Public modificador se puede
omitir en contextos en los que se asumirá el modificador.
Los parámetros de un operador no se pueden declarar ByRef , Optional o ParamArray .
El tipo de al menos uno de los operandos o el valor devuelto debe ser el tipo que contiene el operador.
No hay ninguna variable de devolución de función definida para los operadores. Por lo tanto, la Return
instrucción debe usarse para devolver valores de un cuerpo de operador.
La única excepción a estas restricciones se aplica a los tipos de valor que aceptan valores NULL. Dado que los
tipos de valor que aceptan valores NULL no tienen una definición de tipo real, un tipo de valor puede declarar
operadores definidos por el usuario para la versión que acepta valores NULL del tipo. Al determinar si un tipo
puede declarar un operador definido por el usuario determinado, los ? Modificadores se quitan primero de
todos los tipos implicados en la declaración con el fin de comprobar la validez. Esta relajación no se aplica al tipo
de valor devuelto de los IsTrue IsFalse operadores y; todavía deben devolver Boolean , no Boolean? .
La prioridad y asociatividad de un operador no se pueden modificar mediante una declaración de operador.
Nota. Los operadores tienen la misma restricción en la posición de línea que tienen las subrutinas. La
instrucción inicial, la instrucción end y el bloque deben aparecer al principio de una línea lógica.
Operadores unarios
Se pueden sobrecargar los siguientes operadores unarios:
Operador unario más + (método correspondiente: op_UnaryPlus )
Operador unario menos - (método correspondiente: op_UnaryNegation )
Operador lógico Not (método correspondiente: op_OnesComplement )
Los IsTrue IsFalse operadores y (métodos correspondientes: op_True , op_False )
Todos los operadores unarios sobrecargados deben tomar un parámetro único del tipo contenedor y pueden
devolver cualquier tipo, excepto IsTrue y IsFalse , que debe 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
...
Si un tipo sobrecarga una de IsTrue o IsFalse , debe sobrecargar también el otro. Si solo una está
sobrecargada, se producirá un error en tiempo de compilación.
Nota. IsTrue y IsFalse no son palabras reservadas.
Operadores binarios
Se pueden sobrecargar los siguientes operadores binarios:
Operadores de suma + , resta - , multiplicación, * división / , división integral \ , módulo Mod y
exponenciación ^ (método correspondiente: op_Addition , op_Subtraction , op_Multiply , op_Division
, op_IntegerDivision , op_Modulus , op_Exponent )
Los operadores relacionales,,, = <> < > , <= , >= (métodos correspondientes: op_Equality ,
op_Inequality , op_LessThan , op_GreaterThan , op_LessThanOrEqual , op_GreaterThanOrEqual ). Nota.
Aunque se puede sobrecargar el operador de igualdad, el operador de asignación (que solo se usa en
instrucciones de asignación) no se puede sobrecargar.
Like Operador (método correspondiente: op_Like )
El operador de concatenación & (método correspondiente: op_Concatenate )
Los operadores lógicos And Or and Xor (métodos correspondientes: op_BitwiseAnd , op_BitwiseOr ,
op_ExclusiveOr )
Si se declara uno de los pares, el otro también debe declararse con los tipos de valor devuelto y parámetros
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 sobrecargar los operadores de división y de
división integral, aunque no se trata de un error. (Nota. En general, los dos tipos de división deben ser
completamente distintos: un tipo que admita la división es un entero (en cuyo caso debe admitirse \ ) o no (en
cuyo caso debe admitir / ). Consideramos que se ha hecho un error para definir ambos operadores, pero
como sus lenguajes generalmente no distinguen entre dos tipos de división, la forma en que Visual Basic hace,
nos sentimos más seguro permitir la práctica pero desaconsejarla.
Los operadores de asignación compuesta no se pueden sobrecargar directamente. En su lugar, cuando el
operador binario correspondiente está sobrecargado, el operador de asignación compuesta usará el operador
sobrecargado. Por ejemplo:
Structure Complex
...
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, en un tipo de destino, indicado por el tipo de
valor devuelto del operador de conversión. Las conversiones se deben clasificar como de ampliación o de
restricción. Una declaración de operador de conversión que incluye la Widening palabra clave presenta una
conversión de ampliación definida por el usuario (método correspondiente: op_Implicit ). Una declaración de
operador de conversión que incluye la Narrowing palabra clave presenta una conversión de restricción definida
por el usuario (método correspondiente: op_Explicit ).
En general, las conversiones de ampliación definidas por el usuario deben diseñarse para que nunca se
produzcan excepciones y nunca se pierda 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 los bits de orden superior), esa conversión debe definirse como una conversión de
restricción. En el ejemplo:
Structure Digit
Dim value As Byte
la conversión de Digit a Byte es una conversión de ampliación porque nunca produce excepciones o pierde
información, pero la conversión de Byte a Digit es una conversión de restricción, ya que Digit 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 de destino.
Un operador de conversión definido por el usuario debe convertir a o desde el tipo contenedor; por ejemplo, es
posible que una clase C defina una conversión de C a Integer y de Integer a C , 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 volver a definir 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 de destino son los mismos.
Tanto el tipo de origen como el de destino no son del 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 por 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 definida por el usuario determinada, los ? Modificadores se quitan primero de
todos los tipos implicados en la declaración con el fin de comprobar la 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
No obstante, 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 el conjunto de operadores que Visual Basic admite puede no coincidir exactamente con el conjunto de
operadores que otros lenguajes de la .NET Framework, algunos operadores están asignados de forma especial a
otros operadores cuando se definen o se usan. Concretamente:
Al definir un operador de división integral, se definirá automáticamente un operador de división normal
(solo utilizable desde otros lenguajes) que llamará al operador de división integral.
Al sobrecargar Not los And operadores, y, Or solo se sobrecargará el operador bit a bit de la
perspectiva de otros lenguajes que distinga entre operadores lógicos y bit a bit.
Una clase que sobrecarga solo los operadores lógicos en un lenguaje que distingue entre los operadores
lógicos y bit a bit (es decir, los lenguajes que usan op_LogicalNot , op_LogicalAnd y op_LogicalOr para
Not , And y Or , respectivamente) tendrán sus operadores lógicos asignados en los operadores lógicos
Visual Basic. Si se sobrecargan los operadores lógicos y bit a bit, solo se usarán los operadores bit a bit.
Al sobrecargar << los >> operadores y, solo se sobrecargan los operadores firmados desde la
perspectiva de otros lenguajes que distinguen los operadores de desplazamiento con signo y sin signo.
Una clase que sobrecarga solo un operador de desplazamiento sin signo tendrá el operador de
desplazamiento sin signo asignado al operador de desplazamiento de Visual Basic correspondiente. Si
hay un operador de desplazamiento sin signo y con signo sobrecargado, solo se utilizará el operador de
desplazamiento con signo.
Instrucciones
15/11/2021 • 129 minutes to read
Statement
: LabelDeclarationStatement
| LocalDeclarationStatement
| WithStatement
| SyncLockStatement
| EventStatement
| AssignmentStatement
| InvocationStatement
| ConditionalStatement
| LoopStatement
| ErrorHandlingStatement
| BranchStatement
| ArrayHandlingStatement
| UsingStatement
| AwaitStatement
| YieldStatement
;
Nota. El compilador de Microsoft Visual Basic solo permite instrucciones que comienzan por una palabra clave
o un identificador. Por lo tanto, por ejemplo, se permite la instrucción de invocación " Call (Console).WriteLine
", pero la instrucción de invocación " (Console).WriteLine " no es.
Flujo de control
El flujo de control es la secuencia en la que se ejecutan las instrucciones y expresiones. El orden de ejecución
depende de la instrucción o expresión determinada.
Por ejemplo, al evaluar un operador de suma ( operador de sumade sección), primero se evalúa el operando
izquierdo, después el operando derecho y, por último, el operador. Los bloques se ejecutan ( bloques de sección
y etiquetas) al ejecutar primero su primer subestado y, después, continuar uno a uno a través de las
instrucciones del bloque.
Implícito en este orden es el concepto de punto de control, que es la siguiente operación que se va a ejecutar.
Cuando se invoca un método (o "llamado"), se crea una instancia del método. Una instancia de método consta
de su propia copia de los parámetros y las variables locales del método, y su propio punto de control.
Métodos regulares
A continuación se muestra un ejemplo de un método normal.
Dim en = Test()
For Each x In en ' prints "hello" before the first x
Console.WriteLine(x) ' prints "1" and then "2"
Next
Nota. Los métodos Async no se ejecutan en un subproceso en segundo plano. En su lugar, permiten que un
método se suspenda a través del Await operador y se programe para que se reanude en respuesta a algún
evento.
Cuando se invoca un método asincrónico
1. En primer lugar, se crea una instancia del método asincrónico específico para esa invocación. Esta instancia
incluye una copia de todos los parámetros y las variables locales del método.
2. Después, todos sus parámetros se inicializan en los valores proporcionados y en todas sus variables locales
para los valores predeterminados de sus tipos.
3. En el caso de un método asincrónico con el tipo de valor devuelto Task(Of T) para algunos T , también se
inicializa una variable local implícita denominada variable de devolución de tarea, cuyo tipo es T y cuyo
valor inicial es el valor predeterminado de T .
4. Si el método asincrónico es un Function tipo de valor devuelto Task o Task(Of T) para algunos T , se
crea implícitamente un objeto de ese tipo, asociado a la invocación actual. Esto se denomina un objeto
asincrónico y representa el trabajo futuro que se realizará ejecutando la instancia del método asincrónico.
Cuando el control se reanuda en el autor de la llamada de esta instancia de método asincrónico, el autor de
la llamada recibirá este objeto asincrónico como resultado de la invocación.
5. A continuación, el punto de control de la instancia se establece en la primera instrucción del cuerpo del
método asincrónico y comienza inmediatamente a ejecutar el cuerpo del método desde allí (bloques de
sección y etiquetas).
Delegado de reanudación y llamador actual
Tal como se detalla en la sección Await (operador), la ejecución de una Await expresión tiene la capacidad de
suspender el punto de control de la instancia de método que sale del flujo de control para ir a otro lugar. El flujo
de control se puede reanudar posteriormente en el punto de control de la instancia a través de la invocación de
un delegado de reanudación. Tenga en cuenta que esta suspensión se realiza sin salir del método asincrónico y
no hace que los controladores finally se ejecuten. Todavía se hace referencia a la instancia de método por el
delegado de reanudación y el Task Task(Of T) resultado o (si existe) y no se recolectará como elemento no
utilizado, siempre y cuando exista una referencia dinámica a un delegado o resultado.
Resulta útil imaginar la instrucción Dim x = Await WorkAsync() aproximadamente como una abreviatura
sintáctica para lo siguiente:
En el siguiente caso, el llamador actual de la instancia de método se define como el llamador original, o bien
como el llamador del delegado de reanudación, lo que sea más reciente.
Cuando el flujo de control sale del cuerpo del método asincrónico, hasta alcanzar el End Sub o End Function
que marca su final, o a través de una Return instrucción or explícita o Exit a través de una excepción no
controlada, el punto de control de la instancia se establece en el final del método. Después, el comportamiento
depende del tipo de valor devuelto del método asincrónico.
En el caso de una Async Function con el tipo de valor devuelto Task :
1. Si el flujo de control sale a través de una excepción no controlada, el estado del objeto asincrónico
se establece en TaskStatus.Faulted y su Exception.InnerException propiedad se establece en la
excepción (excepto en el caso de ciertas excepciones definidas por la implementación como
OperationCanceledException cambiar a TaskStatus.Canceled ). El flujo de control se reanuda en el
autor de la llamada actual.
2. De lo contrario, el estado del objeto asincrónico se establece en TaskStatus.Completed . El flujo de
control se reanuda en el autor de la llamada actual.
(Nota. El punto de tarea completo y lo que hace que los métodos asincrónicos resulten
interesantes, es que cuando se completa una tarea, los métodos que estaban esperando la
ejecución de los delegados de reanudación se ejecutarán, es decir, se desbloquearán.
En el caso de un Async Function tipo de valor devuelto Task(Of T) para algunos T : el comportamiento
es como el anterior, salvo que en los casos que no son de excepción, la propiedad del objeto Async
Result también se establece en el valor final de la variable de devolución de tarea.
Bloques y etiquetas
Un grupo de instrucciones ejecutables se denomina bloque de instrucciones. La ejecución de un bloque de
instrucciones comienza con la primera instrucción del bloque. Una vez que se ha ejecutado una instrucción, se
ejecuta la siguiente instrucción en orden léxico, a menos que una instrucción transfiera la ejecución en otro
lugar o se produzca una excepción.
Dentro de un bloque de instrucciones, la división de instrucciones en líneas lógicas no es significativa con la
excepción de las instrucciones de declaración de etiqueta. Una etiqueta es un identificador que identifica una
posición concreta dentro del bloque de instrucciones que se puede usar como destino de una instrucción de
bifurcación como GoTo .
Block
: Statements*
;
LabelDeclarationStatement
: LabelName ':'
;
LabelName
: Identifier
| IntLiteral
;
Statements
: Statement? ( ':' Statement? )*
;
Las instrucciones de declaración de etiqueta deben aparecer al principio de una línea lógica y las etiquetas
pueden ser un identificador o un literal entero. Dado que las instrucciones de declaración de etiquetas y las
instrucciones de invocación pueden estar compuestas de un único identificador, un único identificador al
principio de una línea local siempre se considera una instrucción de declaración de etiqueta. Las instrucciones
de declaración de etiqueta siempre deben ir seguidas de un signo de dos puntos, incluso si no aparecen
instrucciones en la misma línea lógica.
Las etiquetas tienen su propio espacio de declaración y no interfieren con otros identificadores. El ejemplo
siguiente es válido y utiliza la variable de nombre x como un parámetro y como una etiqueta.
Class A
Private i As Integer = 0
Sub F()
i = 1
Dim i As Integer ' Error, use precedes declaration.
i = 2
End Sub
Sub G()
Dim a As Integer = 1
Dim b As Integer = a ' This is valid.
End Sub
End Class
Sub G()
If True Then
Dim i As Integer = 0
End If
Dim i As Integer = 1
End Sub
Sub H()
If True Then
Dim i As Integer = 0
End If
If True Then
Dim i As Integer = 1
End If
End Sub
Sub I()
For i As Integer = 0 To 9
H()
Next i
For i As Integer = 0 To 9
H()
Next i
End Sub
End Class
Cuando el método es una función, se declara implícitamente una variable local especial en el espacio de
declaración del cuerpo del método con el mismo nombre que el método que representa el valor devuelto de la
función. La variable local tiene una semántica especial de resolución de nombres 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:
El uso de paréntesis puede producir situaciones ambiguas (como F(1) , donde F es una función cuyo tipo de
valor devuelto es una matriz unidimensional); en todas las situaciones ambiguas, el nombre se resuelve como la
función en lugar de la variable local. Por ejemplo:
Function F(i As Integer) As Integer()
If i = 0 Then
F = new Integer(2) { 1, 2, 3 }
Else
F = F(i - 1) ' Recursive call, not an index.
End If
End Function
Cuando el flujo de control sale del cuerpo del método, el valor de la variable local se devuelve a la expresión de
invocación. Si el método es una subrutina, no hay ninguna variable local implícita y el control simplemente
vuelve a la expresión de invocación.
LocalDeclarationStatement
: LocalModifier VariableDeclarators StatementTerminator
;
LocalModifier
: 'Static' | 'Dim' | 'Const'
;
Las variables estáticas son variables locales que conservan su valor en las invocaciones del método. Las
variables estáticas declaradas en métodos no compartidos son por instancia: cada instancia del tipo que
contiene el método tiene su propia copia de la variable estática. Las variables estáticas declaradas en Shared
métodos son por tipo; solo hay una copia de la variable estática para todas las instancias. Aunque las variables
locales se inicializan en el valor predeterminado de su tipo en cada entrada del método, las variables estáticas
solo se inicializan en el valor predeterminado de su tipo cuando se inicializa el tipo o la instancia de tipo. Las
variables estáticas no se pueden declarar en estructuras o métodos genéricos.
Las variables locales, las constantes locales y las variables estáticas siempre tienen accesibilidad pública y no
pueden especificar modificadores de accesibilidad. Si no se especifica ningún tipo en una instrucción de
declaración local, los siguientes pasos determinan el tipo de la declaración local:
1. Si la declaración tiene un carácter de tipo, el tipo del carácter de tipo es el tipo de la declaración local.
2. Si la declaración local es una constante local, o si la declaración local es una variable local con un
inicializador y se usa la inferencia de tipo de variable local, el tipo de la declaración local se deduce del
tipo del inicializador. Si el inicializador hace referencia a la declaración local, se produce un error en
tiempo de compilación. (Las constantes locales deben tener inicializadores).
3. Si no se utiliza la semántica estricta, el tipo de la instrucción de declaración local es implícitamente
Object .
Option Infer On
Module Test
Sub Main()
' Error: initializer is not an array type
Dim x() = 1
Si no se especifica ningún tipo en una instrucción de declaración local que tenga un modificador de tipo que
acepta valores NULL, el tipo de la declaración local es la versión que acepta valores NULL del tipo deducido o el
propio tipo deducido si ya existe un tipo de valor que acepta valores NULL. Si el tipo deducido no es un tipo de
valor que se puede hacer que acepte valores NULL, se produce un error en tiempo de compilación. Si un
modificador de tipo que acepta valores NULL y un modificador de tipo matriz o tamaño de matriz se colocan en
una instrucción de declaración local sin tipo, se considera que el modificador de tipo que acepta valores NULL se
aplica al tipo de elemento de la matriz y los pasos anteriores se utilizan para determinar el tipo de elemento.
Los inicializadores de variables en instrucciones de declaración locales son equivalentes a las instrucciones de
asignación situadas en la ubicación textual de la declaración. Por lo tanto, si la ejecución bifurca a través de la
instrucción de declaración local, el inicializador de variable no se ejecuta. Si la instrucción de declaración local se
ejecuta más de una vez, el inicializador de variable se ejecuta el mismo número de veces. Las variables estáticas
solo ejecutan el inicializador por primera vez. Si se produce una excepción al inicializar una variable estática, la
variable estática se considera inicializada con el valor predeterminado del tipo de la variable estática.
En el ejemplo siguiente se muestra el uso de inicializadores:
Module Test
Sub F()
Static x As Integer = 5
Sub Main()
Dim i As Integer
For i = 1 to 3
F()
Next i
i = 3
label:
Dim y As Integer = 8
If i > 0 Then
Console.WriteLine("Local variable y = " & y)
y -= 1
i -= 1
GoTo label
End If
End Sub
End Module
Static variable x = 5
Static variable x = 6
Static variable x = 7
Local variable y = 8
Local variable y = 8
Local variable y = 8
Los inicializadores en variables locales estáticas son seguros para subprocesos y están protegidos contra
excepciones durante la inicialización. Si se produce una excepción durante un inicializador local estático, la
variable local estática tendrá su valor predeterminado y no se inicializará. Un inicializador local estático
Module Test
Sub F()
Static x As Integer = 5
End Sub
End Module
es equivalente a
Imports System.Threading
Imports Microsoft.VisualBasic.CompilerServices
Module Test
Class InitFlag
Public State As Short
End Class
Sub F()
Dim x As Integer
Las variables locales, las constantes locales y las variables estáticas se limitan al bloque de instrucciones en el
que se declaran. Las variables estáticas son especiales, por lo que sus nombres solo se pueden usar una vez en
todo el método. Por ejemplo, no es válido especificar dos declaraciones de variables estáticas con el mismo
nombre, aunque estén en bloques diferentes.
Declaraciones locales IMPLÍCITAS
Además de las instrucciones de declaración local, las variables locales también se pueden declarar
implícitamente mediante el uso de. Expresión de nombre simple que utiliza un nombre que no se resuelve en
otra cosa que declara una variable local con ese nombre. Por ejemplo:
Module Test
Sub Main()
x = 10
y = 20
Console.WriteLine(x + y)
End Sub
End Module
La declaración local implícita solo se produce en contextos de expresión que pueden aceptar una expresión
clasificada como una variable. La excepción a esta regla es que una variable local no se puede declarar
implícitamente cuando es el destino de una expresión de invocación de función, una expresión de indexación o
una expresión de acceso a miembros.
Las variables locales implícitas se tratan como si se declararan al principio del método contenedor. Por lo tanto,
siempre se limitan al cuerpo del método completo, incluso si se declaran dentro de una expresión lambda. Por
ejemplo, el código siguiente:
Option Explicit Off
Module Test
Sub Main()
Dim x = Sub()
a = 10
End Sub
Dim y = Sub()
Console.WriteLine(a)
End Sub
x()
y()
End Sub
End Module
imprimirá el valor 10 . Los variables locales implícitas se escriben como Object si no se adjuntara ningún
carácter de tipo al nombre de variable; de lo contrario, el tipo de la variable es el tipo de carácter de tipo. La
inferencia de tipo de variable local no se utiliza para variables locales implícitas.
Si la declaración local explícita se especifica mediante el entorno de compilación o por Option Explicit , se
deben declarar explícitamente todas las variables locales y no se permite la declaración implícita de variables.
with (Instrucción)
Una With instrucción permite varias referencias a los miembros de una expresión sin especificar varias veces la
expresión.
WithStatement
: 'With' Expression StatementTerminator
Block?
'End' 'With' StatementTerminator
;
La expresión se debe clasificar como un valor y se evalúa una vez, una vez realizada la entrada en el bloque.
Dentro del With bloque de instrucciones, una expresión de acceso a miembros o expresión de acceso de
diccionario que empieza con un punto o un signo de exclamación se evalúa como si la With expresión lo
precedía. Por ejemplo:
Structure Test
Public x As Integer
Module TestModule
Sub Main()
Dim y As Test
With y
.x = 10
Console.WriteLine(.x)
.x = .F()
End With
End Sub
End Module
No es válido crear una bifurcación en un With bloque de instrucciones desde fuera del bloque.
SyncLock (Instrucción)
Una SyncLock instrucción permite que las instrucciones se sincronicen en una expresión, lo que garantiza que
varios subprocesos de ejecución no ejecuten las mismas instrucciones al mismo tiempo.
SyncLockStatement
: 'SyncLock' Expression StatementTerminator
Block?
'End' 'SyncLock' StatementTerminator
;
La expresión se debe clasificar como un valor y se evalúa una vez, tras la entrada al bloque. Al entrar SyncLock
en el bloque, Shared System.Threading.Monitor.Enter se llama al método en la expresión especificada, que se
bloquea hasta que el subproceso de ejecución tiene un bloqueo exclusivo en el objeto devuelto por la expresión.
El tipo de la expresión en una SyncLock instrucción debe ser un tipo de referencia. Por ejemplo:
Class Test
Private count As Integer = 0
En el ejemplo anterior se sincroniza en la instancia específica de la clase Test para garantizar que no más de un
subproceso de ejecución puede Agregar o restar de la variable de recuento a la vez para una instancia
determinada.
El SyncLock bloque está incluido implícitamente en una Try instrucción cuyo Finally bloque llama al Shared
método System.Threading.Monitor.Exit en la expresión. Esto garantiza que se libere el bloqueo incluso cuando
se produzca una excepción. Como resultado, no es válido bifurcar en un SyncLock bloque desde fuera del
bloque y un SyncLock bloque se trata como una única instrucción para los fines de Resume y Resume Next . El
ejemplo anterior es equivalente al código siguiente:
Class Test
Private count As Integer = 0
count += 1
Add = count
Finally
System.Threading.Monitor.Exit(Me)
End Try
End Function
count -= 1
Subtract = count
Finally
System.Threading.Monitor.Exit(Me)
End Try
End Function
End Class
Instrucciones de eventos
Las RaiseEvent AddHandler instrucciones, y RemoveHandler generan eventos y controlan los eventos
dinámicamente.
EventStatement
: RaiseEventStatement
| AddHandlerStatement
| RemoveHandlerStatement
;
RaiseEvent (Instrucción)
Una RaiseEvent instrucción notifica a los controladores de eventos que se ha producido un evento
determinado.
RaiseEventStatement
: 'RaiseEvent' IdentifierOrKeyword
( OpenParenthesis ArgumentList? CloseParenthesis )? StatementTerminator
;
La expresión de nombre simple en una RaiseEvent instrucción se interpreta como una búsqueda de miembros
en Me . Por lo tanto, RaiseEvent x se interpreta como si fuera RaiseEvent Me.x . El resultado de la expresión se
debe clasificar como un acceso de evento para un evento definido en la propia clase; los eventos definidos en
tipos base no se pueden usar en una RaiseEvent instrucción.
La RaiseEvent instrucción se procesa como una llamada al Invoke método del delegado del evento, utilizando
los parámetros proporcionados, si los hay. Si el valor del delegado es Nothing , no se produce ninguna
excepción. Si no hay ningún argumento, se pueden omitir los paréntesis. Por ejemplo:
Class Raiser
Public Event E1(Count As Integer)
RaiseCount += 1
RaiseEvent E1(RaiseCount)
End Sub
End Class
Module Test
Private WithEvents x As Raiser
Class Raiser
Public Event E1(Count As Integer)
RaiseCount += 1
AddHandlerStatement
: 'AddHandler' Expression Comma Expression StatementTerminator
;
RemoveHandlerStatement
: 'RemoveHandler' Expression Comma Expression StatementTerminator
;
Cada instrucción toma dos argumentos: el primer argumento debe ser una expresión que se clasifique como
acceso de evento y el segundo argumento debe ser una expresión que se clasifique como un valor. El tipo del
segundo argumento debe ser el tipo de delegado asociado al acceso al evento. Por ejemplo:
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
Dado un evento, E, la instrucción llama al add_E método o relevante remove_E en la instancia para agregar o
quitar el delegado como un controlador para el evento. Por lo tanto, el código anterior es equivalente a:
Instrucciones de asignación
Una instrucción de asignación asigna el valor de una expresión a una variable. Hay varios tipos de asignación.
AssignmentStatement
: RegularAssignmentStatement
| CompoundAssignmentStatement
| MidAssignmentStatement
;
RegularAssignmentStatement
: Expression Equals Expression StatementTerminator
;
La expresión del lado izquierdo del operador de asignación se debe clasificar como una variable o como un
acceso de propiedad, mientras que la expresión del lado derecho del operador de asignación se debe clasificar
como un valor. El tipo de la expresión se debe poder convertir implícitamente al tipo de la variable o el acceso a
la propiedad.
Si la variable que se va a asignar es un elemento de matriz de un tipo de referencia, se realizará una
comprobación en tiempo de ejecución para asegurarse de que la expresión sea compatible con el tipo de
elemento de matriz. En el ejemplo siguiente, la última asignación hace que System.ArrayTypeMismatchException
se produzca una excepción, porque una instancia de ArrayList no se puede almacenar en un elemento de una
String matriz.
Si la expresión del lado izquierdo del operador de asignación se clasifica como una variable, la instrucción de
asignación almacena el valor en la variable. Si la expresión se clasifica como un acceso de propiedad, la
instrucción de asignación convierte el acceso a la propiedad en una invocación del Set descriptor de acceso de
la propiedad con el valor sustituido por el parámetro value. Por ejemplo:
Module Test
Private PValue As Integer
Sub Main()
' The following two lines are equivalent.
P = 10
set_P(10)
End Sub
End Module
Si el destino de la variable o el acceso a la propiedad se escribe como un tipo de valor pero no se clasifica como
una variable, se produce un error en tiempo de compilación. Por ejemplo:
Structure S
Public F As Integer
End Structure
Class C
Private PValue As S
Public Property P As S
Get
Return PValue
End Get
Set (Value As S)
PValue = Value
End Set
End Property
End Class
Module Test
Sub Main()
Dim ct As C = New C()
Dim rt As Object = new C()
Tenga en cuenta que la semántica de la asignación depende del tipo de la variable o propiedad a la que se
asigna. Si la variable a la que se asigna es un tipo de valor, la asignación copia el valor de la expresión en la
variable. Si la variable a la que se asigna es un tipo de referencia, la asignación copia la referencia, no el propio
valor, en la variable. Si el tipo de la variable es Object , la semántica de asignación se determina si el tipo del
valor es un tipo de valor o un tipo de referencia en tiempo de ejecución.
Nota. En el caso de los tipos intrínsecos como Integer y Date , la semántica de asignación de valores y de
referencia es la misma porque los tipos son inmutables. Como resultado, el lenguaje es libre de usar la
asignación de referencia en los tipos intrínsecos con conversión boxing como una optimización. Desde una
perspectiva de valor, el resultado es el mismo.
Dado que el carácter de igualdad ( = ) se usa para la asignación y para la igualdad, existe una ambigüedad
entre una asignación simple y una instrucción de invocación en situaciones como x = y.ToString() . En todos
estos casos, la instrucción de asignación tiene prioridad sobre el operador de igualdad. Esto significa que la
expresión de ejemplo se interpreta como x = (y.ToString()) en lugar de (x = y).ToString() .
Instrucciones de asignación compuesta
Una instrucción de asignación compuesta adopta la forma V op= E (donde op es un operador binario válido).
CompoundAssignmentStatement
: Expression CompoundBinaryOperator LineTerminator? Expression StatementTerminator
;
CompoundBinaryOperator
: '^' '=' | '*' '=' | '/' '=' | '\\' '=' | '+' '=' | '-' '='
| '&' '=' | '<' '<' '=' | '>' '>' '='
;
La expresión del lado izquierdo del operador de asignación se debe clasificar como una variable o como un
acceso de propiedad, mientras que la expresión del lado derecho del operador de asignación se debe clasificar
como un valor. La instrucción de asignación compuesta es equivalente a la instrucción, V = V op E con la
diferencia de que la variable en el lado izquierdo del operador de asignación compuesta solo se evalúa una vez.
En el ejemplo siguiente se muestra esta diferencia:
Module Test
Function GetIndex() As Integer
Console.WriteLine("Getting index")
Return 1
End Function
Sub Main()
Dim a(2) As Integer
Console.WriteLine("Simple assignment")
a(GetIndex()) = a(GetIndex()) + 1
Console.WriteLine("Compound assignment")
a(GetIndex()) += 1
End Sub
End Module
La expresión a(GetIndex()) se evalúa dos veces para la asignación simple, pero solo una vez para la asignación
compuesta, por lo que el código imprime:
Simple assignment
Getting index
Getting index
Compound assignment
Getting index
MidAssignmentStatement
: 'Mid' '$'? OpenParenthesis Expression Comma Expression
( Comma Expression )? CloseParenthesis Equals Expression StatementTerminator
;
El primer argumento es el destino de la asignación y debe clasificarse como una variable o como un acceso de
propiedad cuyo tipo se pueda convertir implícitamente a y desde String . El segundo parámetro es la posición
inicial de base 1 que corresponde a donde debe comenzar la asignación en la cadena de destino y debe
clasificarse como un valor cuyo tipo se debe poder convertir implícitamente en Integer . El tercer parámetro
opcional es el número de caracteres del valor del lado derecho que se va a asignar a la cadena de destino y se
debe clasificar como un valor cuyo tipo se pueda convertir implícitamente a Integer . El lado derecho es la
cadena de origen y debe clasificarse como un valor cuyo tipo se pueda convertir implícitamente a String . El
lado derecho se trunca al parámetro de longitud, si se especifica, y reemplaza los caracteres de la cadena del
lado izquierdo, comenzando en la posición inicial. Si la cadena del lado derecho contiene menos caracteres que
el tercer parámetro, solo se copiarán los caracteres de la cadena del lado derecho.
En el ejemplo siguiente se muestra ab123fg :
Module Test
Sub Main()
Dim s1 As String = "abcdefg"
Dim s2 As String = "1234567"
Mid$(s1, 3, 3) = s2
Console.WriteLine(s1)
End Sub
End Module
Instrucciones de invocación
Una instrucción de invocación invoca un método precedido por la palabra clave opcional Call . La instrucción
de invocación se procesa de la misma manera que la expresión de invocación de función, con algunas
diferencias que se indican a continuación. La expresión de invocación debe estar clasificada como un valor o
void. Se descarta cualquier valor resultante de la evaluación de la expresión de invocación.
Si Call se omite la palabra clave, la expresión de invocación debe comenzar con un identificador o una palabra
clave, o con . dentro de un With bloque. Por lo tanto, por ejemplo, " Call 1.ToString() " es una instrucción
válida pero " 1.ToString() " no lo es. (Tenga en cuenta que en un contexto de expresión, las expresiones de
invocación tampoco necesitan empezar por un identificador. Por ejemplo, " Dim x = 1.ToString() " es una
instrucción válida).
Existe otra diferencia entre las instrucciones de invocación y las expresiones de invocación: Si una instrucción de
invocación incluye una lista de argumentos, esto siempre se toma como la lista de argumentos de la invocación.
En el ejemplo siguiente se muestra la diferencia:
Module Test
Sub Main()
Call {Function() 15}(0)
' error: (0) is taken as argument list, but array is not invokable
Call f("a")
' error: ("a") is taken as argument list to the invocation of f
Call f()("a")
' valid, since () is the argument list for the invocation of f
Dim y = f("a")
' valid as an expression, since f("a") is interpreted as f()("a")
End Sub
InvocationStatement
: 'Call'? InvocationExpression StatementTerminator
;
Instrucciones condicionales
Las instrucciones condicionales permiten la ejecución condicional de instrucciones basadas en expresiones
evaluadas en tiempo de ejecución.
ConditionalStatement
: IfStatement
| SelectStatement
;
IfStatement
: BlockIfStatement
| LineIfThenStatement
;
BlockIfStatement
: 'If' BooleanExpression 'Then'? StatementTerminator
Block?
ElseIfStatement*
ElseStatement?
'End' 'If' StatementTerminator
;
ElseIfStatement
: ElseIf BooleanExpression 'Then'? StatementTerminator
Block?
;
ElseStatement
: 'Else' StatementTerminator
Block?
;
LineIfThenStatement
: 'If' BooleanExpression 'Then' Statements ( 'Else' Statements )? StatementTerminator
;
ElseIf
: 'ElseIf'
| 'Else' 'If'
;
Cada expresión de una If...Then...Else instrucción debe ser una expresión booleana, según las Expresiones
booleanasde la sección. (Nota: esto no requiere que la expresión tenga el tipo booleano). Si la expresión de la
If instrucción es true, se ejecutan las instrucciones incluidas en el If bloque. Si la expresión es false, se
evalúa cada una de las ElseIf expresiones. Si una de las ElseIf expresiones se evalúa como true, se ejecuta el
bloque correspondiente. Si ninguna expresión se evalúa como true y hay un Else bloque, Else se ejecuta el
bloque. Una vez que finaliza la ejecución de un bloque, la ejecución pasa al final de la If...Then...Else
instrucción.
La versión de línea de la If instrucción tiene un único conjunto de instrucciones que se ejecutarán si la If
expresión es True y un conjunto opcional de instrucciones que se ejecutarán si la expresión es False . Por
ejemplo:
Module Test
Sub Main()
Dim a As Integer = 10
Dim b As Integer = 20
La versión de línea de la instrucción If se enlaza menos estrictamente que ":" y Else se enlaza a la parte anterior
léxicamente más cercana If permitida por la sintaxis. Por ejemplo, las dos versiones siguientes son
equivalentes:
If True Then _
If True Then Console.WriteLine("a") Else Console.WriteLine("b") _
Else Console.WriteLine("c") : Console.WriteLine("d")
If True Then
If True Then
Console.WriteLine("a")
Else
Console.WriteLine("b")
End If
Console.WriteLine("c") : Console.WriteLine("d")
End If
Todas las instrucciones que no sean instrucciones de declaración de etiqueta se permiten dentro de una
instrucción de línea If , incluidas las instrucciones de bloque. Sin embargo, no pueden usar LineTerminators
como StatementTerminators, excepto en expresiones lambda de varias líneas. Por ejemplo:
CaseStatement
: 'Case' CaseClauses StatementTerminator
Block?
;
CaseClauses
: CaseClause ( Comma CaseClause )*
;
CaseClause
: ( 'Is' LineTerminator? )? ComparisonOperator LineTerminator? Expression
| Expression ( 'To' Expression )?
;
ComparisonOperator
: '=' | '<' '>' | '<' | '>' | '>' '=' | '<' '='
;
CaseElseStatement
: 'Case' 'Else' StatementTerminator
Block?
;
La expresión se debe clasificar como un valor. Cuando Select Case se ejecuta una instrucción, la Select
expresión se evalúa primero y las Case instrucciones se evalúan en orden de declaración textual. La primera
Case instrucción que se evalúa como True tiene su bloque ejecutado. Si ninguna Case instrucción se evalúa
como True y hay una Case Else instrucción, se ejecuta ese bloque. Una vez que un bloque ha terminado de
ejecutarse, la ejecución pasa al final de la Select instrucción.
La ejecución de un Case bloque no se permite "pasar" a la siguiente sección del conmutador. Esto evita una
clase común de errores que se producen en otros lenguajes cuando Case se omite accidentalmente una
instrucción de terminación. En el siguiente ejemplo, se muestra este comportamiento:
Module Test
Sub Main()
Dim x As Integer = 10
Select Case x
Case 5
Console.WriteLine("x = 5")
Case 10
Console.WriteLine("x = 10")
Case 20 - 10
Console.WriteLine("x = 20 - 10")
Case 30
Console.WriteLine("x = 30")
End Select
End Sub
End Module
El código imprime:
x = 10
Aunque Case 10 y Case 20 - 10 SELECT para el mismo valor, Case 10 se ejecuta porque precede
Case 20 - 10 textualmente. Cuando Case se alcanza la siguiente, la ejecución continúa después de la Select
instrucción.
Una Case cláusula puede tomar dos formas. Un formulario es una Is palabra clave opcional, un operador de
comparación y una expresión. La expresión se convierte al tipo de la Select expresión; si la expresión no se
pueden convertir implícitamente al tipo de la Select expresión, se produce un error en tiempo de compilación.
Si la Select expresión es E, el operador de comparación es OP y la Case expresión es E1, el caso se evalúa
como E OP E1. El operador debe ser válido para los tipos de las dos expresiones; en caso contrario, se produce
un error en tiempo de compilación.
El otro formulario es una expresión seguido opcionalmente por la palabra clave To y una segunda expresión.
Ambas expresiones se convierten al tipo de la Select expresión; si alguna de las expresiones no se puede
convertir implícitamente al tipo de la Select expresión, se produce un error en tiempo de compilación. Si la
Select expresión es E , la primera Case expresión es E1 y la segunda Case expresión es E2 , Case se
evalúa como E = E1 (si no E2 se especifica) o (E >= E1) And (E <= E2) . Los operadores deben ser válidos
para los tipos de las dos expresiones; en caso contrario, se produce un error en tiempo de compilación.
Instrucciones de bucle
Las instrucciones de bucle permiten la ejecución repetida de las instrucciones en su cuerpo.
LoopStatement
: WhileStatement
| DoLoopStatement
| ForStatement
| ForEachStatement
;
Cada vez que se escribe un cuerpo de bucle, se realiza una copia nueva de todas las variables locales declaradas
en ese cuerpo, inicializadas en los valores anteriores de las variables. Cualquier referencia a una variable dentro
del cuerpo del bucle usará la copia realizada más recientemente. Este código muestra un ejemplo:
Module Test
Sub Main()
Dim lambdas As New List(Of Action)
Dim x = 1
For i = 1 To 3
x = i
Dim y = x
lambdas.Add(Sub() Console.WriteLine(x & y))
Next
31 32 33
Cuando se ejecuta el cuerpo del bucle, usa la copia de la variable actual. Por ejemplo, la instrucción Dim y = x
hace referencia a la última copia de y y la copia original de x . Y cuando se crea una expresión lambda,
recuerda cualquier copia de una variable que estuviera actualizada en el momento en que se creó. Por lo tanto,
cada lambda usa la misma copia compartida de x , pero otra copia de y . Al final del programa, cuando se
ejecutan las expresiones lambda, esa copia compartida de a la x que hacen referencia se encuentra ahora en su
valor final 3.
Tenga en cuenta que si no hay expresiones lambda ni expresiones LINQ, es imposible saber que se realiza una
copia nueva en la entrada de bucle. En realidad, las optimizaciones del compilador evitarán que se realicen
copias en este caso. Tenga en cuenta también que no es válido para GoTo en un bucle que contenga lambdas o
expresiones Linq.
While... Finalizar while y do... Instrucciones Loop
Una While instrucción de bucle or se Do repite en función de una expresión booleana.
WhileStatement
: 'While' BooleanExpression StatementTerminator
Block?
'End' 'While' StatementTerminator
;
DoLoopStatement
: DoTopLoopStatement
| DoBottomLoopStatement
;
DoTopLoopStatement
: 'Do' ( WhileOrUntil BooleanExpression )? StatementTerminator
Block?
'Loop' StatementTerminator
;
DoBottomLoopStatement
: 'Do' StatementTerminator
Block?
'Loop' WhileOrUntil BooleanExpression StatementTerminator
;
WhileOrUntil
: 'While' | 'Until'
;
Una While instrucción de bucle se repite siempre que la expresión booleana se evalúe como true; una Do
instrucción de bucle puede contener una condición más compleja. Una expresión se puede colocar después de la
Do palabra clave o después de la Loop palabra clave, pero no después de ambas. La expresión booleana se
evalúa según las Expresiones booleanaspor sección. (Nota: esto no requiere que la expresión tenga el tipo
booleano). También es válido para no especificar ninguna expresión; en ese caso, el bucle nunca se cerrará. Si la
expresión se coloca después de Do , se evaluará antes de que se ejecute el bloque de bucle en cada iteración. Si
la expresión se coloca después de Loop , se evaluará después de que se haya ejecutado el bloque de bucle en
cada iteración. Al colocar la expresión después de Loop , se generará un bucle más que la ubicación después de
Do . En el ejemplo siguiente se muestra este comportamiento:
Module Test
Sub Main()
Dim x As Integer
x = 3
Do While x = 1
Console.WriteLine("First loop")
Loop
Do
Console.WriteLine("Second loop")
Loop While x = 1
End Sub
End Module
Second Loop
En el caso del primer bucle, la condición se evalúa antes de que se ejecute el bucle. En el caso del segundo bucle,
la condición se ejecuta después de que se ejecute el bucle. La expresión condicional debe ir precedida de una
While palabra clave o una Until palabra clave. El primero interrumpe el bucle si la condición se evalúa como
false, la última cuando la condición se evalúa como true.
Nota. Until no es una palabra reservada.
Para... Instrucciones Next
Una For...Next instrucción repite en función de un conjunto de límites. Una For instrucción especifica una
variable de control de bucle, una expresión de límite inferior, una expresión de límite superior y una expresión
de valor de paso opcional. La variable de control de bucle se especifica mediante un identificador seguido de
una As cláusula opcional o una expresión.
ForStatement
: 'For' LoopControlVariable Equals Expression 'To' Expression
( 'Step' Expression )? StatementTerminator
Block?
( 'Next' NextExpressionList? StatementTerminator )?
;
LoopControlVariable
: Identifier ( IdentifierModifiers 'As' TypeName )?
| Expression
;
NextExpressionList
: Expression ( Comma Expression )*
;
Según las reglas siguientes, la variable de control de bucle hace referencia a una nueva variable local específica
de esta For...Next instrucción o a una variable ya existente, o a una expresión.
Si la variable de control de bucle es un identificador con una As cláusula, el identificador define una
nueva variable local del tipo especificado en la As cláusula, en el ámbito del bucle completo For .
Si la variable de control de bucle es un identificador sin una As cláusula, el identificador se resuelve
primero con las reglas de resolución de nombres simples (consulte la sección expresiones de nombre
simple), excepto que esta aparición del identificador no en y de sí mismo provocaría la creación de una
variable local implícita (sección declaraciones locales implícitas).
Si esta resolución se realiza correctamente y el resultado se clasifica como una variable, la variable
de control de bucle es esa variable ya existente.
Si se produce un error en la resolución, o si la resolución se realiza correctamente y el resultado se
clasifica como un tipo, entonces:
Si se usa la inferencia de tipo de variable local, el identificador define una nueva variable local
cuyo tipo se deduce de las expresiones de paso y límite, con ámbito en el For bucle completo;
Si no se usa la inferencia de tipo de variable local pero la declaración local implícita es, se crea
una variable local implícita cuyo ámbito es el método completo (sección declaraciones locales
implícitas) y la variable de control de bucle hace referencia a esta variable ya existente;
Si no se usa ninguna inferencia de tipo de variable local ni declaraciones locales IMPLÍCITAS, se
trata de un error.
Si la resolución se realiza correctamente con algo clasificado como un tipo o una variable, es un
error.
Si la variable de control de bucle es una expresión, la expresión se debe clasificar como una variable.
Una variable de control de bucle no se puede usar en otra instrucción de inclusión For...Next . El tipo de la
variable de control de bucle de una For instrucción determina el tipo de la iteración y debe ser uno de los
siguientes:
Byte , SByte , UShort , Short , UInteger , Integer , ULong , Long , Decimal , Single , Double
Un tipo enumerado
Object
Tipo T que tiene los siguientes operadores, donde B es un tipo que se puede utilizar en una expresión
booleana:
Las expresiones de enlace y de paso deben poder convertirse implícitamente al tipo de la variable de control de
bucle y deben clasificarse como valores. En tiempo de compilación, el tipo de la variable de control de bucle se
deduce eligiendo el tipo más ancho entre los tipos de expresión de límite inferior, límite superior y expresión de
paso. Si no hay una conversión de ampliación entre dos de los tipos, se produce un error en tiempo de
compilación.
En tiempo de ejecución, si el tipo de la variable de control de bucle es Object , el tipo de la iteración se deduce
como en tiempo de compilación, con dos excepciones. En primer lugar, si las expresiones de paso y límite son
todos tipos enteros pero no tienen un tipo más ancho, se deducirá el tipo más ancho que abarque los tres tipos.
Y en segundo lugar, si el tipo de la variable de control de bucle se deduce como String , se Double deducirá en
su lugar. Si, en tiempo de ejecución, no se puede determinar ningún tipo de control de bucle o si alguna de las
expresiones no se puede convertir al tipo de control de bucle, System.InvalidCastException se producirá una
excepción. Una vez que se ha elegido un tipo de control de bucle al principio del bucle, se usará el mismo tipo a
lo largo de la iteración, independientemente de los cambios realizados en el valor de la variable de control de
bucle.
Una For instrucción debe cerrarse mediante una Next instrucción coincidente. Una Next instrucción sin una
variable coincide con la instrucción Open más interna For , mientras Next que una instrucción con una o más
variables de control de bucle, de izquierda a derecha, coincide con los For bucles que coinciden con cada
variable. Si una variable coincide con un For bucle que no es el bucle más anidado en ese momento, se
producirá un error en tiempo de compilación.
Al principio del bucle, las tres expresiones se evalúan en orden textual y la expresión de límite inferior se asigna
a la variable de control de bucle. Si se omite el valor del paso, es implícitamente el literal 1 , convertido al tipo
de la variable de control de bucle. Las tres expresiones solo se evalúan en el principio del bucle.
Al principio de cada bucle, la variable de control se compara para ver si es mayor que el punto final si la
expresión de paso es positiva o menor que el punto final si la expresión de paso es negativa. Si es, el For bucle
finaliza; de lo contrario, se ejecuta el bloque de bucle. Si la variable de control de bucle no es un tipo primitivo, el
operador de comparación se determina si la expresión step >= step - step es true o false. En la Next
instrucción, el valor STEP se agrega a la variable de control y la ejecución vuelve a la parte superior del bucle.
Tenga en cuenta que no se crea una nueva copia de la variable de control de bucle en cada iteración del bloque
de bucle. En este sentido, la For instrucción difiere de For Each (sección for each... Instrucciones siguientes).
No es válido crear una bifurcación en un For bucle desde fuera del bucle.
Para cada... Instrucciones Next
Una For Each...Next instrucción repite en función de los elementos de una expresión. Una For Each
instrucción especifica una variable de control de bucle y una expresión de enumerador. La variable de control de
bucle se especifica mediante un identificador seguido de una As cláusula opcional o una expresión.
ForEachStatement
: 'For' 'Each' LoopControlVariable 'In' LineTerminator? Expression StatementTerminator
Block?
( 'Next' NextExpressionList? StatementTerminator )?
;
Siguiendo las mismas reglas que las For...Next instrucciones (sección para... Instrucciones siguientes), la
variable de control de bucle hace referencia a una nueva variable local específica de para cada... Instrucción
siguiente, o a una variable ya existente o a una expresión.
La expresión del enumerador debe estar clasificada como un valor y su tipo debe ser un tipo de colección o
Object . Si el tipo de la expresión del enumerador es Object , todo el procesamiento se aplaza hasta el tiempo
de ejecución. De lo contrario, debe existir una conversión del tipo de elemento de la colección al tipo de la
variable de control de bucle.
La variable de control de bucle no se puede usar en otra instrucción de inclusión For Each . Una For Each
instrucción debe cerrarse mediante una Next instrucción coincidente. Una Next instrucción sin una variable de
control de bucle coincide con el abierto más interno For Each . Una Next instrucción con una o más variables
de control de bucle, de izquierda a derecha, coincide con los For Each bucles que tienen la misma variable de
control de bucle. Si una variable coincide con un For Each bucle que no es el bucle más anidado en ese
momento, se produce un error en tiempo de compilación.
Un tipo C se dice que es un tipo de colección si uno de los siguientes:
Se cumplen todas las condiciones siguientes:
C contiene una instancia accesible, un método de extensión o compartido con la firma
GetEnumerator() que devuelve un tipo E .
E contiene una instancia accesible, un método de extensión o compartido con la firma MoveNext() y
el tipo de valor devuelto Boolean .
E contiene una instancia accesible o una propiedad compartida denominada Current que tiene un
captador. El tipo de esta propiedad es el tipo de elemento del tipo de colección.
Implementa la interfaz System.Collections.Generic.IEnumerable(Of T) , en cuyo caso se considera que el
tipo de elemento de la colección es T .
Implementa la interfaz System.Collections.IEnumerable , en cuyo caso se considera que el tipo de
elemento de la colección es Object .
A continuación se muestra un ejemplo de una clase que se puede enumerar:
Return collection.integers(index)
End Get
End Property
End Class
For i = 0 To 10
integers(i) = I
Next i
End Sub
Antes de que comience el bucle, se evalúa la expresión del enumerador. Si el tipo de la expresión no satisface el
modelo de diseño, la expresión se convierte en System.Collections.IEnumerable o
System.Collections.Generic.IEnumerable(Of T) . Si el tipo de expresión implementa la interfaz genérica, se
prefiere la interfaz genérica en tiempo de compilación, pero se prefiere la interfaz no genérica en tiempo de
ejecución. Si el tipo de expresión implementa la interfaz genérica varias veces, la instrucción se considera
ambigua y se produce un error en tiempo de compilación.
Nota. Se prefiere la interfaz no genérica en el caso del enlace en tiempo de ejecución, porque la selección de la
interfaz genérica significa que todas las llamadas a los métodos de interfaz implicarán parámetros de tipo. Dado
que no es posible conocer los argumentos de tipo coincidentes en tiempo de ejecución, todas estas llamadas
tendrían que realizarse mediante llamadas enlazadas en tiempo de ejecución. Esto sería más lento que llamar a
la interfaz no genérica porque se podía llamar a la interfaz no genérica mediante llamadas en tiempo de
compilación.
GetEnumerator se llama a en el valor resultante y el valor devuelto de la función se almacena en un temporal. A
continuación, al principio de cada iteración, MoveNext se llama a en el temporal. Si devuelve False , el bucle
finaliza. De lo contrario, cada iteración del bucle se ejecuta de la siguiente manera:
1. Si la variable de control de bucle identificó una nueva variable local (en lugar de una ya existente), se crea
una copia nueva de esta variable local. En la iteración actual, todas las referencias dentro del bloque de bucle
hará referencia a esta copia.
2. La Current propiedad se recupera, se convierte al tipo de la variable de control de bucle
(independientemente de si la conversión es implícita o explícita) y se asigna a la variable de control de bucle.
3. El bloque de bucle se ejecuta.
Nota. Hay un pequeño cambio en el comportamiento entre la versión 10,0 y la 11,0 del idioma. Antes de 11,0,
no se creaba una nueva variable de iteración para cada iteración del bucle. Esta diferencia solo se observa si la
variable de iteración se captura mediante una expresión lambda o una expresión LINQ que se invoca después
del bucle:
Hasta Visual Basic 10,0, esto generó una advertencia en tiempo de compilación y se imprime "3" tres veces. Esto
se debe a que solo había una única variable "x" compartida por todas las iteraciones del bucle, y las tres
expresiones lambda capturaban la misma "x" y, en el momento en que se ejecutaban las expresiones lambda,
mantenía el número 3. A partir de Visual Basic 11,0, imprime "1, 2, 3". Esto se debe a que cada expresión lambda
captura una variable "x" diferente.
Nota. El elemento actual de la iteración se convierte al tipo de la variable de control de bucle incluso si la
conversión es explícita porque no hay ningún lugar adecuado para introducir un operador de conversión en la
instrucción. Esto resultó especialmente problemático al trabajar con el tipo ahora obsoleto
System.Collections.ArrayList , porque su tipo de elemento es Object . Esto habría requerido conversiones en
un gran número de bucles, algo que pensamos que no era idóneo. Paradójicamente, los genéricos han
habilitado la creación de una colección fuertemente tipada, System.Collections.Generic.List(Of T) , que podría
haber hecho replanteado este punto de diseño, pero por razones de compatibilidad, no se puede cambiar ahora.
Cuando Next se alcanza la instrucción, la ejecución vuelve a la parte superior del bucle. Si se especifica una
variable después de la Next palabra clave, debe ser la misma que la primera variable después de For Each .
Por ejemplo, considere el siguiente código:
Module Test
Sub Main()
Dim i As Integer
Dim c As IntegerCollection = New IntegerCollection()
For Each i In c
Console.WriteLine(i)
Next i
End Sub
End Module
Dim e As IntegerCollection.IntegerCollectionEnumerator
e = c.GetEnumerator()
While e.MoveNext()
i = e.Current
Console.WriteLine(i)
End While
End Sub
End Module
Si el tipo E del enumerador implementa System.IDisposable , el enumerador se elimina al salir del bucle
llamando al Dispose método. Esto garantiza que se liberen los recursos mantenidos por el enumerador. Si el
método que contiene la For Each instrucción no utiliza el control de errores no estructurado, la For Each
instrucción se encapsula en una Try instrucción con el Dispose método llamado en Finally para garantizar la
limpieza.
Nota. El System.Arraytipo es un tipo de colección y, dado que todos los tipos de matriz derivan de
System.Array , se permite cualquier expresión de tipo de matriz en una For Each instrucción. En el caso de las
matrices unidimensionales, la For Each instrucción enumera los elementos de la matriz en aumento del orden
de índice, empezando por el índice 0 y terminando por la longitud del índice-1. En el caso de las matrices
multidimensionales, los índices de la dimensión situada más a la derecha aumentan en primer lugar.
Por ejemplo, el código siguiente imprime 1 2 3 4 :
Module Test
Sub Main()
Dim x(,) As Integer = { { 1, 2 }, { 3, 4 } }
Dim i As Integer
For Each i In x
Console.Write(i & " ")
Next i
End Sub
End Module
No es válido crear una bifurcación en un For Each bloque de instrucciones desde fuera del bloque.
ErrorHandlingStatement
: StructuredErrorStatement
| UnstructuredErrorStatement
;
StructuredErrorStatement
: ThrowStatement
| TryStatement
;
TryStatement
: 'Try' StatementTerminator
Block?
CatchStatement*
FinallyStatement?
'End' 'Try' StatementTerminator
;
Por ejemplo:
Module Test
Sub ThrowException()
Throw New Exception()
End Sub
Sub Main()
Try
ThrowException()
Catch e As Exception
Console.WriteLine("Caught exception!")
Finally
Console.WriteLine("Exiting try.")
End Try
End Sub
End Module
Una Try instrucción se compone de tres tipos de bloques: bloques try, bloques Catch y bloques Finally. Un
bloque try es un bloque de instrucciones que contiene las instrucciones que se van a ejecutar. Un bloque catch
es un bloque de instrucciones que controla una excepción. Un bloque Finally es un bloque de instrucciones que
contiene instrucciones que se ejecutan cuando Try se sale de la instrucción, independientemente de si se ha
producido una excepción y se ha controlado. Una Try instrucción, que solo puede contener un bloque try y un
bloque Finally, debe contener al menos un bloque catch o Finally. No es válido transferir explícitamente la
ejecución a un bloque try excepto desde dentro de un bloque catch en la misma instrucción.
Bloques Finally
Un Finally bloque siempre se ejecuta cuando la ejecución deja parte de la Try instrucción. No se requiere
ninguna acción explícita para ejecutar el Finally bloque; cuando la ejecución sale de la Try instrucción, el
sistema ejecutará automáticamente el Finally bloque y después transferirá la ejecución a su destino previsto.
El Finally bloque se ejecuta sin tener en cuenta el modo en que la ejecución sale de la Try instrucción: hasta
el final del Try bloque, hasta el final de un Catch bloque, a través de una instrucción Exit Try , a través de
una GoTo instrucción o mediante la no administración de una excepción producida.
Tenga en cuenta que la Await expresión de un método asincrónico, y la Yield instrucción en un método de
iterador, puede hacer que el flujo de control se suspenda en la instancia del método Async o iterator y se
reanude en otra instancia del método. Sin embargo, esto es simplemente una suspensión de la ejecución y no
implica salir del método asincrónico o de la instancia del método de iterador respectivo, por lo que no hace que
Finally se ejecuten los bloques.
Bloques catch
Si se produce una excepción al procesar el Try bloque, cada Catch instrucción se examina en orden textual
para determinar si controla la excepción.
CatchStatement
: 'Catch' ( Identifier ( 'As' NonArrayTypeName )? )?
( 'When' BooleanExpression )? StatementTerminator
Block?
;
Module Test
Sub Main()
Dim i As Integer = 5
Try
Throw New ArgumentException()
Catch e As OverflowException When i = 5
Console.WriteLine("First handler")
Catch e As ArgumentException When i = 4
Console.WriteLine("Second handler")
Catch When i = 5
Console.WriteLine("Third handler")
End Try
End Sub
End Module
Third handler
Si una Catch cláusula controla la excepción, la ejecución se transfiere al Catch bloque. Al final del Catch
bloque, la ejecución se transfiere a la primera instrucción que sigue a la Try instrucción. La Try instrucción no
controlará las excepciones que se produzcan en un Catch bloque. Si ninguna Catch cláusula controla la
excepción, la ejecución se transfiere a una ubicación determinada por el sistema.
No es válido transferir explícitamente la ejecución a un Catch bloque.
Los filtros de las cláusulas when se evalúan normalmente antes de que se produzca la excepción. Por ejemplo, el
siguiente código imprimirá "Filter, Finally, catch".
Sub Main()
Try
Foo()
Catch ex As Exception When F()
Console.WriteLine("Catch")
End Try
End Sub
Sub Foo()
Try
Throw New Exception
Finally
Console.WriteLine("Finally")
End Try
End Sub
Sin embargo, los métodos Async e iterator hacen que todos los bloques Finally dentro de ellos se ejecuten antes
que los filtros fuera de. Por ejemplo, si el código anterior tuviera Async Sub Foo() , el resultado sería "Finally,
Filter, catch".
Throw (Instrucción )
La instrucción genera una excepción, que está representada por una instancia de un tipo derivado de
Throw
System.Exception .
ThrowStatement
: 'Throw' Expression? StatementTerminator
;
Una Throw instrucción puede omitir la expresión dentro de un bloque catch de una Try instrucción, siempre y
cuando no haya ningún bloque Finally que intervenga. En ese caso, la instrucción vuelve a producir la excepción
que se controla actualmente dentro del bloque catch. Por ejemplo:
Sub Test(x As Integer)
Try
Throw New Exception()
Catch
If x = 0 Then
Throw ' OK, rethrows exception from above.
Else
Try
If x = 1 Then
Throw ' OK, rethrows exception from above.
End If
Finally
Throw ' Invalid, inside of a Finally.
End Try
End If
End Try
End Sub
UnstructuredErrorStatement
: ErrorStatement
| OnErrorStatement
| ResumeStatement
;
Por ejemplo:
Module Test
Sub ThrowException()
Error 5
End Sub
Sub Main()
On Error GoTo GotException
ThrowException()
Exit Sub
GotException:
Console.WriteLine("Caught exception!")
Resume Next
End Sub
End Module
ErrorStatement
: 'Error' Expression StatementTerminator
;
Instrucción On Error
Una On Error instrucción modifica el estado más reciente de control de excepciones.
OnErrorStatement
: 'On' 'Error' ErrorClause StatementTerminator
;
ErrorClause
: 'GoTo' '-' '1'
| 'GoTo' '0'
| GoToStatement
| 'Resume' 'Next'
;
ResumeStatement
: 'Resume' ResumeClause? StatementTerminator
;
ResumeClause
: 'Next'
| LabelName
;
Si Next se especifica el modificador, la ejecución vuelve a la instrucción que se habría ejecutado después de la
instrucción que produjo la excepción más reciente. Si se especifica un nombre de etiqueta, la ejecución vuelve a
la etiqueta.
Dado SyncLockque la instrucción contiene un bloque de control de errores estructurado implícito Resume y
Resume Next tiene comportamientos especiales para las excepciones que se producen en las SyncLock
instrucciones. Resume Devuelve la ejecución al principio de la SyncLock instrucción, mientras Resume Next que
devuelve la ejecución a la siguiente instrucción que sigue a la SyncLock instrucción. Por ejemplo, considere el
siguiente código:
Class LockClass
End Class
Module Test
Sub Main()
Dim FirstTime As Boolean = True
Dim Lock As LockClass = New LockClass()
SyncLock Lock
Console.WriteLine("Before exception")
Throw New Exception()
Console.WriteLine("After exception")
End SyncLock
Console.WriteLine("After SyncLock")
Exit Sub
Handler:
If FirstTime Then
FirstTime = False
Resume
Else
Resume Next
End If
End Sub
End Module
Before exception
Before exception
After SyncLock
La primera vez a través de la SyncLock instrucción, Resume devuelve la ejecución al principio de la SyncLock
instrucción. La segunda vez a través de la SyncLock instrucción, Resume Next devuelve la ejecución hasta el final
de la SyncLock instrucción. Resume y Resume Next no están permitidos dentro de una SyncLock instrucción.
En todos los casos, cuando Resume se ejecuta una instrucción, la excepción más reciente se establece en
Nothing . Si Resume se ejecuta una instrucción sin la excepción más reciente, la instrucción genera una
System.Exception excepción que contiene el número de error de Visual Basic 20 (reanudar sin error).
Instrucciones de rama
Las instrucciones de bifurcación modifican el flujo de ejecución en un método. Hay seis instrucciones de
bifurcación:
1. Una GoTo instrucción hace que la ejecución se transfiera a la etiqueta especificada en el método. No se
permite GoTo en un Try bloque,,,, ni en Using SyncLock With For For Each ningún bloque de bucle si
se captura una variable local de ese bloque en una expresión lambda o Linq.
2. Una Exit instrucción transfiere la ejecución a la siguiente instrucción después del final de la instrucción de
bloque que contiene inmediatamente el tipo especificado. Si el bloque es el bloque de método, el flujo de
control sale del método tal y como se describe en la sección flujo de control. Si la Exit instrucción no está
incluida en el tipo de bloque especificado en la instrucción, se produce un error en tiempo de compilación.
3. Una Continue instrucción transfiere la ejecución al final de la instrucción de bucle de bloque que contiene
inmediatamente el tipo especificado. Si la Continue instrucción no está incluida en el tipo de bloque
especificado en la instrucción, se produce un error en tiempo de compilación.
4. Una Stop instrucción hace que se produzca una excepción del depurador.
5. Una End instrucción finaliza el programa. Los finalizadores se ejecutan antes del cierre, pero no se ejecutan
los bloques Finally de las instrucciones que se están ejecutando actualmente Try . Esta instrucción no se
puede usar en programas que no son ejecutables (por ejemplo, dll).
6. Una Return instrucción sin expresión es equivalente a una Exit Sub instrucción o Exit Function . Una
Return instrucción con una expresión solo se permite en un método normal que sea una función, o en un
método asincrónico que sea una función con el tipo de valor devuelto Task(Of T) para algunos T . La
expresión debe estar clasificada como un valor que se pueda convertir implícitamente a la variable de
devolución de función (en el caso de métodos regulares) o a la variable de devolución de tarea (en el caso de
métodos asincrónicos). Su comportamiento es evaluar su expresión, almacenarla en la variable devuelta y, a
continuación, ejecutar una Exit Function instrucción implícita.
BranchStatement
: GoToStatement
| ExitStatement
| ContinueStatement
| StopStatement
| EndStatement
| ReturnStatement
;
GoToStatement
: 'GoTo' LabelName StatementTerminator
;
ExitStatement
: 'Exit' ExitKind StatementTerminator
;
ExitKind
: 'Do' | 'For' | 'While' | 'Select' | 'Sub' | 'Function' | 'Property' | 'Try'
;
ContinueStatement
: 'Continue' ContinueKind StatementTerminator
;
ContinueKind
: 'Do' | 'For' | 'While'
;
StopStatement
: 'Stop' StatementTerminator
;
EndStatement
: 'End' StatementTerminator
;
ReturnStatement
: 'Return' Expression? StatementTerminator
;
Instrucción ReDim
Una ReDim instrucción crea instancias de nuevas matrices.
RedimStatement
: 'ReDim' 'Preserve'? RedimClauses StatementTerminator
;
RedimClauses
: RedimClause ( Comma RedimClause )*
;
RedimClause
: Expression ArraySizeInitializationModifier
;
Cada cláusula de la instrucción debe estar clasificada como una variable o un acceso de propiedad cuyo tipo es
un tipo de matriz o Object , y va seguido de una lista de límites de matriz. El número de los límites debe ser
coherente con el tipo de la variable; se permite cualquier número de límites para Object . En tiempo de
ejecución, se crea una instancia de una matriz para cada expresión de izquierda a derecha con los límites
especificados y, a continuación, se asigna a la variable o propiedad. Si el tipo de variable es Object , el número
de dimensiones es el número de dimensiones especificado y el tipo de elemento de la matriz es Object . Si el
número especificado de dimensiones es incompatible con la variable o la propiedad en tiempo de ejecución, se
produce un error en tiempo de compilación. Por ejemplo:
Module Test
Sub Main()
Dim o As Object
Dim b() As Byte
Dim i(,) As Integer
Si Preserve se especifica la palabra clave, las expresiones también se deben clasificar como un valor y el nuevo
tamaño de cada dimensión, excepto el de la derecha, debe ser el mismo que el tamaño de la matriz existente.
Los valores de la matriz existente se copian en la nueva matriz: Si la nueva matriz es más pequeña, se descartan
los valores existentes; Si la nueva matriz es más grande, los elementos adicionales se inicializarán en el valor
predeterminado del tipo de elemento de la matriz. Por ejemplo, considere el siguiente código:
Module Test
Sub Main()
Dim x(5, 5) As Integer
x(3, 3) = 3
3, 0
Si la referencia de matriz existente es un valor null en tiempo de ejecución, no se proporciona ningún error.
Aparte de la dimensión situada más a la derecha, si cambia el tamaño de una dimensión, se
System.ArrayTypeMismatchException iniciará una.
Module Test
Sub Main()
Dim x() As Integer = New Integer(5) {}
EraseStatement
: 'Erase' EraseExpressions StatementTerminator
;
EraseExpressions
: Expression ( Comma Expression )*
;
Using (instrucción)
El recolector de elementos no utilizados libera automáticamente las instancias de tipos cuando se ejecuta una
colección y no se encuentra ninguna referencia activa a la instancia. Si un tipo se encuentra en un recurso
especialmente valioso y escaso (como conexiones de base de datos o identificadores de archivo), es posible que
no sea deseable esperar hasta la siguiente recolección de elementos no utilizados para limpiar una instancia
concreta del tipo que ya no está en uso. Para proporcionar una manera ligera de liberar recursos antes de una
colección, un tipo puede implementar la System.IDisposable interfaz. Un tipo que lo hace expone un Dispose
método que se puede llamar para obligar a que se liberen recursos valiosos inmediatamente, como por
ejemplo:
Module Test
Sub Main()
Dim x As DBConnection = New DBConnection("...")
UsingStatement
: 'Using' UsingResources StatementTerminator
Block?
'End' 'Using' StatementTerminator
;
UsingResources
: VariableDeclarators
| Expression
;
Si el recurso es una instrucción de declaración de variable local, el tipo de la declaración de variable local debe
ser un tipo que se pueda convertir implícitamente en System.IDisposable . Las variables locales declaradas son
de solo lectura, con el ámbito del Using bloque de instrucciones y deben incluir un inicializador. Si el recurso es
el resultado de una expresión, la expresión se debe clasificar como un valor y debe ser de un tipo que se pueda
convertir implícitamente a System.IDisposable . La expresión solo se evalúa una vez, al principio de la
instrucción.
El Usingbloque está incluido implícitamente en una Try instrucción cuyo bloque Finally llama al método
IDisposable.Dispose en el recurso. Esto garantiza que el recurso se elimine incluso cuando se produzca una
excepción. Como resultado, no es válido bifurcar en un Using bloque desde fuera del bloque y un Using
bloque se trata como una única instrucción para los fines de Resume y Resume Next . Si el recurso es Nothing ,
no se realiza ninguna llamada a Dispose . Por lo tanto, el ejemplo:
equivale a:
Una Using instrucción que tiene una instrucción de declaración de variable local puede adquirir varios recursos
a la vez, lo que equivale a las instrucciones anidadas Using . Por ejemplo, una Using instrucción con la forma:
equivale a:
await (instrucción)
Una instrucción Await tiene la misma sintaxis que una expresión de operador Await ( operador Awaitde sección),
solo se permite en métodos que también permiten expresiones Await y tiene el mismo comportamiento que
una expresión de operador Await.
Sin embargo, se puede clasificar como un valor o como void. Se descarta cualquier valor resultante de la
evaluación de la expresión del operador Await.
AwaitStatement
: AwaitOperatorExpression StatementTerminator
;
Instrucción Yield
Las instrucciones yield se relacionan con los métodos de iterador, que se describen en la sección métodos de
iterador.
YieldStatement
: 'Yield' Expression StatementTerminator
;
Yield es una palabra reservada si el método de inclusión inmediato o la expresión lambda en la que aparece
tiene un Iterator modificador y si Yield aparece después de ese Iterator modificador; no se reserva en
otro lugar. También está sin reservar en las directivas de preprocesador. La instrucción yield solo se permite en el
cuerpo de un método o una expresión lambda donde es una palabra reservada. En el método o la expresión
lambda de inclusión inmediata, la instrucción yield no puede aparecer dentro del cuerpo de un Catch Finally
bloque o, ni dentro del cuerpo de una SyncLock instrucción.
La instrucción yield toma una expresión única que se debe clasificar como un valor y cuyo tipo se puede
convertir implícitamente al tipo de la variable actual del iterador ( métodos de iteradorde sección) de su método
de iterador envolvente.
El flujo de control solo llega a una Yield instrucción cuando MoveNext se invoca el método en un objeto
iterador. (Esto se debe a que una instancia de método de iterador solo ejecuta sus instrucciones debido a los
MoveNext Dispose métodos o que se llaman en un objeto de iterador; y el Dispose método solo ejecutará el
código en Finally bloques, donde Yield no se permite).
Cuando Yield se ejecuta una instrucción, su expresión se evalúa y se almacena en la variable actual del iterador
de la instancia de método de iterador asociada a ese objeto iterador. El valor True se devuelve al invocador de
MoveNext y el punto de control de esta instancia deja de avanzar hasta la siguiente invocación de MoveNext en
el objeto iterador.
Expresiones
15/11/2021 • 304 minutes to read
Una expresión es una secuencia de operadores y operandos que especifica un cálculo de un valor, o que designa
una variable o una constante. En este capítulo se define la sintaxis, el orden de evaluación de los operandos y
operadores y el significado de las expresiones.
Expression
: SimpleExpression
| TypeExpression
| MemberAccessExpression
| DictionaryAccessExpression
| InvocationExpression
| IndexExpression
| NewExpression
| CastExpression
| OperatorExpression
| ConditionalExpression
| LambdaExpression
| QueryExpression
| XMLLiteralExpression
| XMLMemberAccessExpression
;
Clasificaciones de expresiones
Cada expresión se clasifica como una de las siguientes:
Valor. Todos los valores tienen un tipo asociado.
Una variable. Cada variable tiene un tipo asociado, es decir, el tipo declarado de la variable.
Un espacio de nombres. Una expresión con esta clasificación solo puede aparecer como el lado izquierdo
de un acceso de miembro. En cualquier otro contexto, una expresión clasificada como un espacio de
nombres produce un error en tiempo de compilación.
Un tipo. Una expresión con esta clasificación solo puede aparecer como el lado izquierdo de un acceso de
miembro. En cualquier otro contexto, una expresión clasificada como un tipo genera un error en tiempo
de compilación.
Un grupo de métodos, que es un conjunto de métodos sobrecargados en el mismo nombre. Un grupo de
métodos puede tener una expresión de destino asociada y una lista de argumentos de tipo asociada.
Un puntero de método, que representa la ubicación de un método. Un puntero de método puede tener
una expresión de destino asociada y una lista de argumentos de tipo asociada.
Un método lambda, que es un método anónimo.
Un grupo de propiedades, que es un conjunto de propiedades sobrecargadas en el mismo nombre. Un
grupo de propiedades puede tener una expresión de destino asociada.
Un acceso de propiedad. Cada acceso de propiedad tiene un tipo asociado, es decir, el tipo de la
propiedad. Un acceso de propiedad puede tener una expresión de destino asociada.
Acceso enlazado en tiempo de ejecución, que representa un acceso de método o propiedad diferido hasta
el tiempo de ejecución. Un acceso enlazado en tiempo de ejecución puede tener una expresión de destino
asociada y una lista de argumentos de tipo asociada. El tipo de acceso enlazado en tiempo de ejecución
siempre es Object .
Un acceso de evento. Cada acceso a eventos tiene un tipo asociado, es decir, el tipo del evento. Un acceso
de evento puede tener una expresión de destino asociada. Un acceso de evento puede aparecer como el
primer argumento de RaiseEvent las AddHandler instrucciones, y RemoveHandler . En cualquier otro
contexto, una expresión clasificada como acceso de evento produce un error en tiempo de compilación.
Un literal de matriz, que representa los valores iniciales de una matriz cuyo tipo aún no se ha
determinado.
Hueco. Esto sucede cuando la expresión es una invocación de una subrutina o una expresión de operador
Await sin ningún resultado. Una expresión clasificada como void solo es válida en el contexto de una
instrucción de invocación o una instrucción Await.
Un valor predeterminado. Solo el literal Nothing produce esta clasificación.
El resultado final de una expresión suele ser un valor o una variable, con las otras categorías de expresiones que
funcionan como valores intermedios que solo se permiten en determinados contextos.
Tenga en cuenta que las expresiones cuyo tipo es un parámetro de tipo se pueden usar en instrucciones y
expresiones que requieren que el tipo de una expresión tenga ciertas características (como un tipo de referencia,
un tipo de valor, que deriva de algún tipo, etc.) si las restricciones impuestas en el parámetro de tipo satisfacen
esas características.
Reclasificación de expresiones
Normalmente, cuando se utiliza una expresión en un contexto que requiere una clasificación diferente de la de la
expresión, se produce un error en tiempo de compilación, por ejemplo, al intentar asignar un valor a un literal.
Sin embargo, en muchos casos es posible cambiar la clasificación de una expresión a través del proceso de
reclasificación.
Si la reclasificación se realiza correctamente, la reclasificación se juzga como ampliación o restricción. A menos
que se indique lo contrario, todas las reclasificaciones de esta lista se amplían.
Se pueden reclasificar los siguientes tipos de expresiones:
Una variable se puede reclasificar como un valor. Se obtiene el valor almacenado en la variable.
Un grupo de métodos se puede reclasificar como un valor. La expresión de grupo de métodos se
interpreta como una expresión de invocación con la expresión de destino asociada y la lista de
parámetros de tipo, y paréntesis vacíos (es decir, f se interpreta como f() y f(Of Integer) se
interpreta como f(Of Integer)() ). Esta reclasificación puede dar lugar a que la expresión se reclasifique
más como void.
Un puntero de método se puede reclasificar como un valor. Esta reclasificación solo puede producirse en
el contexto de una conversión donde se conoce el tipo de destino. La expresión de puntero de método se
interpreta como el argumento de una expresión de creación de instancias de delegado del tipo adecuado
con la lista de argumentos de tipo asociada. Por ejemplo:
Delegate Sub D(i As Integer)
Module Test
Sub F(i As Integer)
End Sub
Sub Main()
Dim del As D
Un método lambda Async o iterator solo se puede interpretar como argumento para una expresión de
construcción de delegado, si el delegado no tiene ningún parámetro ByRef.
Si la conversión de cualquiera de los tipos de parámetro del delegado en los tipos de parámetro lambda
correspondientes es una conversión de restricción, la reclasificación se juzga como de restricción. en caso
contrario, se amplía.
Nota. La traslación exacta entre los métodos lambda y los árboles de expresión no se puede corregir
entre las versiones del compilador y está fuera del ámbito de esta especificación. En el caso de Microsoft
Visual Basic 11,0, todas las expresiones lambda se pueden convertir en árboles de expresión sujetos a las
siguientes restricciones: (1) 1. Solo las expresiones lambda de una sola línea sin parámetros ByRef se
pueden convertir en árboles de expresión. De las lambdas de una sola línea Sub , solo las instrucciones
de invocación se pueden convertir en árboles de expresión. (2) las expresiones de tipo anónimo no se
pueden convertir en árboles de expresión si se usa un inicializador de campo anterior para inicializar un
inicializador de campo posterior, por ejemplo, New With {.a=1, .b=.a} . (3) las expresiones de
inicializador de objeto no se pueden convertir en árboles de expresión si un miembro del objeto actual
que se está inicializando se usa en uno de los inicializadores de campo, por ejemplo,
New C1 With {.a=1, .b=.Method1()} . (4) las expresiones de creación de matrices multidimensionales solo
se pueden convertir en árboles de expresión si declaran explícitamente su tipo de elemento. (5) las
expresiones de enlace en tiempo de ejecución no se pueden convertir en árboles de expresión. (6) cuando
se pasa una variable o un campo ByRef a una expresión de invocación pero no tiene exactamente el
mismo tipo que el parámetro ByRef, o cuando se pasa una propiedad ByRef, la semántica de VB normal es
que se pasa una copia del argumento ByRef y su valor final se vuelve a copiar en la variable, el campo o la
propiedad. En los árboles de expresión, no se produce la copia atrás. (7) todas estas restricciones se
aplican también a las expresiones lambda anidadas.
Si no se conoce el tipo de destino, el método lambda se interpreta como el argumento de una expresión
de creación de instancias de delegado de un tipo delegado anónimo con la misma firma del método
lambda. Si se usa una semántica estricta y se omite el tipo de cualquiera de los parámetros, se produce
un error en tiempo de compilación; de lo contrario, Object se sustituye por cualquier tipo de parámetro
que falte. Por ejemplo:
Module Test
Sub Main()
' Type of x will be equivalent to Func(Of Object, Object, Object)
Dim x = Function(a, b) a + b
' x Is GetType(Double(,,))
Dim x = { { { 1, 2.0 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } }.GetType()
' y Is GetType(Integer())
Dim y = { 1, 2, 3 }.GetType()
' z Is GetType(Object())
Dim z = { 1, "2" }.GetType()
Nota. Hay un pequeño cambio en el comportamiento entre la versión 9,0 y la versión 10,0 del idioma.
Antes de 10,0, los inicializadores de elementos de matriz no afectaban a la inferencia de tipos de variables
locales y ahora lo hacen. Por lo tanto, se habrían Dim a() = { 1, 2, 3 } deducido Object() como el tipo
de a en la versión 9,0 del lenguaje y Integer() en la versión 10,0.
A continuación, la reclasificación reinterpreta el literal de matriz como una expresión de creación de
matriz. Por lo tanto, los ejemplos son:
Dim x As Double = { 1, 2, 3, 4 }
Dim y = { "a", "b" }
son equivalentes a:
No se puede reclasificar una expresión de espacio de nombres, expresión de tipo, expresión de acceso a eventos
o expresión void. Se pueden realizar varias reclasificaciones al mismo tiempo. Por ejemplo:
Module Test
Sub F(i As Integer)
End Sub
Sub Main()
F(P)
End Property
End Module
Expresiones constantes
Una expresión constante es una expresión cuyo valor se puede evaluar por completo en tiempo de compilación.
ConstantExpression
: Expression
;
El tipo de una expresión constante puede ser Byte , SByte , UShort , Short , UInteger , Integer , ULong ,
Long , Char , Single , Double , Decimal , Date , Boolean , String , Object o cualquier tipo de
enumeración. Las siguientes construcciones se permiten en expresiones constantes:
Literales (incluido Nothing ).
Referencias a miembros de tipo constante o variables locales constantes.
Referencias a miembros de tipos de enumeración.
Subexpresiones entre paréntesis.
Expresiones de coerción, siempre que el tipo de destino sea uno de los tipos enumerados anteriormente.
Las conversiones de y de String son una excepción a esta regla y solo se permiten en valores NULL, ya
que String las conversiones siempre se realizan en la referencia cultural actual del entorno de ejecución
en tiempo de ejecución. Tenga en cuenta que las expresiones de coerción constante solo pueden usar
conversiones intrínsecas.
Los + - Not operadores unarios y, siempre y cuando el operando y el resultado sean de un tipo
enumerado anteriormente.
Los + operadores binarios,,,,,,,,,,,,,,, * ^ Mod / \ << >> & And ,,,, Or Xor AndAlso
- OrElse
= < > <> <= y => , siempre que cada operando y resultado sea de un tipo enumerado
anteriormente.
Operador condicional si, siempre y cuando cada operando y resultado sea de un tipo enumerado
anteriormente.
Las siguientes funciones en tiempo de ejecución: Microsoft.VisualBasic.Strings.ChrW ;
Microsoft.VisualBasic.Strings.Chr si el valor constante está entre 0 y 128;
Microsoft.VisualBasic.Strings.AscW si la cadena de constante no está vacía;
Microsoft.VisualBasic.Strings.Asc si la cadena de constante no está vacía.
Las expresiones constantes de un tipo entero ( ULong , Long ,, UInteger Integer , UShort , Short , SByte o
Byte ) se pueden convertir implícitamente a un tipo entero más estrecho, y las expresiones constantes de tipo
se Double pueden convertir implícitamente a Single , siempre que el valor de la expresión constante esté
dentro del intervalo del tipo de destino. Estas conversiones de restricción se permiten independientemente de si
se usan semánticas permisivas o estrictas.
En general, los accesos enlazados en tiempo de ejecución se resuelven en tiempo de ejecución buscando el
identificador en el tipo en tiempo de ejecución real de la expresión. Si se produce un error en la búsqueda de
miembros enlazados en tiempo de ejecución en tiempo de ejecución, System.MissingMemberException se produce
una excepción. Dado que la búsqueda de miembros enlazados en tiempo de ejecución se realiza únicamente
fuera del tipo en tiempo de ejecución de la expresión de destino asociada, el tipo de tiempo de ejecución de un
objeto nunca es una interfaz. Por lo tanto, no es posible tener acceso a los miembros de interfaz en una
expresión de acceso de miembro enlazada en tiempo de ejecución.
Los argumentos de acceso a miembros enlazados en tiempo de ejecución se evalúan en el orden en que
aparecen en la expresión de acceso a miembros: no en el orden en el que se declaran los parámetros en el
miembro enlazado en tiempo de ejecución. En el ejemplo siguiente se muestra esta diferencia:
Class C
Public Sub f(ByVal x As Integer, ByVal y As Integer)
End Sub
End Class
Module Module1
Sub Main()
Console.Write("Early-bound: ")
Dim c As C = New C
c.f(y:=t("y"), x:=t("x"))
Early-bound: xy
Late-bound: yx
Dado que la resolución de sobrecarga enlazada en tiempo de ejecución se realiza en el tipo en tiempo de
ejecución de los argumentos, es posible que una expresión genere resultados diferentes en función de si se
evalúa en tiempo de compilación o en tiempo de ejecución. En el ejemplo siguiente se muestra esta diferencia:
Class Base
End Class
Class Derived
Inherits Base
End Class
Module Test
Sub F(b As Base)
Console.WriteLine("F(Base)")
End Sub
Sub Main()
Dim b As Base = New Derived()
Dim o As Object = b
F(b)
F(o)
End Sub
End Module
Expresiones simples
Las expresiones simples son literales, expresiones entre paréntesis, expresiones de instancia o expresiones de
nombre simple.
SimpleExpression
: LiteralExpression
| ParenthesizedExpression
| InstanceExpression
| SimpleNameExpression
| AddressOfExpression
;
Expresiones literales
Las expresiones literales se evalúan como el valor representado por el literal. Una expresión literal se clasifica
como un valor, excepto para el literal Nothing , que se clasifica como valor predeterminado.
LiteralExpression
: Literal
;
ParenthesizedExpression
: OpenParenthesis Expression CloseParenthesis
;
Expresiones de instancia
Una expresión de instancia es la palabra clave Me . Solo se puede usar dentro del cuerpo de un método no
compartido, un constructor o un descriptor de acceso de propiedad. Se clasifica como un valor. La palabra clave
Me representa la instancia del tipo que contiene el método o el descriptor de acceso de la propiedad que se está
ejecutando. Si un constructor invoca explícitamente otro constructor ( constructoresde sección), Me no se puede
usar hasta después de la llamada al constructor, porque la instancia aún no se ha construido.
InstanceExpression
: 'Me'
;
El nombre se resuelve y se clasifica con las siguientes "reglas de resolución de nombres simples":
1. A partir del bloque de inclusión inmediato y continuando con cada bloque exterior envolvente (si existe),
si el identificador coincide con el nombre de una variable local, una variable estática, un parámetro de
tipo de método o una variable local, el identificador se refiere a la entidad coincidente.
Si el identificador coincide con una variable local, una variable estática o una variable local y se
proporcionó una lista de argumentos de tipo, se produce un error en tiempo de compilación. Si el
identificador coincide con un parámetro de tipo de método y se proporcionó una lista de argumentos de
tipo, no se produce ninguna coincidencia y la resolución continúa. Si el identificador coincide con una
variable local, la variable local que coincide con la función implícita o el Get descriptor de acceso
devuelven la variable local, y la expresión forma parte de una expresión de invocación, una instrucción de
invocación o una AddressOf expresión, no se produce ninguna coincidencia y la resolución continúa.
La expresión se clasifica como una variable si es una variable local, una variable estática o un parámetro.
La expresión se clasifica como un tipo si es un parámetro de tipo de método. La expresión se clasifica
como un valor si es una constante local.
2. Para cada tipo anidado que contenga la expresión, empezando desde el interior y pasando al exterior, si
una búsqueda del identificador en el tipo produce una coincidencia con un miembro accesible:
u. Si el miembro de tipo coincidente es un parámetro de tipo, el resultado se clasifica como un tipo y es
el parámetro de tipo coincidente. Si se proporcionó una lista de argumentos de tipo, no se produce
ninguna coincidencia y la resolución continúa.
v. De lo contrario, si el tipo es el tipo de inclusión inmediato y la búsqueda identifica un miembro de tipo
no compartido, el resultado es el mismo que un acceso de miembro del formulario Me.E(Of A) ,
donde E es el identificador y A es la lista de argumentos de tipo, si existe.
w. De lo contrario, el resultado es exactamente el mismo que un acceso de miembro del formulario
T.E(Of A) , donde T es el tipo que contiene el miembro coincidente, E es el identificador y A es la
lista de argumentos de tipo, si existe. En este caso, es un error que el identificador haga referencia a un
miembro no compartido.
3. Para cada espacio de nombres anidado, empezando desde el interior y pasando al espacio de nombres
más externo, haga lo siguiente:
ae. Si el espacio de nombres contiene un tipo accesible con el nombre especificado y tiene el mismo
número de parámetros de tipo que se proporcionó en la lista de argumentos de tipo, si lo hubiera, el
identificador hace referencia a ese tipo y se clasifica como un tipo.
af. De lo contrario, si no se proporcionó ninguna lista de argumentos de tipo y el espacio de nombres
contiene un miembro de espacio de nombres con el nombre especificado, el identificador hace
referencia a ese espacio de nombres y se clasifica como un espacio de nombres.
ag. De lo contrario, si el espacio de nombres contiene uno o más módulos estándar accesibles, y una
búsqueda de nombre de miembro del identificador produce una coincidencia accesible en
exactamente un módulo estándar, el resultado es exactamente el mismo que un acceso de miembro
del formulario M.E(Of A) , donde M es el módulo estándar que contiene el miembro coincidente, E
es el identificador y A es la lista de argumentos de tipo, si existe. Si el identificador coincide con
miembros de tipo accesible en más de un módulo estándar, se produce un error en tiempo de
compilación.
4. Si el archivo de origen tiene uno o varios alias de importación y el identificador coincide con el nombre
de uno de ellos, el identificador hace referencia a ese espacio de nombres o tipo. Si se proporciona una
lista de argumentos de tipo, se produce un error en tiempo de compilación.
5. Si el archivo de código fuente que contiene la referencia de nombre tiene una o más importaciones:
ay. Si el identificador coincide exactamente en una importación, el nombre de un tipo accesible con el
mismo número de parámetros de tipo que se proporcionó en la lista de argumentos de tipo, si existe,
o un miembro de tipo, el identificador hace referencia a ese tipo o miembro de tipo. Si el identificador
coincide con más de una importación, el nombre de un tipo accesible con el mismo número de
parámetros de tipo que se proporcionó en la lista de argumentos de tipo, si existe, o un miembro de
tipo accesible, se produce un error en tiempo de compilación.
az. De lo contrario, si no se proporcionó ninguna lista de argumentos de tipo y el identificador coincide
exactamente con una importación el nombre de un espacio de nombres con tipos accesibles, el
identificador hace referencia a ese espacio de nombres. Si no se proporcionó ninguna lista de
argumentos de tipo y el identificador coincide con más de una importación el nombre de un espacio
de nombres con tipos accesibles, se produce un error en tiempo de compilación.
ba. De lo contrario, si las importaciones contienen uno o más módulos estándar accesibles, y una
búsqueda de nombre de miembro del identificador produce una coincidencia accesible en
exactamente un módulo estándar, el resultado es exactamente el mismo que un acceso de miembro
del formulario M.E(Of A) , donde M es el módulo estándar que contiene el miembro coincidente, E
es el identificador y A es la lista de argumentos de tipo, si existe. Si el identificador coincide con
miembros de tipo accesible en más de un módulo estándar, se produce un error en tiempo de
compilación.
6. Si el entorno de compilación define uno o varios alias de importación y el identificador coincide con el
nombre de uno de ellos, el identificador hace referencia a ese espacio de nombres o tipo. Si se
proporciona una lista de argumentos de tipo, se produce un error en tiempo de compilación.
7. Si el entorno de compilación define una o varias importaciones:
bs. Si el identificador coincide exactamente en una importación, el nombre de un tipo accesible con el
mismo número de parámetros de tipo que se proporcionó en la lista de argumentos de tipo, si existe,
o un miembro de tipo, el identificador hace referencia a ese tipo o miembro de tipo. Si el identificador
coincide con más de una importación, el nombre de un tipo accesible con el mismo número de
parámetros de tipo que se proporcionó en la lista de argumentos de tipo, si existe, o un miembro de
tipo, se produce un error en tiempo de compilación.
bt. De lo contrario, si no se proporcionó ninguna lista de argumentos de tipo y el identificador coincide
exactamente con una importación el nombre de un espacio de nombres con tipos accesibles, el
identificador hace referencia a ese espacio de nombres. Si no se proporcionó ninguna lista de
argumentos de tipo y el identificador coincide con más de una importación el nombre de un espacio
de nombres con tipos accesibles, se produce un error en tiempo de compilación.
bu. De lo contrario, si las importaciones contienen uno o más módulos estándar accesibles, y una
búsqueda de nombre de miembro del identificador produce una coincidencia accesible en
exactamente un módulo estándar, el resultado es exactamente el mismo que un acceso de miembro
del formulario M.E(Of A) , donde M es el módulo estándar que contiene el miembro coincidente, E
es el identificador y A es la lista de argumentos de tipo, si existe. Si el identificador coincide con
miembros de tipo accesible en más de un módulo estándar, se produce un error en tiempo de
compilación.
8. De lo contrario, el nombre proporcionado por el identificador es indefinido.
Una expresión de nombre simple que no está definida es un error en tiempo de compilación.
Normalmente, un nombre solo puede aparecer una vez en un espacio de nombres determinado. Sin embargo,
dado que los espacios de nombres se pueden declarar en varios ensamblados .NET, es posible tener una
situación en la que dos ensamblados definen un tipo con el mismo nombre completo. En ese caso, se prefiere
un tipo declarado en el conjunto actual de archivos de código fuente a un tipo declarado en un ensamblado .NET
externo. De lo contrario, el nombre es ambiguo y no hay ninguna manera de eliminar la ambigüedad del
nombre.
Expresiones de AddressOf
Una AddressOf expresión se utiliza para generar un puntero de método. La expresión consta de la AddressOf
palabra clave y una expresión que se debe clasificar como un grupo de métodos o un acceso enlazado en
tiempo de ejecución. El grupo de métodos no puede hacer referencia a constructores.
El resultado se clasifica como un puntero de método, con la misma expresión de destino asociada y la misma
lista de argumentos de tipo (si existe) que el grupo de métodos.
AddressOfExpression
: 'AddressOf' Expression
;
Expresiones de tipo
Una expresión de tipo es una expresión, una expresión, una expresión GetType TypeOf...Is Is o una
GetXmlNamespace expresión.
TypeExpression
: GetTypeExpression
| TypeOfIsExpression
| IsExpression
| GetXmlNamespaceExpression
;
Expresiones GetType
Una GetType expresión consta de la palabra clave GetType y el nombre de un tipo.
GetTypeExpression
: 'GetType' OpenParenthesis GetTypeTypeName CloseParenthesis
;
GetTypeTypeName
: TypeName
| QualifiedOpenTypeName
;
QualifiedOpenTypeName
: Identifier TypeArityList? (Period IdentifierOrKeyword TypeArityList?)*
| 'Global' Period IdentifierOrKeyword TypeArityList?
(Period IdentifierOrKeyword TypeArityList?)*
;
TypeArityList
: OpenParenthesis 'Of' CommaList? CloseParenthesis
;
CommaList
: Comma Comma*
;
Una GetType expresión se clasifica como un valor y su valor es la clase Reflection ( System.Type ) que
representa su GetTypeTypeName. Si GetTypeTypeName es un parámetro de tipo, la expresión devolverá el
System.Type objeto que se corresponde con el argumento de tipo proporcionado para el parámetro de tipo en
tiempo de ejecución.
El GetTypeTypeName es especial de dos maneras:
Se permite que sea System.Void , el único lugar en el lenguaje en el que se puede hacer referencia a este
nombre de tipo.
Puede ser un tipo genérico construido con los argumentos de tipo omitidos. Esto permite GetType que la
expresión devuelva el System.Type objeto que se corresponde con el tipo genérico en sí.
En el ejemplo siguiente se muestra la GetType expresión:
Module Test
Sub Main()
Dim t As Type() = { GetType(Integer), GetType(System.Int32), _
GetType(String), GetType(Double()) }
Dim i As Integer
For i = 0 To t.Length - 1
Console.WriteLine(t(i).Name)
Next i
End Sub
End Module
Int32
Int32
String
Double[]
Typeof... Expresiones is
Una TypeOf...Is expresión se usa para comprobar si el tipo en tiempo de ejecución de un valor es compatible
con un tipo determinado. El primer operando debe estar clasificado como un valor, no puede ser un método
lambda reclasificado y debe ser de un tipo de referencia o de un tipo de parámetro de tipo sin restricciones. El
segundo operando debe ser un nombre de tipo. El resultado de la expresión se clasifica como un valor y es un
Boolean valor. La expresión se evalúa como True si el tipo en tiempo de ejecución del operando tiene una
conversión de identidad, valor predeterminado, referencia, matriz, tipo de valor o parámetro de tipo al tipo; de
False lo contrario,. Se produce un error en tiempo de compilación si no existe ninguna conversión entre el tipo
de la expresión y el tipo específico.
TypeOfIsExpression
: 'TypeOf' Expression 'Is' LineTerminator? TypeName
;
Expresiones is
Una Is IsNot expresión o se utiliza para realizar una comparación de igualdad de referencia.
IsExpression
: Expression 'Is' LineTerminator? Expression
| Expression 'IsNot' LineTerminator? Expression
;
Cada expresión debe estar clasificada como un valor y el tipo de cada expresión debe ser un tipo de referencia,
un tipo de parámetro de tipo sin restricciones o un tipo de valor que acepte valores NULL. Sin embargo, si el
tipo de una expresión es un tipo de parámetro de tipo sin restricciones o un tipo de valor que acepta valores
NULL, la otra expresión debe ser el literal Nothing .
El resultado se clasifica como un valor y se escribe como Boolean . Una Is operación se evalúa como True si
ambos valores hacen referencia a la misma instancia o ambos valores son Nothing , o de False lo contrario,.
Una IsNot operación se evalúa como False si ambos valores hacen referencia a la misma instancia o ambos
valores son Nothing , o de True lo contrario,.
Expresiones GetXmlNamespace
Una GetXmlNamespace expresión consta de la palabra clave GetXmlNamespace y el nombre de un espacio de
nombres XML declarado por el entorno de compilación o el archivo de código fuente.
GetXmlNamespaceExpression
: 'GetXmlNamespace' OpenParenthesis XMLNamespaceName? CloseParenthesis
;
Imports <xmlns:db="http://example.org/database">
Module Test
Sub Main()
Dim db = GetXmlNamespace(db)
Todo lo que se encuentra entre paréntesis se considera parte del nombre del espacio de nombres, por lo que se
aplican las reglas XML en torno a elementos como espacio en blanco. Por ejemplo:
Imports <xmlns:db-ns="http://example.org/database">
Module Test
Sub Main()
' OK.
Dim db3 = GetXmlNamespace(db-ns)
End Sub
End Module
También se puede omitir la expresión del espacio de nombres XML, en cuyo caso la expresión devuelve el objeto
que representa el espacio de nombres XML predeterminado.
MemberAccessBase
: Expression
| NonArrayTypeName
| 'Global'
| 'MyClass'
| 'MyBase'
;
Un acceso de miembro del formulario E.I(Of A) , donde E es una expresión, un nombre de tipo que no es de
matriz, la palabra clave Global o se omite y I es un identificador con una lista de argumentos de tipo opcional
A , se evalúa y se clasifica como sigue:
Class Derived
Inherits Base
Class MoreDerived
Inherits Derived
Module Test
Sub Main()
Dim x As MoreDerived = new MoreDerived()
x.F()
x.G()
x.H()
End Sub
End Module
MoreDerived.F
Derived.F
Derived.F
Cuando una expresión de acceso a miembros comienza con la palabra clave Global , la palabra clave
representa el espacio de nombres sin nombre más externo, que es útil en situaciones en las que una declaración
sombrea un espacio de nombres envolvente. La Global palabra clave permite "escapar" en el espacio de
nombres más externo en esa situación. Por ejemplo:
Class System
End Class
Module Test
Sub Main()
' Error: Class System does not contain Console
System.Console.WriteLine("Hello, world!")
En el ejemplo anterior, la primera llamada al método no es válida porque el identificador se System enlaza a la
clase System , no al espacio de nombres System . La única manera de tener acceso al System espacio de
nombres es usar Global para escapar al espacio de nombres más externo.
Si el miembro al que se tiene acceso es compartido, cualquier expresión del lado izquierdo del período es
superflua y no se evalúa a menos que el acceso a miembros se realice en tiempo de ejecución. Por ejemplo,
considere el siguiente código:
Class C
Public Shared F As Integer = 10
End Class
Module Test
Public Function ReturnC() As C
Console.WriteLine("Returning a new instance of C.")
Return New C()
End Function
Imprime The value of F is: 10 porque ReturnC no es necesario llamar a la función para proporcionar una
instancia de C para tener acceso al miembro compartido F .
Nombres de tipo y miembro idénticos
No es raro asignar nombres a los miembros con el mismo nombre que su tipo. En ese caso, se puede producir
una ocultación de nombres no adecuada:
Enum Color
Red
Green
Yellow
End Enum
Class Test
ReadOnly Property Color() As Color
Get
Return Color.Red
End Get
End Property
En el ejemplo anterior, el nombre simple Color en DefaultColor se enlaza a la propiedad de instancia en lugar
de al tipo. Dado que no se puede hacer referencia a un miembro de instancia en un miembro compartido,
normalmente sería un error.
Sin embargo, una regla especial permite el acceso al tipo en este caso. Si la expresión base de una expresión de
acceso a miembros es un nombre simple y se enlaza a una constante, un campo, una propiedad, una variable
local o un parámetro cuyo tipo tiene el mismo nombre, la expresión base puede hacer referencia al miembro o
al tipo. Esto nunca puede producir ambigüedad, ya que los miembros a los que se puede tener acceso desde
cualquiera de ellos son los mismos.
Instancias predeterminadas
En algunas situaciones, las clases derivadas de una clase base común suelen tener o siempre una sola instancia.
Por ejemplo, la mayoría de las ventanas que se muestran en una interfaz de usuario solo tienen una instancia
que se muestra en la pantalla en cualquier momento. Para simplificar el trabajo con estos tipos de clases, Visual
Basic puede generar automáticamente las instancias predeterminadas de las clases que proporcionan una única
instancia de a la que se hace referencia fácilmente para cada clase.
Las instancias predeterminadas siempre se crean para una familia de tipos en lugar de para un tipo
determinado. Por lo tanto, en lugar de crear una instancia predeterminada para una clase Form1 que deriva de
form, se crean instancias predeterminadas para todas las clases derivadas de Form. Esto significa que cada clase
individual que deriva de la clase base no tiene que estar marcada especialmente para tener una instancia
predeterminada.
La instancia predeterminada de una clase se representa mediante una propiedad generada por el compilador
que devuelve la instancia predeterminada de esa clase. La propiedad generada como miembro de una clase
denominada clase de grupo que administra la asignación y destrucción de las instancias predeterminadas para
todas las clases derivadas de la clase base concreta. Por ejemplo, todas las propiedades de instancia
predeterminadas de las clases derivadas de Form se pueden recopilar en la MyForms clase. Si la expresión
devuelve una instancia de la clase Group My.Forms , el código siguiente obtiene acceso a las instancias
predeterminadas de las clases derivadas Form1 y Form2 :
Class Form1
Inherits Form
Public x As Integer
End Class
Class Form2
Inherits Form
Public y As Integer
End Class
Module Main
Sub Main()
My.Forms.Form1.x = 10
Console.WriteLine(My.Forms.Form2.y)
End Sub
End Module
Las instancias predeterminadas no se crearán hasta la primera referencia a ellas. la captura de la propiedad que
representa la instancia predeterminada hace que se cree la instancia predeterminada si aún no se ha creado o se
ha establecido en Nothing . Para permitir la comprobación de la existencia de una instancia predeterminada,
cuando una instancia predeterminada es el destino de Is un IsNot operador OR, no se creará la instancia
predeterminada. Por lo tanto, es posible probar si una instancia predeterminada es Nothing o alguna otra
referencia sin provocar la creación de la instancia predeterminada.
Las instancias predeterminadas están diseñadas para facilitar la referencia a la instancia predeterminada desde
fuera de la clase que tiene la instancia predeterminada. El uso de una instancia predeterminada de dentro de
una clase que la define podría causar confusión en lo que se refiere a la instancia, es decir, la instancia
predeterminada o la instancia actual. Por ejemplo, el código siguiente modifica únicamente el valor x de la
instancia predeterminada, aunque se llama desde otra instancia. Por lo tanto, el código imprimiría el valor 5 en
lugar de 10 :
Class Form1
Inherits Form
Public x As Integer = 5
Module Main
Sub Main()
Dim f As Form1 = New Form1()
f.ChangeX()
Console.WriteLine(f.x)
End Sub
End Module
Para evitar este tipo de confusión, no es válido hacer referencia a una instancia predeterminada desde dentro de
un método de instancia del tipo de la instancia predeterminada.
Instancias predeterminadas y nombres de tipo
También se puede tener acceso a una instancia predeterminada directamente a través de su nombre de tipo. En
este caso, en cualquier contexto de expresión donde no se permite el nombre de tipo, la expresión E , donde E
representa el nombre completo de la clase con una instancia predeterminada, se cambia a E' , donde E'
representa una expresión que captura la propiedad de instancia predeterminada. Por ejemplo, si las instancias
predeterminadas para las clases derivadas de Form permiten el acceso a la instancia predeterminada mediante
el nombre de tipo, el código siguiente es equivalente al código del ejemplo anterior:
Module Main
Sub Main()
Form1.x = 10
Console.WriteLine(Form2.y)
End Sub
End Module
Esto también significa que una instancia predeterminada a la que se puede tener acceso a través del nombre de
su tipo también se puede asignar a través del nombre de tipo. Por ejemplo, el código siguiente establece la
instancia predeterminada de Form1 en Nothing :
Module Main
Sub Main()
Form1 = Nothing
End Sub
End Module
Tenga en cuenta que el significado de E.I era E una clase y I representa un miembro compartido que no
cambia. Este tipo de expresión sigue teniendo acceso al miembro compartido directamente fuera de la instancia
de clase y no hace referencia a la instancia predeterminada.
Clases de grupo
El Microsoft.VisualBasic.MyGroupCollectionAttribute atributo indica la clase de grupo de una familia de
instancias predeterminadas. El atributo tiene cuatro parámetros:
El parámetro TypeToCollect especifica la clase base para el grupo. Todas las clases que se recrean sin
parámetros de tipo abierto que se derivan de un tipo con este nombre (independientemente de los
parámetros de tipo) tendrán automáticamente una instancia predeterminada.
El parámetro CreateInstanceMethodName especifica el método que se va a llamar en la clase Group para
crear una nueva instancia en una propiedad de instancia predeterminada.
El parámetro DisposeInstanceMethodName especifica el método que se va a llamar en la clase de grupo
para desechar una propiedad de instancia predeterminada si se asigna el valor a la propiedad de
instancia predeterminada Nothing .
El parámetro DefaultInstanceAlias especifica la expresión E' que se va a sustituir por el nombre de
clase si se puede obtener acceso a las instancias predeterminadas directamente a través de su nombre de
tipo. Si este parámetro es Nothing o una cadena vacía, no se puede tener acceso a las instancias
predeterminadas de este tipo de grupo directamente a través del nombre de su tipo. (Nota. En todas las
implementaciones actuales del lenguaje Visual Basic, DefaultInstanceAlias se omite el parámetro,
excepto en el código proporcionado por el compilador.
Se pueden recopilar varios tipos en el mismo grupo separando los nombres de los tipos y métodos en los tres
primeros parámetros mediante comas. Debe haber el mismo número de elementos en cada parámetro y los
elementos de la lista coinciden en orden. Por ejemplo, la siguiente declaración de atributo recopila los tipos que
derivan de C1 , C2 o C3 en un único grupo:
<Microsoft.VisualBasic.MyGroupCollection("Form", "Create", _
"Dispose", "My.Forms")> _
Public NotInheritable Class MyForms
Private Shared Function Create(Of T As {New, Form}) _
(Instance As T) As T
If Instance Is Nothing Then
Return New T()
Else
Return Instance
End If
End Function
Si un archivo de código fuente declara una clase derivada Form1 , la clase de grupo generada sería equivalente
a:
<Microsoft.VisualBasic.MyGroupCollection("Form", "Create", _
"Dispose", "My.Forms")> _
Public NotInheritable Class MyForms
Private Shared Function Create(Of T As {New, Form}) _
(Instance As T) As T
If Instance Is Nothing Then
Return New T()
Else
Return Instance
End If
End Function
Imports System.Runtime.CompilerServices
Class C1
End Class
Namespace N1
Module N1C1Extensions
<Extension> _
Sub M1(c As C1, x As Integer)
End Sub
End Module
End Namespace
Namespace N1.N2
Module N2C1Extensions
<Extension> _
Sub M1(c As C1, y As Double)
End Sub
End Module
End Namespace
Namespace N1.N2.N3
Module Test
Sub Main()
Dim x As New C1()
Module Ext1
<Extension> _
Sub M(x As Integer, y As Integer)
End Sub
End Module
Module Ext2
<Extension> _
Sub M(x As Integer, y As Double)
End Sub
End Module
Module Main
Sub Test()
Dim v As Integer = 10
Además de quitar el primer parámetro del método de extensión, currificación también quita cualquier
parámetro de tipo de método que forme parte del tipo del primer parámetro. Cuando currificación un método
de extensión con el parámetro de tipo de método, la inferencia de tipos se aplica al primer parámetro y el
resultado se fija para todos los parámetros de tipo que se deducen. Si se produce un error en la inferencia de
tipos, se omite el método. Por ejemplo:
Imports System.Runtime.CompilerServices
Module Ext1
<Extension> _
Sub M(Of T, U)(x As T, y As U)
End Sub
End Module
Module Ext2
<Extension> _
Sub M(Of T)(x As T, y As T)
End Sub
End Module
Module Main
Sub Test()
Dim v As Integer = 10
Imports System.Runtime.CompilerServices
Module Ext1
<Extension> _
Sub M1(Of T As Structure)(x As T, y As Integer)
End Sub
<Extension> _
Sub M2(Of T As U, U)(x As T, y As U)
End Sub
End Module
Module Main
Sub Test()
Dim s As String = "abc"
Nota. Uno de los principales motivos para realizar currificación de métodos de extensión es que permite que las
expresiones de consulta infieran el tipo de iteración antes de evaluar los argumentos de un método de patrón
de consulta. Dado que la mayoría de los métodos de patrón de consulta toman expresiones lambda, que
requieren la inferencia de tipos, esto simplifica en gran medida el proceso de evaluación de una expresión de
consulta.
A diferencia de la herencia de interfaz normal, los métodos de extensión que amplían dos interfaces que no se
relacionan entre sí están disponibles, siempre y cuando no tengan la misma firma currificada:
Imports System.Runtime.CompilerServices
Interface I1
End Interface
Interface I2
End Interface
Class C1
Implements I1, I2
End Class
Module I1Ext
<Extension> _
Sub M1(i As I1, x As Integer)
End Sub
<Extension> _
Sub M2(i As I1, x As Integer)
End Sub
End Module
Module I2Ext
<Extension> _
Sub M1(i As I2, x As Integer)
End Sub
<Extension> _
Sub M2(I As I2, x As Double)
End Sub
End Module
Module Main
Sub Test()
Dim c As New C1()
Por último, es importante recordar que los métodos de extensión no se tienen en cuenta al realizar el enlace en
tiempo de ejecución:
Module Test
Sub Main()
Dim o As Object = ...
El tipo de la expresión debe tener una propiedad predeterminada indizada por un único String parámetro. La
expresión de acceso a miembros E!I de diccionario se transforma en la expresión E.D("I") , donde D es la
propiedad predeterminada de E . Por ejemplo:
Class Keys
Public ReadOnly Default Property Item(s As String) As Integer
Get
Return 10
End Get
End Property
End Class
Module Test
Sub Main()
Dim x As Keys = new Keys()
Dim y As Integer
' The two statements are equivalent.
y = x!abc
y = x("abc")
End Sub
End Module
Si se especifica un signo de exclamación sin expresión, se asume la expresión de la instrucción que contiene
inmediatamente With . Si no hay ninguna instrucción contenedora With , se produce un error en tiempo de
compilación.
Expresiones de invocación
Una expresión de invocación consta de un destino de invocación y una lista de argumentos opcional.
InvocationExpression
: Expression ( OpenParenthesis ArgumentList? CloseParenthesis )?
;
ArgumentList
: PositionalArgumentList
| PositionalArgumentList Comma NamedArgumentList
| NamedArgumentList
;
PositionalArgumentList
: Expression? ( Comma Expression? )*
;
NamedArgumentList
: IdentifierOrKeyword ColonEquals Expression
( Comma IdentifierOrKeyword ColonEquals Expression )*
;
La expresión de destino debe clasificarse como un grupo de métodos o un valor cuyo tipo sea un tipo de
delegado. Si la expresión de destino es un valor cuyo tipo es un tipo de delegado, el destino de la expresión de
invocación se convierte en el grupo de métodos para el Invoke miembro del tipo de delegado y la expresión de
destino se convierte en la expresión de destino asociada del grupo de métodos.
Una lista de argumentos tiene dos secciones: argumentos posicionales y argumentos con nombre. Los
argumentos posicionales son expresiones y deben preceder a cualquier argumento con nombre. Los
argumentos con nombre comienzan con un identificador que puede coincidir con palabras clave, seguido de
:= y una expresión.
Si el grupo de métodos solo contiene un método accesible, incluidos los métodos de instancia y de extensión, y
ese método no toma ningún argumento y es una función, el grupo de métodos se interpreta como una
expresión de invocación con una lista de argumentos vacía y el resultado se usa como destino de una expresión
de invocación con las listas de argumentos proporcionadas. Por ejemplo:
Class C1
Function M1() As Integer()
Return New Integer() { 1, 2, 3 }
End Sub
End Class
Module Test
Sub Main()
Dim c As New C1()
' Prints 3
Console.WriteLine(c.M1(2))
End Sub
End Module
De lo contrario, se aplica la resolución de sobrecarga a los métodos para elegir el método más aplicable para las
listas de argumentos dadas. Si el método más aplicable es una función, el resultado de la expresión de
invocación se clasifica como un valor escrito como el tipo de valor devuelto de la función. Si el método más
aplicable es una subrutina, el resultado se clasifica como void. Si el método más aplicable es un método parcial
que no tiene cuerpo, se omite la expresión de invocación y el resultado se clasifica como void.
En el caso de una expresión de invocación enlazada en tiempo de compilación, los argumentos se evalúan en el
orden en que se declaran los parámetros correspondientes en el método de destino. En el caso de una expresión
de acceso a miembros enlazados en tiempo de ejecución, se evalúan en el orden en que aparecen en la
expresión de acceso a miembros: vea la sección expresiones enlazadas en tiempo de ejecución.
Expresiones de índice
Una expresión de índice da como resultado un elemento de matriz o reclasifica un grupo de propiedades en un
acceso de propiedad. Una expresión de índice consta de, en orden, una expresión, un paréntesis de apertura, una
lista de argumentos de índice y un paréntesis de cierre.
IndexExpression
: Expression OpenParenthesis ArgumentList? CloseParenthesis
;
El destino de la expresión de índice debe clasificarse como un grupo de propiedades o un valor. Una expresión
de índice se procesa de la siguiente manera:
Si la expresión de destino se clasifica como un valor y su tipo no es un tipo de matriz, Object , o
System.Array , el tipo debe tener una propiedad predeterminada. El índice se realiza en un grupo de
propiedades que representa todas las propiedades predeterminadas del tipo. Aunque no es válido
declarar una propiedad predeterminada sin parámetros en Visual Basic, otros lenguajes pueden permitir
declarar dicha propiedad. Por consiguiente, se permite indizar una propiedad sin argumentos.
Si la expresión da como resultado un valor de un tipo de matriz, el número de argumentos de la lista de
argumentos debe ser el mismo que el rango del tipo de matriz y no puede incluir argumentos con
nombre. Si alguno de los índices no es válido en tiempo de ejecución, System.IndexOutOfRangeException
se produce una excepción. Cada expresión debe poder convertirse implícitamente al tipo Integer . El
resultado de la expresión de índice es la variable en el índice especificado y se clasifica como una variable.
Si la expresión se clasifica como un grupo de propiedades, se utiliza la resolución de sobrecarga para
determinar si una de las propiedades es aplicable a la lista de argumentos de índice. Si el grupo de
propiedades solo contiene una propiedad que tiene un Get descriptor de acceso y el descriptor de
acceso no toma ningún argumento, el grupo de propiedades se interpreta como una expresión de índice
con una lista de argumentos vacía. El resultado se usa como destino de la expresión de índice actual. Si
no hay ninguna propiedad aplicable, se produce un error en tiempo de compilación. De lo contrario, la
expresión da como resultado un acceso de propiedad con la expresión de destino asociada (si existe) del
grupo de propiedades.
Si la expresión se clasifica como un grupo de propiedades enlazadas en tiempo de ejecución o como un
valor cuyo tipo es Object o System.Array , el procesamiento de la expresión de índice se aplaza hasta el
tiempo de ejecución y la indexación se enlaza en tiempo de ejecución. La expresión da como resultado un
acceso de propiedad enlazado en tiempo de ejecución con el tipo Object . La expresión de destino
asociada es la expresión de destino, si es un valor, o la expresión de destino asociada del grupo de
propiedades. En tiempo de ejecución, la expresión se procesa de la siguiente manera:
Si la expresión se clasifica como un grupo de propiedades enlazadas en tiempo de ejecución, la expresión
puede dar como resultado un grupo de métodos, un grupo de propiedades o un valor (si el miembro es
una instancia o una variable compartida). Si el resultado es un grupo de métodos o un grupo de
propiedades, se aplica la resolución de sobrecarga al grupo para determinar el método correcto para la
lista de argumentos. Si se produce un error en la resolución de sobrecarga,
System.Reflection.AmbiguousMatchException se produce una excepción. Después, el resultado se procesa
como un acceso de propiedad o como una invocación y se devuelve el resultado. Si la invocación es de
una subrutina, el resultado es Nothing .
Si el tipo en tiempo de ejecución de la expresión de destino es un tipo de matriz o System.Array , el
resultado de la expresión de índice es el valor de la variable en el índice especificado.
De lo contrario, el tipo en tiempo de ejecución de la expresión debe tener una propiedad predeterminada
y el índice se realiza en el grupo de propiedades que representa todas las propiedades predeterminadas
del tipo. Si el tipo no tiene ninguna propiedad predeterminada, System.MissingMemberException se
produce una excepción.
Expresiones nuevas
El New operador se usa para crear nuevas instancias de tipos. Existen cuatro formas de New expresiones:
Las expresiones de creación de objetos se utilizan para crear nuevas instancias de tipos de clase y tipos
de valor.
Las expresiones de creación de matrices se utilizan para crear nuevas instancias de tipos de matriz.
Las expresiones de creación de delegado (que no tienen una sintaxis distinta de las expresiones de
creación de objetos) se usan para crear nuevas instancias de tipos de delegado.
Las expresiones de creación de objetos anónimos se utilizan para crear nuevas instancias de tipos de
clase anónima.
NewExpression
: ObjectCreationExpression
| ArrayExpression
| AnonymousObjectCreationExpression
;
Una New expresión se clasifica como un valor y el resultado es la nueva instancia del tipo.
Expresiones de Object-Creation
Una expresión de creación de objetos se usa para crear una nueva instancia de un tipo de clase o un tipo de
estructura.
ObjectCreationExpression
: 'New' NonArrayTypeName ( OpenParenthesis ArgumentList? CloseParenthesis )?
ObjectCreationExpressionInitializer?
;
ObjectCreationExpressionInitializer
: ObjectMemberInitializer
| ObjectCollectionInitializer
;
ObjectMemberInitializer
: 'With' OpenCurlyBrace FieldInitializerList CloseCurlyBrace
;
FieldInitializerList
: FieldInitializer ( Comma FieldInitializer )*
;
FieldInitializer
: 'Key'? ('.' IdentifierOrKeyword Equals )? Expression
;
ObjectCollectionInitializer
: 'From' CollectionInitializer
;
CollectionInitializer
: OpenCurlyBrace CollectionElementList? CloseCurlyBrace
;
CollectionElementList
: CollectionElement ( Comma CollectionElement )*
;
CollectionElement
: Expression
| CollectionInitializer
;
El tipo de una expresión de creación de objeto debe ser un tipo de clase, un tipo de estructura o un parámetro
de tipo con una New restricción y no puede ser una MustInherit clase. Dada una expresión de creación de
objeto con el formato New T(A) , donde T es un tipo de clase o un tipo de estructura y A es una lista de
argumentos opcional, la resolución de sobrecarga determina el constructor correcto de T para llamar a. Un
parámetro de tipo con una New restricción se considera que tiene un único constructor sin parámetros. Si no se
puede llamar a ningún constructor, se produce un error en tiempo de compilación; de lo contrario, la expresión
da como resultado la creación de una nueva instancia de T con el constructor elegido. Si no hay ningún
argumento, se pueden omitir los paréntesis.
La asignación de una instancia depende de si la instancia es un tipo de clase o un tipo de valor. New las
instancias de tipos de clase se crean en el montón del sistema, mientras que las instancias nuevas de tipos de
valor se crean directamente en la pila.
Una expresión de creación de objetos puede especificar opcionalmente una lista de inicializadores de miembro
después de los argumentos de constructor. Estos inicializadores de miembro tienen el prefijo de la palabra clave
With y la lista de inicializadores se interpreta como si estuviera en el contexto de una With instrucción. Por
ejemplo, dada la clase:
Class Customer
Dim Name As String
Dim Address As String
End Class
El código:
Module Test
Sub Main()
Dim x As New Customer() With { .Name = "Bob Smith", _
.Address = "123 Main St." }
End Sub
End Module
Es aproximadamente equivalente a:
Module Test
Sub Main()
Dim x, _t1 As Customer
x = _t1
End Sub
End Module
Cada inicializador debe especificar un nombre para asignar y el nombre debe ser una ReadOnly variable o
propiedad que no sea de instancia del tipo que se está construyendo; el acceso a miembros no se enlazará en
tiempo de ejecución si el tipo que se está construyendo es Object . Los inicializadores no pueden usar la Key
palabra clave. Cada miembro de un tipo solo se puede inicializar una vez. Sin embargo, las expresiones de
inicializador pueden hacer referencia entre sí. Por ejemplo:
Module Test
Sub Main()
Dim x As New Customer() With { .Name = "Bob Smith", _
.Address = .Name & " St." }
End Sub
End Module
Los inicializadores se asignan de izquierda a derecha, por lo que si un inicializador hace referencia a un
miembro que todavía no se ha inicializado, verá el valor de la variable de instancia después de ejecutar el
constructor:
Module Test
Sub Main()
' The value of Address will be " St." since Name has not been
' assigned yet.
Dim x As New Customer() With { .Address = .Name & " St." }
End Sub
End Module
Class Customer
Dim Name As String
Dim Address As Address
Dim Age As Integer
End Class
Class Address
Dim Street As String
Dim City As String
Dim State As String
Dim ZIP As String
End Class
Module Test
Sub Main()
Dim c As New Customer() With { _
.Name = "John Smith", _
.Address = New Address() With { _
.Street = "23 Main St.", _
.City = "Peoria", _
.State = "IL", _
.ZIP = "13934" }, _
.Age = 34 }
End Sub
End Module
Si el tipo que se va a crear es un tipo de colección y tiene un método de instancia denominado Add (incluidos
los métodos de extensión y los métodos compartidos), la expresión de creación de objetos puede especificar un
inicializador de colección con el prefijo de la palabra clave From . Una expresión de creación de objetos no
puede especificar un inicializador de miembro y un inicializador de colección. Cada elemento del inicializador de
colección se pasa como argumento a una invocación de la Add función. Por ejemplo:
equivale a:
Si un elemento es un inicializador de colección, cada elemento del inicializador de subcolección se pasará como
un argumento individual a la Add función. Por ejemplo, los elementos siguientes:
equivale a:
Dim dict = New Dictionary(Of Integer, String)
dict.Add(1, "One")
dict.Add(2, "Two")
Esta expansión siempre se realiza y solo se realiza en un nivel de profundidad. después de eso, los
subinicializadores se consideran literales de matriz. Por ejemplo:
' Error: List(Of T) does not have an Add method that takes two parameters.
Dim list = New List(Of Integer())() From { { 1, 2 }, { 3, 4 } }
' OK, this initializes the dictionary with (Integer, Integer()) pairs.
Dim dict = New Dictionary(Of Integer, Integer())() From _
{ { 1, { 2, 3 } }, { 3, { 4, 5 } } }
Expresiones de matriz
Una expresión de matriz se usa para crear una nueva instancia de un tipo de matriz. Hay dos tipos de
expresiones de matriz: expresiones de creación de matrices y literales de matriz.
Expresiones de creación de matrices
Si se proporciona un modificador de inicialización de tamaño de matriz, el tipo de matriz resultante se deriva
eliminando cada uno de los argumentos individuales de la lista de argumentos de inicialización de tamaño de la
matriz. El valor de cada argumento determina el límite superior de la dimensión correspondiente en la instancia
de matriz recién asignada. Si la expresión tiene un inicializador de colección que no está vacío, cada argumento
de la lista de argumentos debe ser una constante, y las longitudes de rango y dimensión especificadas por la
lista de expresiones deben coincidir con las del inicializador de colección.
Si no se proporciona un modificador de inicialización de tamaño de matriz, el nombre de tipo debe ser un tipo
de matriz y el inicializador de colección debe estar vacío o tener el mismo número de niveles de anidamiento
que el rango del tipo de matriz especificado. Todos los elementos del nivel de anidamiento más interno deben
poder convertirse implícitamente al tipo de elemento de la matriz y se deben clasificar como un valor. El número
de elementos de cada inicializador de colección anidada siempre debe ser coherente con el tamaño de las otras
colecciones en el mismo nivel. Las longitudes de las dimensiones individuales se deducen del número de
elementos de cada uno de los niveles de anidamiento correspondientes del inicializador de colección. Si el
inicializador de colección está vacío, la longitud de cada dimensión es cero.
El nivel de anidamiento más externo de un inicializador de colección corresponde a la dimensión situada más a
la izquierda de una matriz y el nivel de anidamiento más interno corresponde a la dimensión situada más a la
derecha. El ejemplo:
Dim array As Integer(,) = _
{ { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 8, 9 } }
Es equivalente a lo siguiente:
array(0, 0) = 0: array(0, 1) = 1
array(1, 0) = 2: array(1, 1) = 3
array(2, 0) = 4: array(2, 1) = 5
array(3, 0) = 6: array(3, 1) = 7
array(4, 0) = 8: array(4, 1) = 9
Si el inicializador de la colección está vacío (es decir, uno que contiene llaves pero no hay ninguna lista de
inicializadores) y los límites de las dimensiones de la matriz que se está inicializando son conocidos, el
inicializador de colección vacío representa una instancia de matriz del tamaño especificado donde todos los
elementos se han inicializado en el valor predeterminado del tipo de elemento. Si no se conocen los límites de
las dimensiones de la matriz que se va a inicializar, el inicializador de colección vacío representa una instancia de
matriz en la que todas las dimensiones tienen el tamaño cero.
El rango de una instancia de matriz y la longitud de cada dimensión son constantes para toda la duración de la
instancia. En otras palabras, no es posible cambiar el rango de una instancia de matriz existente, ni tampoco es
posible cambiar el tamaño de sus dimensiones.
Literales de matriz
Un literal de matriz denota una matriz cuyo tipo de elemento, rango y límites se deducen a partir de una
combinación del contexto de la expresión y un inicializador de colección. Esto se explica en la sección
reclasificación de expresiones.
ArrayExpression
: ArrayCreationExpression
| ArrayLiteralExpression
;
ArrayCreationExpression
: 'New' NonArrayTypeName ArrayNameModifier CollectionInitializer
;
ArrayLiteralExpression
: CollectionInitializer
;
Por ejemplo:
' array of integers
Dim a = {1, 2, 3}
El formato y los requisitos del inicializador de colección en un literal de matriz es exactamente el mismo que el
inicializador de colección en una expresión de creación de matriz.
Nota. Un literal de matriz no crea la matriz en y de sí mismo. en su lugar, es la reclasificación de la expresión en
un valor que hace que se cree la matriz. Por ejemplo, la conversión CType(new Integer() {1,2,3}, Short()) no es
posible porque no hay ninguna conversión de Integer() a Short() ; pero la expresión CType({1,2,3},Short())
es posible porque primero reclasifica el literal de matriz en la expresión de creación de matriz
New Short() {1,2,3} .
Module Test
Sub M()
End Sub
Sub Main()
' Valid
Dim x As F = AddressOf M
End Sub
End Module
Nota. Esta relajación solo se permite cuando no se usan semánticas estrictas debido a los métodos de
extensión. Dado que los métodos de extensión solo se tienen en cuenta si un método normal no era aplicable, es
posible que un método de instancia sin parámetros oculte un método de extensión con parámetros para la
construcción del delegado.
Si más de un método al que hace referencia el puntero del método es aplicable al tipo de delegado, la resolución
de sobrecarga se utiliza para elegir entre los métodos candidatos. Los tipos de los parámetros del delegado se
utilizan como los tipos de los argumentos para la resolución de sobrecarga. Si ningún candidato de método es
más aplicable, se produce un error en tiempo de compilación. En el ejemplo siguiente, la variable local se
inicializa con un delegado que hace referencia al segundo Square método porque ese método es más aplicable
a la firma y al tipo de valor devuelto de DoubleFunc .
Module Test
Function Square(x As Single) As Single
Return x * x
End Function
Sub Main()
Dim a As New DoubleFunc(AddressOf Square)
End Sub
End Module
Si el segundo Square método no estuviera presente, Square se habría elegido el primer método. Si el entorno
de compilación o, después especifica la semántica estricta Option Strict , se produce un error en tiempo de
compilación si el método más específico al que hace referencia el puntero del método es más estrecho que la
firma del delegado. Un método M se considera más estrecho que un tipo de delegado D si:
Un tipo de parámetro de M tiene una conversión de ampliación al tipo de parámetro correspondiente de
D .
O bien, el tipo de valor devuelto de M tiene una conversión de restricción al tipo de valor devuelto de D
.
Si los argumentos de tipo están asociados al puntero de método, solo se tienen en cuenta los métodos con el
mismo número de argumentos de tipo. Si no hay ningún argumento de tipo asociado al puntero de método, se
usa la inferencia de tipos al buscar coincidencias de firmas con un método genérico. A diferencia de otras
inferencias de tipos normales, el tipo de valor devuelto del delegado se usa al deducir los argumentos de tipo,
pero los tipos de valor devuelto todavía no se tienen en cuenta al determinar la sobrecarga genérica menos. En
el ejemplo siguiente se muestran las dos maneras de proporcionar un argumento de tipo a una expresión de
creación de delegado:
Delegate Function D(s As String, i As Integer) As Integer
Delegate Function E() As Integer
Module Test
Public Function F(Of T)(s As String, t1 As T) As T
End Function
Sub Main()
Dim d1 As D = AddressOf f(Of Integer) ' OK, type arg explicit
Dim d2 As D = AddressOf f ' OK, type arg inferred
En el ejemplo anterior, se creó una instancia de un tipo de delegado no genérico mediante un método genérico.
También es posible crear una instancia de un tipo de delegado construido mediante un método genérico. Por
ejemplo:
Module Test
Function Compare(Of T)(t1 As List(of T), t2 As List(of T)) As Boolean
...
End Function
Sub Main()
Dim p As Predicate(Of List(Of Integer))
p = AddressOf Compare(Of Integer)
End Sub
End Module
Si el argumento de la expresión de creación de delegado es un método lambda, el método lambda debe ser
aplicable a la firma del tipo de delegado. Un método lambda L es aplicable a un tipo de delegado D si:
Si L tiene parámetros, D tiene el mismo número de parámetros. (Si L no tiene ningún parámetro, D
se omiten los parámetros de).
Los tipos de parámetro de L cada tienen una conversión al tipo del tipo de parámetro correspondiente
de D , y sus modificadores (es decir ByRef , ByVal ) coinciden.
Si D es una función, el tipo de valor devuelto de L tiene una conversión al tipo de valor devuelto de D
. (Si D es una subrutina, se omite el valor devuelto de L ).
Si se omite el tipo de parámetro de un parámetro de L , se deduce el tipo del parámetro correspondiente de D
. Si el parámetro de L tiene modificadores de nombre de matriz o de nombre que aceptan valores NULL, se
produce un error en tiempo de compilación. Una vez que todos los tipos de parámetro de L están disponibles,
se deduce el tipo de la expresión en el método lambda. Por ejemplo:
Delegate Function F(x As Integer, y As Long) As Long
Module Test
Sub Main()
' b inferred to Integer, c and return type inferred to Long
Dim a As F = Function(b, c) b + c
En algunas situaciones en las que la firma de delegado no coincide exactamente con el método o la firma de
método lambda, es posible que el .NET Framework no admita la creación de delegado de forma nativa. En esa
situación, se usa una expresión de método lambda para buscar coincidencias con los dos métodos. Por ejemplo:
Module Test
Function SquareString(x As String) As String
Return CInt(x) * CInt(x)
End Function
Sub Main()
' The following two lines are equivalent
Dim a As New IntFunc(AddressOf SquareString)
Dim b As New IntFunc( _
Function(x As Integer) CInt(SquareString(CStr(x))))
End Sub
End Module
El resultado de una expresión de creación de delegado es una instancia de delegado que hace referencia al
método coincidente con la expresión de destino asociada (si existe) de la expresión de puntero de método. Si la
expresión de destino se escribe como un tipo de valor, el tipo de valor se copia en el montón del sistema porque
un delegado solo puede apuntar a un método de un objeto en el montón. El método y el objeto al que hace
referencia un delegado permanecen constantes durante toda la duración del delegado. En otras palabras, no es
posible cambiar el destino o el objeto de un delegado una vez creado.
Expresiones de Object-Creation anónimas
Una expresión de creación de objetos con inicializadores de miembro también puede omitir el nombre de tipo
por completo.
AnonymousObjectCreationExpression
: 'New' ObjectMemberInitializer
;
En ese caso, se crea un tipo anónimo basado en los tipos y nombres de los miembros inicializados como parte
de la expresión. Por ejemplo:
Module Test
Sub Main()
Dim Customer = New With { .Name = "John Smith", .Age = 34 }
Console.WriteLine(Customer.Name)
End Sub
End Module
El tipo creado por una expresión de creación de objeto anónima es una clase que no tiene nombre, hereda
directamente de y Object tiene un conjunto de propiedades con el mismo nombre que los miembros
asignados a en la lista de inicializadores de miembro. El tipo de cada propiedad se deduce con las mismas reglas
que la inferencia de tipo de variable local. Los tipos anónimos generados también invalidan ToString ,
devolviendo una representación de cadena de todos los miembros y sus valores. (El formato exacto de esta
cadena está fuera del ámbito de esta especificación).
De forma predeterminada, las propiedades generadas por el tipo anónimo son de lectura y escritura. Es posible
marcar una propiedad de tipo anónimo como de solo lectura mediante el Key modificador. El Key modificador
especifica que el campo se puede utilizar para identificar de forma única el valor que representa el tipo
anónimo. Además de hacer que la propiedad sea de solo lectura, también hace que el tipo anónimo invalide
Equals e GetHashCode implemente la interfaz System.IEquatable(Of T) (rellenando el tipo anónimo para T ).
Los miembros se definen de la siguiente manera:
Function Equals(obj As Object) As Boolean y Function Equals(val As T) As Boolean se implementan validando
que las dos instancias son del mismo tipo y, a continuación, comparando cada Key miembro mediante
Object.Equals . Si todos los Key miembros son iguales, Equals devuelve True , de lo contrario, Equals
devuelve False .
Function GetHashCode() As Integer se implementa de tal modo que si Equals es true para dos instancias del
tipo anónimo, GetHashCode devolverá el mismo valor. El hash se inicia con un valor de inicialización y, a
continuación, para cada Key miembro, en orden multiplica el hash por 31 y agrega el Key valor hash del
miembro (proporcionado por GetHashCode ) si el miembro no es un tipo de referencia o un tipo de valor que
acepta valores NULL con el valor de Nothing .
Por ejemplo, el tipo creado en la instrucción:
crea una clase que tiene un aspecto similar al siguiente (aunque la implementación exacta puede variar):
Friend NotInheritable Class $Anonymous1
Implements IEquatable(Of $Anonymous1)
Return True
End Function
Return hash
End Function
Para simplificar la situación en la que se crea un tipo anónimo a partir de los campos de otro tipo, los nombres
de campo se pueden deducir directamente de las expresiones en los casos siguientes:
Una expresión de nombre simple x deduce el nombre x .
Una expresión de acceso a miembros x.y deduce el nombre y .
Una expresión de búsqueda de diccionario x!y deduce el nombre y .
Una invocación o una expresión de índice sin argumentos x() deduce el nombre x .
Una expresión de acceso a miembros XML x.<y> , x...<y> , x.@y deduce el nombre y .
Una expresión de acceso a miembros XML que es el destino de una expresión de acceso a miembros
x.<y>.z deduce el nombre z .
Una expresión de acceso a miembros XML que es el destino de una invocación o expresión de índice sin
argumentos x.<y>.z() deduce el nombre z .
Una expresión de acceso a miembros XML que es el destino de una expresión de invocación o de índice
x.<y>(0) deduce el nombre y .
El inicializador se interpreta como una asignación de la expresión al nombre deducido. Por ejemplo, los
siguientes inicializadores son equivalentes:
Class Address
Public Street As String
Public City As String
Public State As String
Public ZIP As String
End Class
Class C1
Sub Test(a As Address)
Dim cityState1 = New With { .City = a.City, .State = a.State }
Dim cityState2 = New With { a.City, a.State }
End Sub
End Class
Si se infiere un nombre de miembro que entra en conflicto con un miembro existente del tipo, como
GetHashCode , se produce un error en tiempo de compilación. A diferencia de los inicializadores de miembro
normales, las expresiones de creación de objetos anónimos no permiten que los inicializadores de miembro
tengan referencias circulares, o para hacer referencia a un miembro antes de que se haya inicializado. Por
ejemplo:
Module Test
Sub Main()
' Error: Circular references
Dim x = New With { .a = .b, .b = .a }
Si dos expresiones de creación de clases anónimas se producen dentro del mismo método y producen la misma
forma resultante, si el orden de las propiedades, los nombres de propiedad y los tipos de propiedad coinciden,
se hará referencia a la misma clase anónima. El ámbito del método de una instancia o una variable miembro
compartida con un inicializador es el constructor en el que se inicializa la variable.
Nota. Es posible que un compilador pueda elegir unificar más tipos anónimos, como en el nivel de ensamblado,
pero en este momento no se puede confiar en esto.
Expresiones de conversión
Una expresión de conversión convierte una expresión en un tipo determinado. Las palabras clave de conversión
específicas convierten las expresiones en tipos primitivos. Tres palabras clave de conversión general, CType
TryCast y DirectCast , convierten una expresión en un tipo.
CastExpression
: 'DirectCast' OpenParenthesis Expression Comma TypeName CloseParenthesis
| 'TryCast' OpenParenthesis Expression Comma TypeName CloseParenthesis
| 'CType' OpenParenthesis Expression Comma TypeName CloseParenthesis
| CastTarget OpenParenthesis Expression CloseParenthesis
;
CastTarget
: 'CBool' | 'CByte' | 'CChar' | 'CDate' | 'CDec' | 'CDbl' | 'CInt'
| 'CLng' | 'CObj' | 'CSByte' | 'CShort' | 'CSng' | 'CStr' | 'CUInt'
| 'CULng' | 'CUShort'
;
DirectCast y TryCast tienen comportamientos especiales. Por este motivo, solo admiten conversiones nativas.
Además, el tipo de destino de una TryCast expresión no puede ser un tipo de valor. Los operadores de
conversión definidos por el usuario no se tienen en cuenta cuando DirectCast TryCast se usa o. (Nota. El
conjunto de conversión que DirectCast y TryCast admiten están restringidos porque implementan las
conversiones de "CLR nativo". El propósito de DirectCast es proporcionar la funcionalidad de la instrucción
"unbox", mientras que el propósito de TryCast es proporcionar la funcionalidad de la instrucción "isinst". Dado
que se asignan a las instrucciones de CLR, la compatibilidad con las conversiones no admitidas directamente
por CLR anularía el propósito previsto).
DirectCast convierte expresiones cuyo tipo es distinto Object de CType . Al convertir una expresión de tipo
Object cuyo tipo en tiempo de ejecución es un tipo de valor primitivo, DirectCast produce una
System.InvalidCastException excepción si el tipo especificado no es el mismo que el tipo en tiempo de ejecución
de la expresión o System.NullReferenceException si la expresión se evalúa como Nothing . (Nota. Como se
indicó anteriormente, DirectCast se asigna directamente a la instrucción de CLR "unbox" cuando el tipo de la
expresión es Object . En cambio, CType convierte en una llamada a una aplicación auxiliar en tiempo de
ejecución para realizar la conversión de modo que se admitan las conversiones entre tipos primitivos. En el caso
de que una Object expresión se convierta en un tipo de valor primitivo y el tipo de la instancia real coincida
con el tipo de destino, DirectCast será significativamente más rápido que CType ).
TryCast convierte expresiones pero no inicia una excepción si la expresión no se puede convertir al tipo de
destino. En su lugar, producirá TryCast Nothing si la expresión no se puede convertir en tiempo de ejecución.
(Nota. Como se indicó anteriormente, se TryCast asigna directamente a la instrucción de CLR "isinst".
Mediante la combinación de la comprobación de tipos y la conversión en una sola operación, TryCast puede
ser más barato que hacer TypeOf ... Is y después a CType .
Por ejemplo:
Interface ITest
Sub Test()
End Interface
Module Test
Sub Convert(o As Object)
Dim i As ITest = TryCast(o, ITest)
Expresiones de operador
Hay dos tipos de operadores. Los operadores unarios toman un operando y usan la notación de prefijo (por
ejemplo, -x ). Los operadores binarios toman dos operandos y usan una notación infija (por ejemplo, x + y ).
Con la excepción de los operadores relacionales, que siempre dan como resultado Boolean , un operador
definido para un tipo determinado genera ese tipo. Los operandos de un operador siempre se deben clasificar
como un valor; el resultado de una expresión de operador se clasifica como un valor.
OperatorExpression
: ArithmeticOperatorExpression
| RelationalOperatorExpression
| LikeOperatorExpression
| ConcatenationOperatorExpression
| ShortCircuitLogicalOperatorExpression
| LogicalOperatorExpression
| ShiftOperatorExpression
| AwaitOperatorExpression
;
Operador Await
Exponenciación ^
Negación unaria + , -
Multiplicativa * , /
División de enteros \
Modulus Mod
Aditiva + , -
Concatenación &
O lógico Or , OrElse
Cuando una expresión contiene dos operadores con la misma prioridad, la asociatividad de los operadores
controla el orden en el que se realizan las operaciones. Todos los operadores binarios son asociativos a la
izquierda, lo que significa que las operaciones se realizan de izquierda a derecha. La prioridad y la asociatividad
se pueden controlar mediante expresiones entre paréntesis.
Operandos de objeto
Además de los tipos regulares admitidos por cada operador, todos los operadores admiten operandos de tipo
Object . Los operadores aplicados a Object los operandos se controlan de forma similar a las llamadas a
métodos realizadas en Object los valores: se puede elegir una llamada al método enlazado en tiempo de
ejecución, en cuyo caso el tipo en tiempo de ejecución de los operandos, en lugar del tipo en tiempo de
compilación, determina la validez y el tipo de la operación. Si la semántica estricta se especifica mediante el
entorno de compilación o Option Strict , cualquier operador con operandos de tipo Object producirá un
error en tiempo de compilación, excepto los TypeOf...Is Is operadores, y IsNot .
Cuando la resolución de operadores determina que una operación se debe realizar enlazada en tiempo de
ejecución, el resultado de la operación es el resultado de aplicar el operador a los tipos de operando si los tipos
en tiempo de ejecución de los operandos son tipos admitidos por el operador. El valor Nothing se trata como el
valor predeterminado del tipo del otro operando en una expresión de operador binario. En una expresión de
operador unario, o si ambos operandos están Nothing en una expresión de operador binario, el tipo de la
operación es Integer o el único tipo de resultado del operador, si el operador no da como resultado Integer .
El resultado de la operación siempre se vuelve a convertir a Object . Si los tipos de operando no tienen ningún
operador válido, System.InvalidCastException se produce una excepción. Las conversiones en tiempo de
ejecución se realizan sin tener en cuenta si son implícitas o explícitas.
Si el resultado de una operación binaria numérica produciría una excepción de desbordamiento
(independientemente de si la comprobación de desbordamiento de enteros está activada o desactivada), el tipo
de resultado se promueve al siguiente tipo numérico más amplio, si es posible. Por ejemplo, considere el
siguiente código:
Module Test
Sub Main()
Dim o As Object = CObj(CByte(2)) * CObj(CByte(255))
System.Int16 = 512
Si no hay ningún tipo numérico más amplio disponible para contener el número, System.OverflowException se
produce una excepción.
Resolución de operadores
Dado un tipo de operador y un conjunto de operandos, la resolución de operadores determina el operador que
se va a utilizar para los operandos. Al resolver operadores, los operadores definidos por el usuario se
considerarán en primer lugar, siguiendo estos pasos:
1. En primer lugar, se recopilan todos los operadores candidatos. Los operadores candidatos son todos los
operadores definidos por el usuario del tipo de operador determinado en el tipo de origen y todos los
operadores definidos por el usuario del tipo determinado en el tipo de destino. Si el tipo de origen y el
tipo de destino están relacionados, los operadores comunes solo se consideran una vez.
2. A continuación, se aplica la resolución de sobrecarga a los operadores y operandos para seleccionar el
operador más específico. En el caso de los operadores binarios, esto puede dar lugar a una llamada
enlazada en tiempo de ejecución.
Al recopilar los operadores candidatos para un tipo T? , se usan los operadores de tipo T en su lugar. T
También se elevan los operadores definidos por el usuario de que solo incluyen tipos de valor que no aceptan
valores NULL. Un operador de elevación utiliza la versión que acepta valores NULL de cualquier tipo de valor,
con la excepción de los tipos de valor devueltos de IsTrue y IsFalse (que debe ser Boolean ). Los operadores
de elevación se evalúan convirtiendo los operandos a su versión que no acepta valores NULL, evaluando
después el operador definido por el usuario y, a continuación, convirtiendo el tipo de resultado a su versión que
acepta valores NULL. Si el operando ether es Nothing , el resultado de la expresión es un valor de Nothing tipo
como la versión que acepta valores NULL del tipo de resultado. Por ejemplo:
Structure T
...
End Structure
Structure S
Public Shared Operator +(ByVal op1 As S, ByVal op2 As T) As T
...
End Operator
End Structure
Module Test
Sub Main()
Dim x As S?
Dim y, z As T?
Si el operador es un operador binario y uno de los operandos es un tipo de referencia, el operador también se
eleva, pero cualquier enlace al operador produce un error. Por ejemplo:
Structure S1
Public F1 As Integer
Module Test
Sub Main()
Dim a? As S1
Dim s As String
Nota. Esta regla existe porque se ha tenido en cuenta si se desea agregar tipos de referencia de propagación de
valores NULL en una versión futura, en cuyo caso cambiará el comportamiento en el caso de los operadores
binarios entre los dos tipos.
Al igual que con las conversiones, siempre se prefieren los operadores definidos por el usuario a los operadores
de elevación.
Al resolver los operadores sobrecargados, puede haber diferencias entre las clases definidas en Visual Basic y
las definidas en otros lenguajes:
En otros lenguajes,, Not And y Or se pueden sobrecargar como operadores lógicos y operadores bit a
bit. Después de la importación de un ensamblado externo, cualquier forma se acepta como una
sobrecarga válida para estos operadores. Sin embargo, para un tipo que define operadores lógicos y bit a
bit, solo se considerará la implementación bit a bit.
En otros lenguajes, >> y << se pueden sobrecargar como operadores con signo y como operadores sin
signo. Después de la importación de un ensamblado externo, cualquier forma se acepta como una
sobrecarga válida. Sin embargo, para un tipo que define los operadores con y sin signo, solo se
considerará la implementación firmada.
Si ningún operador definido por el usuario es más específico para los operandos, se tendrán en cuenta
los operadores intrínsecos. Si no se define ningún operador intrínseco para los operandos y alguno de
los operandos tiene un objeto de tipo, el operador se resolverá en tiempo de ejecución; de lo contrario, se
producirá un error en tiempo de compilación.
En versiones anteriores de Visual Basic, si había exactamente un operando de tipo Object, y no hay ningún
operador definido por el usuario aplicable y no hay ningún operador intrínseco aplicable, se produjo un error. A
partir de Visual Basic 11, ahora se resuelve enlazado en tiempo de ejecución. Por ejemplo:
Module Module1
Sub Main()
Dim p As Object = Nothing
Dim U As New Uri("http://www.microsoft.com")
Dim j = U * p ' is now resolved late-bound
End Sub
End Module
Un tipo T que tiene un operador intrínseco también define el mismo operador para T? . El resultado del
operador en T? será el mismo que para T , salvo que si alguno de los operandos es Nothing , el resultado del
operador será Nothing (es decir, se propagará el valor null). Con el fin de resolver el tipo de una operación, ?
se quita de los operandos que los tienen, se determina el tipo de la operación y ? se agrega un al tipo de la
operación si alguno de los operandos fuera tipos de valor que aceptan valores NULL. Por ejemplo:
Cada operador enumera los tipos intrínsecos para los que se define y el tipo de la operación realizada según los
tipos de operando. El resultado del tipo de una operación intrínseca sigue estas reglas generales:
Si todos los operandos son del mismo tipo y el operador se define para el tipo, no se produce ninguna
conversión y se utiliza el operador para ese tipo.
Cualquier operando cuyo tipo no esté definido para el operador se convierte mediante los pasos
siguientes y el operador se resuelve con los nuevos tipos:
El operando se convierte al siguiente tipo más ancho que se define para el operador y el operando
y al que se pueden convertir implícitamente.
Si no existe este tipo, el operando se convierte al siguiente tipo más estrecho que se define para el
operador y el operando y en el que es implícitamente convertible.
Si no existe ese tipo o no se puede realizar la conversión, se produce un error en tiempo de
compilación.
De lo contrario, los operandos se convierten al mayor de los tipos de operando y se usa el operador para
ese tipo. Si el tipo de operando más estrecho no se puede convertir implícitamente al tipo de operador
más amplio, se produce un error en tiempo de compilación.
Sin embargo, a pesar de estas reglas generales, hay una serie de casos especiales que se indican en las tablas de
resultados de operadores.
Nota. Por motivos de formato, las tablas de tipo de operador abrevian los nombres predefinidos a los dos
primeros caracteres. "By" es Byte , "UI" es UInteger , "St" es String , etc. "Err" significa que no hay ninguna
operación definida para los tipos de operando especificados.
Operadores aritméticos
Los * / operadores,, \ , ^ , Mod , + y - son los operadores aritméticos.
ArithmeticOperatorExpression
: UnaryPlusExpression
| UnaryMinusExpression
| AdditionOperatorExpression
| SubtractionOperatorExpression
| MultiplicationOperatorExpression
| DivisionOperatorExpression
| ModuloOperatorExpression
| ExponentOperatorExpression
;
Las operaciones aritméticas de punto flotante se pueden realizar con una precisión mayor que el tipo de
resultado de la operación. Por ejemplo, algunas arquitecturas de hardware admiten un tipo de punto flotante
"extendido" o "Long Double" con un intervalo y una precisión mayores que el Double tipo y realizan
implícitamente todas las operaciones de punto flotante mediante este tipo de precisión superior. Se pueden
realizar arquitecturas de hardware para realizar operaciones de punto flotante con menos precisión solo a un
costo excesivo de rendimiento. en lugar de requerir una implementación para que pierda el rendimiento y la
precisión, Visual Basic permite usar el tipo de precisión superior para todas las operaciones de punto flotante.
Aparte de ofrecer resultados más precisos, esto rara vez tiene efectos medibles. Sin embargo, en las expresiones
con el formato x * y / z , donde la multiplicación genera un resultado que está fuera del Double intervalo,
pero la división posterior devuelve el resultado temporal al Double intervalo, el hecho de que la expresión se
evalúe en un formato de rango superior puede provocar que se genere un resultado finito en lugar de infinito.
Operador unario más
UnaryPlusExpression
: '+' Expression
;
El operador unario más se define para Byte los SByte tipos,,,,,,,,, UShort Short UInteger Integer ULong
Long Single Double y Decimal .
Tipo de operación:
RES
GU P RI
CO ST EE. AR CA ME
BO GR MO RA UU. IN UI LO UL DO & SÍ & M R OB
UnaryMinusExpression
: '-' Expression
;
RES
GU P RI
CO ST EE. AR CA ME
BO GR MO RA UU. IN UI LO UL DO & SÍ & M R OB
RES
GU P RI
CO ST EE. AR CA ME
BO GR MO RA UU. IN UI LO UL DO & SÍ & M R OB
Operador de suma
El operador de suma calcula la suma de los dos operandos.
AdditionOperatorExpression
: Expression '+' LineTerminator? Expression
;
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
EE US En UI Lo UL De Si Co Err Err Co OB
. sas sas
U qu qu
U. e e
ha ha
cer cer
In En Lo Lo De De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
UI UI Lo UL De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Lo Lo De De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
UL UL De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Re De Si Co Err Err Co OB
sg sas sas
ua qu qu
rd e e
o ha ha
cer cer
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
Sí Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Ca Pri Pri OB
m me me
r r
Pri Pri OB
m me
er r
O OB
B
Operador de resta
El operador de resta resta el segundo operando del primer operando.
SubtractionOperatorExpression
: Expression '-' LineTerminator? Expression
;
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
EE US En UI Lo UL De Si Co Err Err Co OB
. sas sas
U qu qu
U. e e
ha ha
cer cer
In En Lo Lo De De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
UI UI Lo UL De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
Lo Lo De De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
UL UL De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Re De Si Co Err Err Co OB
sg sas sas
ua qu qu
rd e e
o ha ha
cer cer
Sí Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Pri Co OB
m sas
er qu
e
ha
cer
O OB
B
Operador de multiplicación
El operador de multiplicación calcula el producto de dos operandos.
MultiplicationOperatorExpression
: Expression '*' LineTerminator? Expression
;
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
EE US En UI Lo UL De Si Co Err Err Co OB
. sas sas
U qu qu
U. e e
ha ha
cer cer
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
In En Lo Lo De De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
UI UI Lo UL De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Lo Lo De De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
UL UL De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Re De Si Co Err Err Co OB
sg sas sas
ua qu qu
rd e e
o ha ha
cer cer
Sí Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Pri Co OB
m sas
er qu
e
ha
cer
O OB
B
Operadores de división
Los operadores de división calculan el cociente de dos operandos. Hay dos operadores de división: el operador
de división normal (punto flotante) y el operador de división de enteros.
DivisionOperatorExpression
: FPDivisionOperatorExpression
| IntegerDivisionOperatorExpression
;
FPDivisionOperatorExpression
: Expression '/' LineTerminator? Expression
;
IntegerDivisionOperatorExpression
: Expression '\\' LineTerminator? Expression
;
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
Bo Co Co Co Co Co Co Co Co Co De Si Co Err Err Co OB
sas sas sas sas sas sas sas sas sas sas sas
qu qu qu qu qu qu qu qu qu qu qu
e e e e e e e e e e e
ha ha ha ha ha ha ha ha ha ha ha
cer cer cer cer cer cer cer cer cer cer cer
G Co Co Co Co Co Co Co Co De Si Co Err Err Co OB
R sas sas sas sas sas sas sas sas sas sas
qu qu qu qu qu qu qu qu qu qu
e e e e e e e e e e
ha ha ha ha ha ha ha ha ha ha
cer cer cer cer cer cer cer cer cer cer
Co Co Co Co Co Co Co Co De Si Co Err Err Co OB
m sas sas sas sas sas sas sas sas sas
o qu qu qu qu qu qu qu qu qu
e e e e e e e e e
ha ha ha ha ha ha ha ha ha
cer cer cer cer cer cer cer cer cer
EE Co Co Co Co Co De Si Co Err Err Co OB
. sas sas sas sas sas sas sas
U qu qu qu qu qu qu qu
U. e e e e e e e
ha ha ha ha ha ha ha
cer cer cer cer cer cer cer
In Co Co Co Co De Si Co Err Err Co OB
sas sas sas sas sas sas
qu qu qu qu qu qu
e e e e e e
ha ha ha ha ha ha
cer cer cer cer cer cer
UI Co Co Co De Si Co Err Err Co OB
sas sas sas sas sas
qu qu qu qu qu
e e e e e
ha ha ha ha ha
cer cer cer cer cer
Lo Co Co De Si Co Err Err Co OB
sas sas sas sas
qu qu qu qu
e e e e
ha ha ha ha
cer cer cer cer
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
UL Co De Si Co Err Err Co OB
sas sas sas
qu qu qu
e e e
ha ha ha
cer cer cer
Re De Si Co Err Err Co OB
sg sas sas
ua qu qu
rd e e
o ha ha
cer cer
Sí Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Pri Co OB
m sas
er qu
e
ha
cer
O OB
B
El operador de división de enteros se define para Byte , SByte , UShort , Short ,,, UInteger Integer ULong y
Long . Si el valor del operando derecho es cero, System.DivideByZeroException se produce una excepción. La
división redondea el resultado hacia cero y el valor absoluto del resultado es el entero más grande posible que
es menor que el valor absoluto del cociente de los dos operandos. El resultado es cero o positivo cuando los dos
operandos tienen el mismo signo y cero o negativo cuando los dos operandos tienen signos opuestos. Si el
operando izquierdo es el valor negativo máximo SByte ,, Short Integer o Long , y el operando derecho es
-1 , se produce un desbordamiento; Si la comprobación de desbordamiento de enteros está activada,
System.OverflowException se produce una excepción. De lo contrario, no se informará del desbordamiento y el
resultado será en su lugar el valor del operando izquierdo.
Nota. Dado que los dos operandos para los tipos sin signo siempre serán cero o positivos, el resultado es
siempre cero o positivo. Dado que el resultado de la expresión siempre será menor o igual que el mayor de los
dos operandos, no es posible que se produzca un desbordamiento. Como tal, la comprobación de
desbordamiento de enteros no se realiza para la división de enteros con dos enteros sin signo. El resultado es el
tipo del operando izquierdo.
Tipo de operación:
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
EE US En UI Lo UL Lo Lo Lo Err Err Lo OB
.
U
U.
In En Lo Lo Lo Lo Lo Lo Err Err Lo OB
UI UI Lo UL Lo Lo Lo Err Err Lo OB
Lo Lo Lo Lo Lo Lo Err Err Lo OB
UL UL Lo Lo Lo Err Err Lo OB
Re Lo Lo Lo Err Err Lo OB
sg
ua
rd
o
Sí Lo Err Err Lo OB
Pri Lo OB
m
er
O OB
B
Mod (Operador)
El Mod operador (módulo) calcula el resto de la división entre dos operandos.
ModuloOperatorExpression
: Expression 'Mod' LineTerminator? Expression
;
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
EE US En UI Lo UL De Si Co Err Err Co OB
. sas sas
U qu qu
U. e e
ha ha
cer cer
In En Lo Lo De De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
UI UI Lo UL De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Lo Lo De De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
UL UL De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Re De Si Co Err Err Co OB
sg sas sas
ua qu qu
rd e e
o ha ha
cer cer
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
Sí Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Pri Co OB
m sas
er qu
e
ha
cer
O OB
B
Exponencial (operador)
El operador de exponenciación calcula el primer operando elevado a la potencia del segundo operando.
ExponentOperatorExpression
: Expression '^' LineTerminator? Expression
;
El operador de exponenciación se define para el tipo Double . El valor se calcula de acuerdo con las reglas de
aritmética de IEEE 754.
Tipo de operación:
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
Bo Co Co Co Co Co Co Co Co Co Co Co Co Err Err Co OB
sas sas sas sas sas sas sas sas sas sas sas sas sas
qu qu qu qu qu qu qu qu qu qu qu qu qu
e e e e e e e e e e e e e
ha ha ha ha ha ha ha ha ha ha ha ha ha
cer cer cer cer cer cer cer cer cer cer cer cer cer
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
G Co Co Co Co Co Co Co Co Co Co Co Err Err Co OB
R sas sas sas sas sas sas sas sas sas sas sas sas
qu qu qu qu qu qu qu qu qu qu qu qu
e e e e e e e e e e e e
ha ha ha ha ha ha ha ha ha ha ha ha
cer cer cer cer cer cer cer cer cer cer cer cer
Co Co Co Co Co Co Co Co Co Co Co Err Err Co OB
m sas sas sas sas sas sas sas sas sas sas sas
o qu qu qu qu qu qu qu qu qu qu qu
e e e e e e e e e e e
ha ha ha ha ha ha ha ha ha ha ha
cer cer cer cer cer cer cer cer cer cer cer
EE Co Co Co Co Co Co Co Co Err Err Co OB
. sas sas sas sas sas sas sas sas sas
U qu qu qu qu qu qu qu qu qu
U. e e e e e e e e e
ha ha ha ha ha ha ha ha ha
cer cer cer cer cer cer cer cer cer
In Co Co Co Co Co Co Co Err Err Co OB
sas sas sas sas sas sas sas sas
qu qu qu qu qu qu qu qu
e e e e e e e e
ha ha ha ha ha ha ha ha
cer cer cer cer cer cer cer cer
UI Co Co Co Co Co Co Err Err Co OB
sas sas sas sas sas sas sas
qu qu qu qu qu qu qu
e e e e e e e
ha ha ha ha ha ha ha
cer cer cer cer cer cer cer
Lo Co Co Co Co Co Err Err Co OB
sas sas sas sas sas sas
qu qu qu qu qu qu
e e e e e e
ha ha ha ha ha ha
cer cer cer cer cer cer
UL Co Co Co Co Err Err Co OB
sas sas sas sas sas
qu qu qu qu qu
e e e e e
ha ha ha ha ha
cer cer cer cer cer
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
Re Co Co Co Err Err Co OB
sg sas sas sas sas
ua qu qu qu qu
rd e e e e
o ha ha ha ha
cer cer cer cer
Sí Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Pri Co OB
m sas
er qu
e
ha
cer
O OB
B
Operadores relacionales
Los operadores relacionales comparan valores entre sí. Los operadores de comparación son = , <> , < , > ,
<= y >= .
RelationalOperatorExpression
: Expression '=' LineTerminator? Expression
| Expression '<' '>' LineTerminator? Expression
| Expression '<' LineTerminator? Expression
| Expression '>' LineTerminator? Expression
| Expression '<' '=' LineTerminator? Expression
| Expression '>' '=' LineTerminator? Expression
;
Tipo de operación:
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
EE US En UI Lo UL De Si Co Err Err Co OB
. sas sas
U qu qu
U. e e
ha ha
cer cer
In En Lo Lo De De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
UI UI Lo UL De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Lo Lo De De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
UL UL De Si Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Re De Si Co Err Err Co OB
sg sas sas
ua qu qu
rd e e
o ha ha
cer cer
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
Sí Co Err Err Co OB
sas sas
qu qu
e e
ha ha
cer cer
Ca Ca Pri OB
m m me
r
Pri Pri OB
m me
er r
O OB
B
Like (Operador)
El Like operador determina si una cadena coincide con un patrón determinado.
LikeOperatorExpression
: Expression 'Like' LineTerminator? Expression
;
El Like operador se define para el String tipo. El primer operando es la cadena con la que se encuentra la
coincidencia y el segundo operando es el patrón con el que debe coincidir. El patrón se compone de caracteres
Unicode. Las secuencias de caracteres siguientes tienen significados especiales:
El carácter ? coincide con cualquier carácter individual.
El carácter * coincide con cero o más caracteres.
El carácter # coincide con cualquier dígito individual (0-9).
Una lista de caracteres entre corchetes ( [ab...] ) coincide con cualquier carácter individual de la lista.
Una lista de caracteres rodeados por corchetes y prefijados por un signo de exclamación ( [!ab...] )
coincide con cualquier carácter individual que no esté en la lista de caracteres.
Dos caracteres de una lista de caracteres separados por un guión ( - ) especifican un intervalo de
caracteres Unicode empezando por el primer carácter y terminando por el segundo carácter. Si el
segundo carácter no es más adelante en el criterio de ordenación que el primer carácter, se produce una
excepción en tiempo de ejecución. Un guion que aparece al principio o al final de una lista de caracteres
se especifica a sí mismo.
Para que coincida con los caracteres especiales, corchete de apertura ( [ ), signo de interrogación ( ? ), signo
de número ( # ) y asterisco ( * ), los corchetes deben encerrarlos. El corchete de cierre ( ] ) no se puede usar
dentro de un grupo para que coincida, pero se puede usar fuera de un grupo como un carácter individual. La
secuencia de caracteres [] se considera el literal de cadena "" .
Tenga en cuenta que las comparaciones de caracteres y el orden de las listas de caracteres dependen del tipo de
comparaciones que se usan. Si se usan comparaciones binarias, las comparaciones de caracteres y la ordenación
se basan en los valores numéricos de Unicode. Si se usan comparaciones de texto, las comparaciones de
caracteres y la ordenación se basan en la configuración regional actual que se usa en el .NET Framework.
En algunos idiomas, los caracteres especiales del alfabeto representan dos caracteres independientes y
viceversa. Por ejemplo, varios lenguajes utilizan el carácter æ para representar los caracteres a y e cuando
aparecen juntos, mientras que los caracteres ^ y O se pueden utilizar para representar el carácter Ô . Al usar
comparaciones de texto, el Like operador reconoce dichas equivalencias culturales. En ese caso, una aparición
del carácter especial individual en cualquier patrón o cadena coincide con la secuencia de dos caracteres
equivalente en la otra cadena. De forma similar, un solo carácter especial en el patrón entre corchetes (por sí
solo, en una lista o en un intervalo) coincide con la secuencia de dos caracteres equivalentes de la cadena y
viceversa.
En una Like expresión en la que ambos operandos son Nothing o un operando tiene una conversión
intrínseca en String y el otro operando es Nothing , Nothing se trata como si fuera el literal de cadena vacío
"" .
Tipo de operación:
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
Bo Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
me me me me me me me me me me me me me me me
r r r r r r r r r r r r r r r
G Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
R me me me me me me me me me me me me me me
r r r r r r r r r r r r r r
Co Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
m me me me me me me me me me me me me me
o r r r r r r r r r r r r r
Str Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
a me me me me me me me me me me me me
r r r r r r r r r r r r
EE Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
. me me me me me me me me me me me
U r r r r r r r r r r r
U.
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
In Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
me me me me me me me me me me
r r r r r r r r r r
Ca Pri Pri OB
m me me
r r
Pri Pri OB
m me
er r
O OB
B
Operadores de concatenación
ConcatenationOperatorExpression
: Expression '&' LineTerminator? Expression
;
El operador de concatenación se define para todos los tipos intrínsecos, incluidas las versiones que aceptan
valores NULL de los tipos de valor intrínsecos. También se define para la concatenación entre los tipos
mencionados anteriormente y System.DBNull , que se trata como una Nothing cadena. El operador de
concatenación convierte todos sus operandos en String ; en la expresión, se considera que todas las
conversiones a String son de ampliación, independientemente de si se utiliza la semántica estricta. Un
System.DBNull valor se convierte en el literal con Nothing tipo String . Un tipo de valor que acepta valores
NULL cuyo valor se Nothing convierte también en el literal Nothing con tipo String , en lugar de producir un
error en tiempo de ejecución.
Una operación de concatenación produce una cadena que es la concatenación de los dos operandos en orden
de izquierda a derecha. El valor Nothing se trata como si fuera el literal de cadena vacío "" .
Tipo de operación:
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
Bo Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
me me me me me me me me me me me me me me me
r r r r r r r r r r r r r r r
G Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
R me me me me me me me me me me me me me me
r r r r r r r r r r r r r r
Co Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
m me me me me me me me me me me me me me
o r r r r r r r r r r r r r
Str Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
a me me me me me me me me me me me me
r r r r r r r r r r r r
EE Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
. me me me me me me me me me me me
U r r r r r r r r r r r
U.
In Pri Pri Pri Pri Pri Pri Pri Pri Pri Pri OB
me me me me me me me me me me
r r r r r r r r r r
Ca Pri Pri OB
m me me
r r
Pri Pri OB
m me
er r
O OB
B
Operadores lógicos
Los And Not operadores,, Or y Xor se denominan operadores lógicos.
LogicalOperatorExpression
: 'Not' Expression
| Expression 'And' LineTerminator? Expression
| Expression 'Or' LineTerminator? Expression
| Expression 'Xor' LineTerminator? Expression
;
Or se evalúa como true si alguno de los operandos es true; false es que ambos operandos son
false; Nothing en caso contrario,.
Por ejemplo:
Module Test
Sub Main()
Dim x?, y? As Boolean
x = Nothing
y = True
If x Or y Then
' Will execute
End If
End Sub
End Module
Nota. Idealmente, los operadores lógicos And y Or se elevarían con la lógica de tres valores para cualquier
tipo que se pueda usar en una expresión booleana (es decir, un tipo que implemente IsTrue y IsFalse ), de la
misma manera que y cortocircuita en AndAlso OrElse cualquier tipo que se pueda usar en una expresión
booleana. Desafortunadamente, el levantamiento de tres valores solo se aplica a Boolean? , por lo que los tipos
definidos por el usuario que deseen lógica de tres valores deben hacerlo manualmente definiendo And Or los
operadores y para su versión que acepta valores NULL.
No es posible realizar desbordamientos en estas operaciones. Los operadores de tipo enumerados realizan la
operación bit a bit en el tipo subyacente del tipo enumerado, pero el valor devuelto es el tipo enumerado.
Tipo de operación not:
RES
GU P RI
CO ST EE. AR CA ME
BO GR MO RA UU. IN UI LO UL DO & SÍ & M R OB
EE US En UI Lo UL Lo Lo Lo Err Err Lo OB
.
U
U.
In En Lo Lo Lo Lo Lo Lo Err Err Lo OB
UI UI Lo UL Lo Lo Lo Err Err Lo OB
Lo Lo Lo Lo Lo Lo Err Err Lo OB
UL UL Lo Lo Lo Err Err Lo OB
Re Lo Lo Lo Err Err Lo OB
sg
ua
rd
o
Sí Lo Err Err Lo OB
Pri Lo OB
m
er
O OB
B
Los AndAlso OrElse operadores y se definen para el tipo Boolean , o para cualquier tipo T que sobrecargue
los operadores siguientes:
Al evaluar los AndAlso OrElse operadores o, el primer operando se evalúa solo una vez y el segundo operando
no se evalúa ni se evalúa exactamente una vez. Por ejemplo, considere el siguiente código:
Module Test
Function TrueValue() As Boolean
Console.Write(" True")
Return True
End Function
Sub Main()
Console.Write("And:")
If FalseValue() And TrueValue() Then
End If
Console.WriteLine()
Console.Write("Or:")
If TrueValue() Or FalseValue() Then
End If
Console.WriteLine()
Console.Write("AndAlso:")
If FalseValue() AndAlso TrueValue() Then
End If
Console.WriteLine()
Console.Write("OrElse:")
If TrueValue() OrElse FalseValue() Then
End If
Console.WriteLine()
End Sub
End Module
En la forma de elevación de los AndAlso OrElse operadores y, si el primer operando era un valor null
Boolean? , se evalúa el segundo operando, pero el resultado siempre es un valor null Boolean? .
Tipo de operación:
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
Bo Bo Bo Bo Bo Bo Bo Bo Bo Bo Bo Bo Bo Err Err Bo OB
G Bo Bo Bo Bo Bo Bo Bo Bo Bo Bo Bo Err Err Bo OB
R
Co Bo Bo Bo Bo Bo Bo Bo Bo Bo Bo Err Err Bo OB
m
o
RE
SG
CO EE. UA PR
M ST U RD CA IM
BO GR O RA U. IN UI LO UL O & SÍ & M ER OB
EE Bo Bo Bo Bo Bo Bo Bo Bo Err Err Bo OB
.
U
U.
In Bo Bo Bo Bo Bo Bo Bo Err Err Bo OB
UI Bo Bo Bo Bo Bo Bo Err Err Bo OB
Lo Bo Bo Bo Bo Bo Err Err Bo OB
UL Bo Bo Bo Bo Err Err Bo OB
Re Bo Bo Bo Err Err Bo OB
sg
ua
rd
o
Sí Bo Err Err Bo OB
Pri Bo OB
m
er
O OB
B
Operadores de desplazamiento
Los operadores binarios << y >> realizan operaciones de desplazamiento de bits.
ShiftOperatorExpression
: Expression '<' '<' LineTerminator? Expression
| Expression '>' '>' LineTerminator? Expression
;
Los operadores se definen para los Byte SByte tipos,,,,, UShort Short UInteger Integer ULong y Long . A
diferencia de otros operadores binarios, el tipo de resultado de una operación de desplazamiento se determina
como si el operador fuera un operador unario con solo el operando izquierdo. El tipo del operando derecho se
debe poder convertir implícitamente a Integer y no se utiliza para determinar el tipo de resultado de la
operación.
El << operador hace que los bits del primer operando se desplacen a la izquierda el número de posiciones
especificado por la cantidad de desplazamiento. Los bits de orden superior fuera del intervalo del tipo de
resultado se descartan y las posiciones de bits vacantes de orden inferior se rellenan con ceros.
El >> operador hace que los bits del primer operando se desplacen a la derecha el número de posiciones
especificado por la cantidad de desplazamiento. Los bits de orden inferior se descartan y las posiciones de bits
vacantes de orden superior se establecen en cero si el operando izquierdo es positivo o en uno si es negativo. Si
el operando izquierdo es de tipo Byte , UShort , UInteger o ULong los bits de orden superior vacíos se
rellenan con ceros.
Los operadores de desplazamiento desplazan los bits de la representación subyacente del primer operando por
la cantidad del segundo operando. Si el valor del segundo operando es mayor que el número de bits del primer
operando, o es negativo, la cantidad de desplazamiento se calcula como RightOperand And SizeMask donde
SizeMask es:
T IP O L EF TO P ERA N D SIZ EM A SK
Si la cantidad de desplazamiento es cero, el resultado de la operación es idéntico al valor del primer operando.
No es posible realizar desbordamientos en estas operaciones.
Tipo de operación:
RES
GU P RI
CO ST EE. AR CA ME
BO GR MO RA UU. IN UI LO UL DO & SÍ & M R OB
Expresiones booleanas
Una expresión booleana es una expresión que se puede probar para ver si es true o si es false.
BooleanExpression
: Expression
;
Nota. Es interesante tener en cuenta que Option Strict , si está desactivada, se aceptará una expresión que
tenga una conversión de restricción Boolean sin un error en tiempo de compilación, pero el lenguaje seguirá
preferentemente a un IsTrue operador si existe. Esto se debe a Option Strict que solo cambia lo que el
lenguaje no acepta y no cambia nunca el significado real de una expresión. Por lo tanto, IsTrue se debe preferir
siempre a través de una conversión de restricción, independientemente de Option Strict .
Por ejemplo, la clase siguiente no define una conversión de ampliación a Boolean . Como resultado, su uso en la
If instrucción produce una llamada al IsTrue operador.
Class MyBool
Public Shared Widening Operator CType(b As Boolean) As MyBool
...
End Operator
Module Test
Sub Main()
Dim b As New MyBool
If b Then Console.WriteLine("True")
End Sub
End Module
Si una expresión booleana se escribe como o se convierte en Boolean o Boolean? , es true si el valor es True y
false en caso contrario.
De lo contrario, una expresión booleana llama al IsTrue operador y devuelve True si el operador devuelve
True ; de lo contrario, es false (pero nunca llama al IsFalse operador).
En el ejemplo siguiente, Integer tiene una conversión de restricción en Boolean , por lo que un valor null
Integer? tiene una conversión de restricción a ambos Boolean? (produciendo un valor null Boolean ) y a
Boolean (que produce una excepción). La conversión de restricción en Boolean? es preferible, por lo que el
valor de " i " como una expresión booleana es, por tanto, False .
Expresiones lambda
Una expresión lambda define un método anónimo denominado método lambda. Los métodos lambda facilitan
el paso de métodos "en línea" a otros métodos que toman tipos de delegado.
LambdaExpression
: SingleLineLambda
| MultiLineLambda
;
SingleLineLambda
: LambdaModifier* 'Function' ( OpenParenthesis ParameterList? CloseParenthesis )? Expression
| 'Sub' ( OpenParenthesis ParameterList? CloseParenthesis )? Statement
;
MultiLineLambda
: MultiLineFunctionLambda
| MultiLineSubLambda
;
MultiLineFunctionLambda
: LambdaModifier? 'Function' ( OpenParenthesis ParameterList? CloseParenthesis )? ( 'As' TypeName )?
LineTerminator
Block
'End' 'Function'
;
MultiLineSubLambda
: LambdaModifier? 'Sub' ( OpenParenthesis ParameterList? CloseParenthesis )? LineTerminator
Block
'End' 'Sub'
;
LambdaModifier
: 'Async' | 'Iterator'
;
El ejemplo:
Module Test
Delegate Function IntFunc(x As Integer) As Integer
Sub Main()
Dim a() As Integer = { 1, 2, 3, 4 }
se imprimirá:
2 4 6 8
Una expresión lambda comienza con los modificadores opcionales Async o Iterator , seguido de la palabra
clave Function o Sub y una lista de parámetros. Los parámetros de una expresión lambda no se pueden
declarar Optional o ParamArray y no pueden tener atributos. A diferencia de los métodos normales, omitir un
tipo de parámetro para un método lambda no se deduce automáticamente Object . En su lugar, cuando se
reclasifica un método lambda, los tipos de parámetros y ByRef Modificadores omitidos se deducen del tipo de
destino. En el ejemplo anterior, la expresión lambda podría haberse escrito como Function(x) x * 2 y habría
deducido el tipo de x para que Integer fuera cuando se usaba el método lambda para crear una instancia del
IntFunc tipo de delegado. A diferencia de la inferencia de variables locales, si un parámetro de método lambda
omite un tipo pero tiene una matriz o un modificador de nombre que acepta valores NULL, se produce un error
en tiempo de compilación.
Una expresión lambda regular es una sin Async Iterator Modificadores ni.
Una expresión lambda de iterador es una con el Iterator modificador y ningún Async modificador. Debe
ser una función. Cuando se reclasifica a un valor, solo se puede reclasificar a un valor de tipo delegado cuyo tipo
de valor devuelto sea IEnumerator , o IEnumerable , o IEnumerator(Of T) o IEnumerable(Of T) para algunos T
, y que no tenga ningún parámetro ByRef.
Una expresión lambda asincrónica es aquella con el Async modificador y ningún Iterator modificador.
Una expresión lambda sub asincrónica solo se puede reclasificar a un valor de tipo sub Delegate sin parámetros
ByRef. Una expresión lambda de función asincrónica solo se puede reclasificar a un valor de tipo de delegado de
función cuyo tipo de valor devuelto sea Task o Task(Of T) para algunos T , y que no tenga parámetros ByRef.
Las expresiones lambda pueden ser de una o varias líneas. Las expresiones lambda de una sola línea Function
contienen una expresión única que representa el valor devuelto desde el método lambda. Las Sub expresiones
lambda de una sola línea contienen una sola instrucción sin su cierre StatementTerminator . Por ejemplo:
Module Test
Sub Do(a() As Integer, action As Action(Of Integer))
For index As Integer = 0 To a.Length - 1
action(a(index))
Next index
End Sub
Sub Main()
Dim a() As Integer = { 1, 2, 3, 4 }
Las construcciones lambda de una sola línea se enlazan de forma menos estrecha que las demás expresiones e
instrucciones. Por lo tanto, por ejemplo, " Function() x + 5 " es equivalente a " Function() (x+5)" en lugar de"
(Function() x) + 5 ". Para evitar ambigüedades, una expresión lambda de una sola línea Sub no puede
contener una instrucción Dim o una instrucción de declaración de etiqueta. Además, a menos que se incluya
entre paréntesis, una expresión lambda de una sola línea Sub no puede ir seguida inmediatamente de un signo
de dos puntos ":", un operador de acceso a miembro ".", un operador de acceso a miembros de diccionario "!" o
un paréntesis de apertura "(". No puede contener ninguna instrucción Block ( With , SyncLock, If...EndIf ,
While , For , Do , Using ) ni OnError Resume .
Nota. En la expresión lambda Function(i) x=i , el cuerpo se interpreta como una expresión (que comprueba si
x y i son iguales). Pero en la expresión lambda Sub(i) x=i , el cuerpo se interpreta como una instrucción
(que se asigna i a x ).
Una expresión lambda de varias líneas contiene un bloque de instrucciones y debe terminar con una End
instrucción adecuada (es decir, End Function o End Sub ). Al igual que con los métodos regulares, las
instrucciones o instrucción de un método lambda de varias líneas Function Sub End deben estar en sus
propias líneas. Por ejemplo:
' Error: Function statement must be on its own line!
Dim x = Sub(x As Integer) : Console.WriteLine(x) : End Sub
' OK
Dim y = Sub(x As Integer)
Console.WriteLine(x)
End Sub
Las expresiones lambda de varias líneas Function pueden declarar un tipo de valor devuelto, pero no pueden
colocar atributos en él. Si una expresión lambda de varias líneas Function no declara un tipo de valor devuelto,
pero el tipo de valor devuelto se puede deducir del contexto en el que se usa la expresión lambda, se utiliza ese
tipo de valor devuelto. De lo contrario, el tipo de valor devuelto de la función se calcula de la siguiente manera:
En una expresión lambda regular, el tipo de valor devuelto es el tipo dominante de las expresiones en
todas las Return instrucciones del bloque de instrucciones.
En una expresión lambda asincrónica, el tipo de valor devuelto es Task(Of T) donde T es el tipo
dominante de las expresiones en todas las Return instrucciones del bloque de instrucciones.
En una expresión lambda de iterador, el tipo de valor devuelto es IEnumerable(Of T) donde T es el tipo
dominante de las expresiones en todas las Yield instrucciones del bloque de instrucciones.
Por ejemplo:
En todos los casos, si no hay Return instrucciones (respectivamente Yield ) o si no hay ningún tipo dominante
entre ellas y se usa la semántica estricta, se produce un error en tiempo de compilación; de lo contrario, el tipo
dominante es implícitamente Object .
Tenga en cuenta que el tipo de valor devuelto se calcula a partir de todas las Return instrucciones, aunque no
estén accesibles. Por ejemplo:
No hay ninguna variable de devolución implícita, ya que no hay ningún nombre para la variable.
Los bloques de instrucciones dentro de las expresiones lambda de varias líneas tienen las restricciones
siguientes:
On Error``Resume no se permiten las instrucciones y, aunque Try se permiten las instrucciones.
Las variables locales estáticas no se pueden declarar en expresiones lambda de varias líneas.
No es posible crear una bifurcación dentro o fuera del bloque de instrucciones de una expresión lambda
de varias líneas, aunque las reglas de bifurcación normales se aplican dentro de ella. Por ejemplo:
Label1:
Dim x = Sub()
' Error: Cannot branch out
GoTo Label1
Una expresión lambda es aproximadamente equivalente a un método anónimo declarado en el tipo contenedor.
El ejemplo inicial es aproximadamente equivalente a:
Module Test
Delegate Function IntFunc(x As Integer) As Integer
Sub Main()
Dim a() As Integer = { 1, 2, 3, 4 }
Cierres
Las expresiones lambda tienen acceso a todas las variables del ámbito, incluidas las variables locales o los
parámetros definidos en el método contenedor y las expresiones lambda. Cuando una expresión lambda hace
referencia a una variable o parámetro local, la expresión lambda captura la variable a la que se hace referencia
en un cierre. Un cierre es un objeto que reside en el montón en lugar de en la pila y, cuando se captura una
variable, todas las referencias a la variable se redirigen al cierre. Esto permite que las expresiones lambda sigan
haciendo referencia a variables y parámetros locales, incluso después de que se complete el método contenedor.
Por ejemplo:
Module Test
Delegate Function D() As Integer
Function M() As D
Dim x As Integer = 10
Return Function() x
End Function
Sub Main()
Dim y As D = M()
' Prints 10
Console.WriteLine(y())
End Sub
End Module
es aproximadamente equivalente a:
Module Test
Delegate Function D() As Integer
Class $Closure1
Public x As Integer
Function M() As D
Dim c As New $Closure1()
c.x = 10
Return AddressOf c.$Lambda1
End Function
Sub Main()
Dim y As D = M()
' Prints 10
Console.WriteLine(y())
End Sub
End Module
Un cierre captura una nueva copia de una variable local cada vez que entra en el bloque en el que se declara la
variable local, pero la nueva copia se inicializa con el valor de la copia anterior, si existe. Por ejemplo:
Module Test
Delegate Function D() As Integer
For i As Integer = 0 To 9
Dim x
a(i) = Function() x
x += 1
Next i
Return a
End Function
Sub Main()
Dim y() As D = M()
For i As Integer = 0 To 9
Console.Write(y(i)() & " ")
Next i
End Sub
End Module
impresiones
1 2 3 4 5 6 7 8 9 10
en lugar de
9 9 9 9 9 9 9 9 9 9
Dado que los cierres se deben inicializar al entrar en un bloque, no se permite en GoTo un bloque con un cierre
desde fuera de ese bloque, aunque se permite en Resume un bloque con un cierre. Por ejemplo:
Module Test
Sub Main()
Dim a = 10
If a = 10 Then
L1:
Dim x = Function() a
Dado que no se pueden capturar en una clausura, los siguientes elementos no pueden aparecer dentro de una
expresión lambda:
Parámetros de referencia.
Expresiones de instancia ( Me , MyClass , MyBase ) si el tipo de Me no es una clase.
Los miembros de una expresión de creación de tipos anónima, si la expresión lambda forma parte de la
expresión. Por ejemplo:
Class C1
ReadOnly F1 As Integer
Sub New()
' Valid, doesn't modify F1
Dim x = Function() F1
Expresiones de consulta
Una expresión de consulta es una expresión que aplica una serie de operadores de consulta a los elementos de
una colección consultable . Por ejemplo, la siguiente expresión toma una colección de Customer objetos y
devuelve los nombres de todos los clientes en el estado de Washington:
Dim names = _
From cust In Customers _
Where cust.State = "WA" _
Select cust.Name
Una expresión de consulta debe comenzar con un From Aggregate operador o y puede terminar con cualquier
operador de consulta. El resultado de una expresión de consulta se clasifica como un valor. el tipo de resultado
de la expresión depende del tipo de resultado del último operador de consulta en la expresión.
QueryExpression
: FromOrAggregateQueryOperator QueryOperator*
;
FromOrAggregateQueryOperator
: FromQueryOperator
| AggregateQueryOperator
;
QueryOperator
: FromQueryOperator
| AggregateQueryOperator
| SelectQueryOperator
| DistinctQueryOperator
| WhereQueryOperator
| OrderByQueryOperator
| PartitionQueryOperator
| LetQueryOperator
| GroupByQueryOperator
| JoinOrGroupJoinQueryOperator
;
JoinOrGroupJoinQueryOperator
: JoinQueryOperator
| GroupJoinQueryOperator
;
Variables de rango
Algunos operadores de consulta introducen un tipo especial de variable denominada variable de rango. Las
variables de rango no son variables reales; en su lugar, representan los valores individuales durante la
evaluación de la consulta a través de las colecciones de entrada.
CollectionRangeVariableDeclarationList
: CollectionRangeVariableDeclaration ( Comma CollectionRangeVariableDeclaration )*
;
CollectionRangeVariableDeclaration
: Identifier ( 'As' TypeName )? 'In' LineTerminator? Expression
;
ExpressionRangeVariableDeclarationList
: ExpressionRangeVariableDeclaration ( Comma ExpressionRangeVariableDeclaration )*
;
ExpressionRangeVariableDeclaration
: Identifier ( 'As' TypeName )? Equals Expression
;
Las variables de rango se limitan desde el operador de consulta Introducing hasta el final de una expresión de
consulta, o hasta un operador de consulta como Select que los oculte. Por ejemplo, en la siguiente consulta
Dim waCusts = _
From cust As Customer In Customers _
Where cust.State = "WA"
el From operador de consulta introduce una variable de rango cust con el tipo Customer que representa a
cada cliente de la Customers colección. El siguiente Where operador de consulta hace referencia a la variable
cust de rango de la expresión de filtro para determinar si se debe filtrar un cliente individual de la colección
resultante.
Hay dos tipos de variables de rango: las variables de rango de colección y las variables de rango de expresión.
Las variables de rango de colección toman sus valores de los elementos de las colecciones que se consultan. La
expresión de colección en una declaración de variable de rango de colección debe estar clasificada como un
valor cuyo tipo es consultable. Si se omite el tipo de una variable de rango de colección, se deduce que es el tipo
de elemento de la expresión de colección, o bien, Object si la expresión de colección no tiene un tipo de
elemento (es decir, solo define un Cast método). Si la expresión de colección no es consultable (es decir, no se
puede inferir el tipo de elemento de la colección), se produce un error en tiempo de compilación.
Una variable de rango de expresión es una variable de rango cuyo valor se calcula mediante una expresión en
lugar de una colección. En el ejemplo siguiente, el Select operador de consulta introduce una variable de rango
cityState de expresión denominada calculada a partir de dos campos:
Dim cityStates = _
From cust As Customer In Customers _
Select cityState = cust.City & "," & cust.State _
Where cityState.Length() < 10
No es necesario que una variable de rango de expresión haga referencia a otra variable de rango, aunque dicha
variable puede ser de un valor dudoso. La expresión asignada a una variable de rango de expresión se debe
clasificar como un valor y se debe poder convertir implícitamente al tipo de la variable de rango, si se especifica.
Solo en un operador Let puede que se especifique el tipo de una variable de rango de expresión. En otros
operadores, o si no se especifica su tipo, se usa la inferencia de tipo de variable local para determinar el tipo de
la variable de rango.
Una variable de rango debe seguir las reglas para declarar variables locales en lo que respecta a la ocultación.
Por lo tanto, una variable de rango no puede ocultar el nombre de una variable local o un parámetro en el
método envolvente o en otra variable de rango (a menos que el operador de consulta oculte específicamente
todas las variables de rango actuales en el ámbito).
Tipos consultables
Las expresiones de consulta se implementan mediante la conversión de la expresión en llamadas a métodos
conocidos en un tipo de colección. Estos métodos bien definidos definen el tipo de elemento de la colección
consultable, así como los tipos de resultado de los operadores de consulta que se ejecutan en la colección. Cada
operador de consulta especifica el método o los métodos a los que se suele traducir el operador de consulta,
aunque la traducción específica depende de la implementación. Los métodos se proporcionan en la
especificación mediante un formato general que tiene el siguiente aspecto:
el resultado de la consulta será un tipo de colección con un tipo de elemento de String . Si hay varias
variables de rango en el ámbito, R es un tipo anónimo que contiene todas las variables de rango en el
ámbito como Key campos. En el ejemplo:
el resultado de la consulta será un tipo de colección con un tipo de elemento de un tipo anónimo con una
propiedad de solo lectura denominada Name de tipo String y una propiedad de solo lectura
denominada ProductName de tipo String .
Dentro de una expresión de consulta, los tipos anónimos generados para contener variables de rango
son transparentes, lo que significa que las variables de rango siempre están disponibles sin calificación.
Por ejemplo, en el ejemplo anterior se c podía tener acceso a las variables de rango y o sin calificación
en el Select operador de consulta, aunque el tipo de elemento de la colección de entrada era un tipo
anónimo.
El tipo CX representa un tipo de colección, no necesariamente el tipo de colección de entrada, cuyo tipo
de elemento es algún tipo X .
Un tipo de colección consultable debe cumplir una de las siguientes condiciones, en orden de preferencia:
Debe definir un Select método compatible.
Debe tener uno de los métodos siguientes:
Function AsEnumerable() As CT
Function AsQueryable() As CT
al que se puede llamar para obtener una colección consultable. Si se proporcionan ambos métodos,
AsQueryable se prefiere AsEnumerable .
que se puede llamar con el tipo de la variable de rango para generar una colección consultable.
Dado que determinar el tipo de elemento de una colección se produce independientemente de una invocación
de método real, no se puede determinar la aplicabilidad de métodos concretos. Por lo tanto, al determinar el tipo
de elemento de una colección si hay métodos de instancia que coinciden con métodos conocidos, se omiten los
métodos de extensión que coinciden con los métodos conocidos.
La conversión del operador de consulta se produce en el orden en que se producen los operadores de consulta
en la expresión. No es necesario que un objeto de colección implemente todos los métodos que necesitan todos
los operadores de consulta, aunque cada objeto de colección debe ser compatible al menos con el Select
operador de consulta. Si no hay un método necesario, se produce un error en tiempo de compilación. Cuando se
enlazan nombres de método conocidos, los métodos que no son de se pasan por alto para la herencia múltiple
en interfaces y enlace de método de extensión, aunque se sigue aplicando la semántica de sombreado. Por
ejemplo:
Class Q1
Public Function [Select](selector As Func(Of Integer, Integer)) As Q1
End Function
End Class
Class Q2
Inherits Q1
Module Test
Sub Main()
Dim qs As New Q2()
Solo se puede hacer referencia a la propiedad predeterminada con la sintaxis de acceso de propiedad
predeterminada; no se puede hacer referencia a la propiedad predeterminada por el nombre. Por ejemplo:
FromQueryOperator
: LineTerminator? 'From' LineTerminator? CollectionRangeVariableDeclarationList
;
Cuando un From operador de consulta declara varias variables de rango de colección o no es el primer From
operador de consulta en la expresión de consulta, cada nueva variable de rango de colección se combina con el
conjunto existente de variables de rango. El resultado es que la consulta se evalúa en el producto cruzado de
todos los elementos de las colecciones Unidas. Por ejemplo, la expresión:
From c In Customers _
From e In Employees _
...
y es exactamente equivalente a:
Las variables de rango introducidas en operadores de consulta anteriores se pueden usar en un From operador
de consulta posterior. Por ejemplo, en la siguiente expresión de consulta, el segundo From operador de consulta
hace referencia al valor de la primera variable de rango:
Solo se admiten varias variables de rango en un From operador de consulta o en varios From operadores de
consulta si el tipo de colección contiene uno o ambos de los métodos siguientes:
Function SelectMany(selector As Func(Of T, CR)) As CR
Function SelectMany(selector As Func(Of T, CS), _
resultsSelector As Func(Of T, S, R)) As CR
El código.
normalmente se traduce a
JoinQueryOperator
: LineTerminator? 'Join' LineTerminator? CollectionRangeVariableDeclaration
JoinOrGroupJoinQueryOperator? LineTerminator? 'On' LineTerminator? JoinConditionList
;
JoinConditionList
: JoinCondition ( 'And' LineTerminator? JoinCondition )*
;
JoinCondition
: Expression 'Equals' LineTerminator? Expression
;
Por ejemplo:
Dim customersAndOrders = _
From cust In Customers _
Join ord In Orders On cust.ID Equals ord.CustomerID
El código.
normalmente se traduce a
LetQueryOperator
: LineTerminator? 'Let' LineTerminator? ExpressionRangeVariableDeclarationList
;
Por ejemplo:
Dim taxedPrices = _
From o In Orders _
Let tax = o.Price * 0.088 _
Where tax > 3.50 _
Select o.Price, tax, total = o.Price + tax
El código.
normalmente se traduce a
SelectQueryOperator
: LineTerminator? 'Select' LineTerminator? ExpressionRangeVariableDeclarationList
;
Dim smiths = _
From cust In Customers _
Select name = cust.name _
Where name.EndsWith("Smith")
el Where operador de consulta solo tiene acceso a la name variable de rango introducida por el Select
operador; si el Where operador ha intentado hacer referencia a cust , se habría producido un error en tiempo
de compilación.
En lugar de especificar explícitamente los nombres de las variables de rango, un Select operador de consulta
puede deducir los nombres de las variables de rango, con las mismas reglas que las expresiones de creación de
objetos de tipo anónimo. Por ejemplo:
Dim custAndOrderNames = _
From cust In Customers, ord In cust.Orders _
Select cust.name, ord.ProductName _
Where name.EndsWith("Smith")
Dim custAndOrderNames = _
From cust In Customers, ord In cust.Orders _
Select cust.Name & " bought " & ord.ProductName _
Take 10
Si hay ambigüedad en un Select operador de consulta entre la asignación de un nombre a una variable de
rango y una expresión de igualdad, se prefiere la asignación de nombre. Por ejemplo:
Dim badCustNames = _
From c In Customers _
Let name = "John Smith" _
Select name = c.Name ' Creates a range variable named "name"
Dim goodCustNames = _
From c In Customers _
Let name = "John Smith" _
Select match = (name = c.Name)
Cada expresión del Select operador de consulta se debe clasificar como un valor. Un Select operador de
consulta solo se admite si el tipo de colección contiene un método:
El código.
normalmente se traduce a
Dim distinctCustomerPrice = _
From cust In Customers, ord In cust.Orders _
Select cust.Name, ord.Price _
Distinct
solo devolverá una fila por cada emparejamiento diferente del precio del nombre y del pedido del cliente,
incluso si el cliente tiene varios pedidos con el mismo precio. Un Distinct operador de consulta solo se admite
si el tipo de colección contiene un método:
Function Distinct() As CT
El código.
normalmente se traduce a
WhereQueryOperator
: LineTerminator? 'Where' LineTerminator? BooleanExpression
;
Un Where operador de consulta toma una expresión booleana que se evalúa para cada conjunto de valores de
variable de rango; Si el valor de la expresión es true, los valores aparecen en la colección de salida; en caso
contrario, se omiten los valores. Por ejemplo, la expresión de consulta:
El código.
normalmente se traduce a
PartitionQueryOperator
: LineTerminator? 'Take' LineTerminator? Expression
| LineTerminator? 'Take' 'While' LineTerminator? BooleanExpression
| LineTerminator? 'Skip' LineTerminator? Expression
| LineTerminator? 'Skip' 'While' LineTerminator? BooleanExpression
;
El Take operador de consulta da como resultado los primeros n elementos de una colección. Cuando se usa
con el While modificador, el Take operador produce los primeros n elementos de una colección que
satisfacen una expresión booleana. El Skip operador omite los primeros n elementos de una colección y, a
continuación, devuelve el resto de la colección. Cuando se usa junto con el While modificador, el Skip operador
omite los primeros n elementos de una colección que satisfacen una expresión booleana y, a continuación,
devuelve el resto de la colección. Las expresiones de un Take Skip operador de consulta o se deben clasificar
como un valor.
Un Take operador de consulta solo se admite si el tipo de colección contiene un método:
Function Take(count As N) As CT
Function Skip(count As N) As CT
Un Take While operador de consulta solo se admite si el tipo de colección contiene un método:
Function TakeWhile(predicate As Func(Of T, B)) As CT
Un Skip While operador de consulta solo se admite si el tipo de colección contiene un método:
El código.
normalmente se traduce a
OrderByQueryOperator
: LineTerminator? 'Order' 'By' LineTerminator? OrderExpressionList
;
OrderExpressionList
: OrderExpression ( Comma OrderExpression )*
;
OrderExpression
: Expression Ordering?
;
Ordering
: 'Ascending' | 'Descending'
;
Un Order By operador de consulta toma expresiones que especifican los valores de clave que se deben usar
para ordenar las variables de iteración. Por ejemplo, la consulta siguiente devuelve los productos ordenados por
precio:
Dim productsByPrice = _
From p In Products _
Order By p.Price _
Select p.Name
Una ordenación se puede marcar como Ascending , en cuyo caso los valores más pequeños van antes que los
valores mayores, o Descending , en cuyo caso los valores mayores van antes que los valores más pequeños. El
valor predeterminado para una ordenación si no se especifica ninguno es Ascending . Por ejemplo, la consulta
siguiente devuelve los productos ordenados por precio con el producto más caro en primer lugar:
Dim productsByPriceDesc = _
From p In Products _
Order By p.Price Descending _
Select p.Name
El Order By operador de consulta puede especificar varias expresiones para la ordenación, en cuyo caso la
colección se ordena de manera anidada. Por ejemplo, la consulta siguiente ordena los clientes por estado y, a
continuación, por ciudad en cada Estado y, a continuación, por código postal dentro de cada ciudad:
Dim customersByLocation = _
From c In Customers _
Order By c.State, c.City, c.ZIP _
Select c.Name, c.State, c.City, c.ZIP
Las expresiones de un Order By operador de consulta se deben clasificar como un valor. Un Order By operador
de consulta solo se admite si el tipo de colección contiene uno o los dos métodos siguientes:
El tipo de valor devuelto CT debe ser una colección ordenada. Una colección ordenada es un tipo de colección
que contiene uno o ambos métodos:
El código.
normalmente se traduce a
Nota. Dado que los operadores de consulta simplemente asignan sintaxis a métodos que implementan una
operación de consulta determinada, la preservación del orden no está dictada por el lenguaje y viene
determinada por la implementación del propio operador. Esto es muy similar a los operadores definidos por el
usuario en que la implementación para sobrecargar el operador de suma para un tipo numérico definido por el
usuario no puede hacer nada similar a una adición. Por supuesto, para conservar la capacidad de predicción, no
se recomienda implementar algo que no coincida con las expectativas del usuario.
Nota. Order y By no son palabras reservadas.
Group by (operador de consulta)
El Group By operador de consulta agrupa las variables de rango en el ámbito en función de una o varias
expresiones y, a continuación, genera nuevas variables de intervalo basadas en esas agrupaciones.
GroupByQueryOperator
: LineTerminator? 'Group' ( LineTerminator? ExpressionRangeVariableDeclarationList )?
LineTerminator? 'By' LineTerminator? ExpressionRangeVariableDeclarationList
LineTerminator? 'Into' LineTerminator? ExpressionRangeVariableDeclarationList
;
Por ejemplo, la consulta siguiente agrupa todos los clientes por State y, a continuación, calcula el recuento y la
antigüedad media de cada grupo:
Dim averageAges = _
From cust In Customers _
Group By cust.State _
Into Count(), Average(cust.Age)
El Group By operador de consulta tiene tres cláusulas: la Group cláusula opcional, la By cláusula y la Into
cláusula. La Group cláusula tiene la misma sintaxis y efecto que un Select operador de consulta, salvo que solo
afecta a las variables de rango disponibles en la Into cláusula y no a la By cláusula. Por ejemplo:
Dim averageAges = _
From cust In Customers _
Group cust.Age By cust.State _
Into Count(), Average(Age)
La By cláusula declara las variables de rango de expresión que se usan como valores de clave en la operación
de agrupación. La Into cláusula permite la declaración de variables de rango de expresión que calculan
agregaciones sobre cada uno de los grupos formados por la By cláusula. Dentro de la Into cláusula, solo se
puede asignar una expresión a la variable de rango de expresión, que es una invocación de método de una
función de agregado. Una función de agregado es una función del tipo de colección del grupo (que puede no ser
necesariamente el mismo tipo de colección de la colección original), que es similar a cualquiera de los métodos
siguientes:
Si una función de agregado toma un argumento de delegado, la expresión de invocación puede tener una
expresión de argumento que se debe clasificar como un valor. La expresión de argumento puede usar las
variables de rango que se encuentran en el ámbito; dentro de la llamada a una función de agregado, esas
variables de rango representan los valores del grupo que se va a formar, no todos los valores de la colección.
Por ejemplo, en el ejemplo original de esta sección, la Average función calcula el promedio de las edades de los
clientes por estado en lugar de todos los clientes juntos.
Se considera que todos los tipos de colección tienen definida la función Group de agregado, que no toma
ningún parámetro y simplemente devuelve el grupo. Otras funciones de agregado estándar que puede
proporcionar un tipo de colección son:
Count y LongCount , que devuelven el recuento de los elementos del grupo o el recuento de los elementos del
grupo que satisfacen una expresión booleana. Count y LongCount solo se admiten si el tipo de colección
contiene uno de los métodos:
Function Count() As N
Function Count(selector As Func(Of T, B)) As N
Function LongCount() As N
Function LongCount(selector As Func(Of T, B)) As N
Sum , que devuelve la suma de una expresión en todos los elementos del grupo. Sum solo se admite si el tipo de
colección contiene uno de los métodos:
Function Sum() As N
Function Sum(selector As Func(Of T, N)) As N
Min que devuelve el valor mínimo de una expresión en todos los elementos del grupo. Min solo se admite si el
tipo de colección contiene uno de los métodos:
Function Min() As N
Function Min(selector As Func(Of T, N)) As N
Max , que devuelve el valor máximo de una expresión en todos los elementos del grupo. Max solo se admite si
el tipo de colección contiene uno de los métodos:
Function Max() As N
Function Max(selector As Func(Of T, N)) As N
Average , que devuelve el promedio de una expresión en todos los elementos del grupo. Average solo se
admite si el tipo de colección contiene uno de los métodos:
Function Average() As N
Function Average(selector As Func(Of T, N)) As N
Any , que determina si un grupo contiene miembros o si una expresión booleana es true para cualquier
elemento del grupo. Any Devuelve un valor que se puede usar en una expresión booleana y solo se admite si el
tipo de colección contiene uno de los métodos:
Function Any() As B
Function Any(predicate As Func(Of T, B)) As B
All , que determina si una expresión booleana es true para todos los elementos del grupo. All Devuelve un
valor que se puede usar en una expresión booleana y solo se admite si el tipo de colección contiene un método:
Después de un Group By operador de consulta, las variables de rango anteriormente en el ámbito están ocultas
y las variables de rango introducidas por las By Into cláusulas y están disponibles. Un Group By operador de
consulta solo se admite si el tipo de colección contiene el método:
Las declaraciones de variable de rango de la Group cláusula solo se admiten si el tipo de colección contiene el
método:
Function GroupBy(keySelector As Func(Of T, K), _
elementSelector As Func(Of T, S), _
resultSelector As Func(Of K, CS, R)) As CR
El código.
normalmente se traduce a
AggregateQueryOperator
: LineTerminator? 'Aggregate' LineTerminator? CollectionRangeVariableDeclaration QueryOperator*
LineTerminator? 'Into' LineTerminator? ExpressionRangeVariableDeclarationList
;
Por ejemplo, la consulta siguiente agrega el total de todos los pedidos realizados por los clientes en Washington:
Dim orderTotals = _
From cust In Customers _
Where cust.State = "WA" _
Aggregate order In cust.Orders _
Into Sum(order.Total)
El resultado de esta consulta es una colección cuyo tipo de elemento es un tipo anónimo con una propiedad
denominada con cust tipo Customer y una propiedad denominada con el nombre Sum Integer .
A diferencia Group By de, se pueden colocar operadores de consulta adicionales entre las Aggregate Into
cláusulas y. Entre una Aggregate cláusula y el final de la Into cláusula, se pueden usar todas las variables de
rango del ámbito, incluidas las declaradas por la Aggregate cláusula. Por ejemplo, la consulta siguiente agrega
la suma total de todos los pedidos realizados por los clientes en Washington antes del 2006:
Dim orderTotals = _
From cust In Customers _
Where cust.State = "WA" _
Aggregate order In cust.Orders _
Where order.Date <= #01/01/2006# _
Into Sum = Sum(order.Total)
El Aggregate operador también se puede utilizar para iniciar una expresión de consulta. En este caso, el
resultado de la expresión de consulta será el valor único calculado por la Into cláusula. Por ejemplo, la consulta
siguiente calcula la suma de todos los totales del pedido antes del 1 de enero de 2006:
Dim ordersTotal = _
Aggregate order In Orders _
Where order.Date <= #01/01/2006# _
Into Sum(order.Total)
El resultado de la consulta es un Integer valor único. Un Aggregate operador de consulta está siempre
disponible (aunque la función de agregado también debe estar disponible para que la expresión sea válida). El
código.
normalmente se traduce a
GroupJoinQueryOperator
: LineTerminator? 'Group' 'Join' LineTerminator? CollectionRangeVariableDeclaration
JoinOrGroupJoinQueryOperator? LineTerminator? 'On' LineTerminator? JoinConditionList
LineTerminator? 'Into' LineTerminator? ExpressionRangeVariableDeclarationList
;
Por ejemplo, la siguiente consulta genera elementos que contienen el nombre de un solo cliente, un grupo de
todos sus pedidos y la cantidad total de estos pedidos:
Dim custsWithOrders = _
From cust In Customers _
Group Join order In Orders On cust.ID Equals order.CustomerID _
Into Orders = Group, OrdersTotal = Sum(order.Total) _
Select cust.Name, Orders, OrdersTotal
El resultado de la consulta es una colección cuyo tipo de elemento es un tipo anónimo con tres propiedades:
Name , con tipo como, con String Orders tipo como una colección cuyo tipo de elemento es Order , y
OrdersTotal , con tipo Integer . Un Group Join operador de consulta solo se admite si el tipo de colección
contiene el método:
El código.
normalmente se traduce a
Expresiones condicionales
Una If expresión condicional prueba una expresión y devuelve un valor.
ConditionalExpression
: 'If' OpenParenthesis BooleanExpression Comma Expression Comma Expression CloseParenthesis
| 'If' OpenParenthesis Expression Comma Expression CloseParenthesis
;
Sin embargo, a diferencia de la IIF función en tiempo de ejecución, una expresión condicional solo evalúa sus
operandos si es necesario. Por lo tanto, por ejemplo, la expresión If(c Is Nothing, c.Name, "Unknown") no
producirá una excepción si el valor de c es Nothing . La expresión condicional tiene dos formatos: uno que
toma dos operandos y otro que toma tres operandos.
Si se proporcionan tres operandos, las tres expresiones se deben clasificar como valores y el primer operando
debe ser una expresión booleana. Si el resultado de la expresión es true, la segunda expresión será el resultado
del operador; de lo contrario, la tercera expresión será el resultado del operador. El tipo de resultado de la
expresión es el tipo dominante entre los tipos de la segunda y tercera expresión. Si no hay ningún tipo
dominante, se produce un error en tiempo de compilación.
Si se proporcionan dos operandos, ambos operandos deben estar clasificados como valores y el primer
operando debe ser un tipo de referencia o un tipo de valor que acepte valores NULL. If(x, y) A continuación,
la expresión se evalúa como si fuera la expresión If(x IsNot Nothing, x, y) , con dos excepciones. En primer
lugar, la primera expresión solo se evalúa una vez y, en segundo lugar, si el tipo del segundo operando es un tipo
de valor que no acepta valores NULL y el tipo del primer operando es, ? se quita del tipo del primer operando
al determinar el tipo dominante para el tipo de resultado de la expresión. Por ejemplo:
Module Test
Sub Main()
Dim x?, y As Integer
Dim a?, b As Long
En ambos formatos de la expresión, si un operando es Nothing , su tipo no se utiliza para determinar el tipo
dominante. En el caso de la expresión If(<expression>, Nothing, Nothing) , se considera que el tipo dominante
es Object .
XMLLiteralExpression
: XMLDocument
| XMLElement
| XMLProcessingInstruction
| XMLComment
| XMLCDATASection
;
El resultado de una expresión literal XML es un valor con tipo de uno de los tipos del System.Xml.Linq espacio
de nombres. Si los tipos de ese espacio de nombres no están disponibles, una expresión literal XML producirá
un error en tiempo de compilación. Los valores se generan a través de las llamadas de constructor traducidas
desde la expresión literal XML. Por ejemplo, el código:
Una expresión literal XML puede adoptar la forma de un documento XML, un elemento XML, una instrucción de
procesamiento XML, un comentario XML o una sección CDATA.
Nota. Esta especificación solo contiene suficiente Descripción de XML para describir el comportamiento del
lenguaje Visual Basic. Puede encontrar más información sobre XML en http://www.w3.org/TR/REC-xml/ .
Reglas léxicas
XMLCharacter
: '<Unicode tab character (0x0009)>'
| '<Unicode linefeed character (0x000A)>'
| '<Unicode carriage return character (0x000D)>'
| '<Unicode characters 0x0020 - 0xD7FF>'
| '<Unicode characters 0xE000 - 0xFFFD>'
| '<Unicode characters 0x10000 - 0x10FFFF>'
;
XMLString
: XMLCharacter+
;
XMLWhitespace
: XMLWhitespaceCharacter+
;
XMLWhitespaceCharacter
: '<Unicode carriage return character (0x000D)>'
| '<Unicode linefeed character (0x000A)>'
| '<Unicode space character (0x0020)>'
| '<Unicode tab character (0x0009)>'
;
XMLNameCharacter
: XMLLetter
| XMLDigit
| '.'
| '-'
| '_'
| ':'
| XMLCombiningCharacter
| XMLExtender
;
XMLNameStartCharacter
: XMLLetter
| '_'
| ':'
;
XMLName
: XMLNameStartCharacter XMLNameCharacter*
;
XMLLetter
: '<Unicode character as defined in the Letter production of the XML 1.0 specification>'
;
XMLDigit
: '<Unicode character as defined in the Digit production of the XML 1.0 specification>'
;
XMLCombiningCharacter
: '<Unicode character as defined in the CombiningChar production of the XML 1.0 specification>'
;
XMLExtender
: '<Unicode character as defined in the Extender production of the XML 1.0 specification>'
;
Las expresiones literales XML se interpretan utilizando las reglas léxicas de XML en lugar de las reglas léxicas de
código de Visual Basic normal. Los dos conjuntos de reglas generalmente difieren de las siguientes maneras:
El espacio en blanco es significativo en XML. Como resultado, la gramática de expresiones literales XML
indica explícitamente dónde se permite el espacio en blanco. No se conserva el espacio en blanco,
excepto cuando se produce en el contexto de los datos de caracteres dentro de un elemento. Por ejemplo:
XMLEmbeddedExpression
: '<' '%' '=' LineTerminator? Expression LineTerminator? '%' '>'
;
Por ejemplo, el código siguiente coloca la cadena John Smith como el valor del elemento XML:
Las expresiones se pueden incrustar en varios contextos. Por ejemplo, el código siguiente genera un elemento
denominado customer :
Cada contexto en el que se puede usar una expresión insertada especifica los tipos que se aceptarán. Cuando se
encuentra en el contexto de la parte de la expresión de una expresión incrustada, se siguen aplicando las reglas
léxicas normales para Visual Basic código, por ejemplo, se deben usar continuaciones de línea:
' Visual Basic expression uses line continuation, XML does not
Dim element As System.Xml.Linq.XElement = _
<<%= name & _
name %>>John
Smith</>
Documentos XML
XMLDocument
: XMLDocumentPrologue XMLMisc* XMLDocumentBody XMLMisc*
;
XMLDocumentPrologue
: '<' '?' 'xml' XMLVersion XMLEncoding? XMLStandalone? XMLWhitespace? '?' '>'
;
XMLVersion
: XMLWhitespace 'version' XMLWhitespace? '=' XMLWhitespace? XMLVersionNumberValue
;
XMLVersionNumberValue
: SingleQuoteCharacter '1' '.' '0' SingleQuoteCharacter
| DoubleQuoteCharacter '1' '.' '0' DoubleQuoteCharacter
;
XMLEncoding
: XMLWhitespace 'encoding' XMLWhitespace? '=' XMLWhitespace? XMLEncodingNameValue
;
XMLEncodingNameValue
: SingleQuoteCharacter XMLEncodingName SingleQuoteCharacter
| DoubleQuoteCharacter XMLEncodingName DoubleQuoteCharacter
;
XMLEncodingName
: XMLLatinAlphaCharacter XMLEncodingNameCharacter*
;
XMLEncodingNameCharacter
: XMLUnderscoreCharacter
| XMLLatinAlphaCharacter
| XMLNumericCharacter
| XMLPeriodCharacter
| XMLDashCharacter
;
XMLLatinAlphaCharacter
: '<Unicode Latin alphabetic character (0x0041-0x005a, 0x0061-0x007a)>'
;
XMLNumericCharacter
: '<Unicode digit character (0x0030-0x0039)>'
;
XMLHexNumericCharacter
: XMLNumericCharacter
| '<Unicode Latin hex alphabetic character (0x0041-0x0046, 0x0061-0x0066)>'
;
XMLPeriodCharacter
: '<Unicode period character (0x002e)>'
;
XMLUnderscoreCharacter
: '<Unicode underscore character (0x005f)>'
;
XMLDashCharacter
: '<Unicode dash character (0x002d)>'
;
XMLStandalone
: XMLWhitespace 'standalone' XMLWhitespace? '=' XMLWhitespace? XMLYesNoValue
;
XMLYesNoValue
: SingleQuoteCharacter XMLYesNo SingleQuoteCharacter
| DoubleQuoteCharacter XMLYesNo DoubleQuoteCharacter
;
XMLYesNo
: 'yes'
| 'no'
;
XMLMisc
: XMLComment
| XMLProcessingInstruction
| XMLWhitespace
;
XMLDocumentBody
: XMLElement
| XMLEmbeddedExpression
;
Dim pi As System.Xml.Linq.XProcessingInstruction = _
<?instruction?>
Un documento XML puede contener una expresión incrustada cuyo tipo puede ser cualquier tipo; sin embargo,
en tiempo de ejecución, el objeto debe cumplir los requisitos del XDocument constructor o se producirá un error
en tiempo de ejecución.
A diferencia de XML normal, las expresiones de documento XML no admiten DTD (declaraciones de tipos de
documento). Además, se omitirá el atributo de codificación, si se proporciona, ya que la codificación de la
expresión literal XML es siempre la misma que la codificación del archivo de código fuente.
Nota. Aunque se omite el atributo de codificación, sigue siendo un atributo válido para mantener la capacidad
de incluir cualquier documento XML 1,0 válido en el código fuente.
Elementos XML
XMLElement
: XMLEmptyElement
| XMLElementStart XMLContent XMLElementEnd
;
XMLEmptyElement
: '<' XMLQualifiedNameOrExpression XMLAttribute* XMLWhitespace? '/' '>'
;
XMLElementStart
: '<' XMLQualifiedNameOrExpression XMLAttribute* XMLWhitespace? '>'
;
XMLElementEnd
: '<' '/' '>'
| '<' '/' XMLQualifiedName XMLWhitespace? '>'
;
XMLContent
: XMLCharacterData? ( XMLNestedContent XMLCharacterData? )+
;
XMLCharacterData
: '<Any XMLCharacterDataString that does not contain the string "]]>">'
;
XMLCharacterDataString
: '<Any Unicode character except < or &>'+
;
XMLNestedContent
: XMLElement
| XMLReference
| XMLCDATASection
| XMLProcessingInstruction
| XMLComment
| XMLEmbeddedExpression
;
XMLAttribute
: XMLWhitespace XMLAttributeName XMLWhitespace? '=' XMLWhitespace? XMLAttributeValue
| XMLWhitespace XMLEmbeddedExpression
;
XMLAttributeName
: XMLQualifiedNameOrExpression
| XMLNamespaceAttributeName
;
XMLAttributeValue
: DoubleQuoteCharacter XMLAttributeDoubleQuoteValueCharacter* DoubleQuoteCharacter
| SingleQuoteCharacter XMLAttributeSingleQuoteValueCharacter* SingleQuoteCharacter
| XMLEmbeddedExpression
;
XMLAttributeDoubleQuoteValueCharacter
: '<Any XMLCharacter except <, &, or DoubleQuoteCharacter>'
| XMLReference
;
XMLAttributeSingleQuoteValueCharacter
: '<Any XMLCharacter except <, &, or SingleQuoteCharacter>'
| XMLReference
;
XMLReference
: XMLEntityReference
| XMLCharacterReference
;
XMLEntityReference
: '&' XMLEntityName ';'
;
XMLEntityName
: 'lt' | 'gt' | 'amp' | 'apos' | 'quot'
: 'lt' | 'gt' | 'amp' | 'apos' | 'quot'
;
XMLCharacterReference
: '&' '#' XMLNumericCharacter+ ';'
| '&' '#' 'x' XMLHexNumericCharacter+ ';'
;
Un elemento XML da como resultado un valor con tipo System.Xml.Linq.XElement . A diferencia de XML normal,
los elementos XML pueden omitir el nombre en la etiqueta de cierre y se cerrará el elemento más anidado
actual. Por ejemplo:
Las declaraciones de atributos de un elemento XML dan como resultado valores con tipo
System.Xml.Linq.XAttribute . Los valores de atributo se normalizan según la especificación XML. Cuando el
valor de un atributo es Nothing , el atributo no se creará, por lo que no será necesario comprobar la expresión
de valor de atributo Nothing . Por ejemplo:
Los elementos y atributos XML pueden contener expresiones anidadas en los lugares siguientes:
Nombre del elemento, en cuyo caso la expresión incrustada debe ser un valor de un tipo que se pueda convertir
implícitamente en System.Xml.Linq.XName . Por ejemplo:
Nombre de un atributo del elemento, en cuyo caso la expresión incrustada debe ser un valor de un tipo que se
pueda convertir implícitamente en System.Xml.Linq.XName . Por ejemplo:
El valor de un atributo del elemento, en cuyo caso la expresión incrustada puede ser un valor de cualquier tipo.
Por ejemplo:
Atributo del elemento, en cuyo caso la expresión incrustada puede ser un valor de cualquier tipo. Por ejemplo:
El contenido del elemento, en cuyo caso la expresión incrustada puede ser un valor de cualquier tipo. Por
ejemplo:
Dim name = <name><%= "Bob" %></>
XMLNamespaceAttributeName
: XMLPrefixedNamespaceAttributeName
| XMLDefaultNamespaceAttributeName
;
XMLPrefixedNamespaceAttributeName
: 'xmlns' ':' XMLNamespaceName
;
XMLDefaultNamespaceAttributeName
: 'xmlns'
;
XMLNamespaceName
: XMLNamespaceNameStartCharacter XMLNamespaceNameCharacter*
;
XMLNamespaceNameStartCharacter
: '<Any XMLNameCharacter except :>'
;
XMLNamespaceNameCharacter
: XMLLetter
| '_'
;
XMLQualifiedNameOrExpression
: XMLQualifiedName
| XMLEmbeddedExpression
;
XMLQualifiedName
: XMLPrefixedName
| XMLUnprefixedName
;
XMLPrefixedName
: XMLNamespaceName ':' XMLNamespaceName
;
XMLUnprefixedName
: XMLNamespaceName
;
Las restricciones en la definición de los espacios de nombres xml y xmlns se aplican y generarán errores en
tiempo de compilación. Las declaraciones de espacio de nombres XML no pueden tener una expresión
incrustada para su valor; el valor proporcionado debe ser un literal de cadena no vacío. Por ejemplo:
' Declares a valid namespace
Dim customer = <db:customer xmlns:db="http://example.org/database">Bob</>
Nota. Esta especificación solo contiene suficiente Descripción del espacio de nombres XML para describir el
comportamiento del lenguaje Visual Basic. Puede encontrar más información sobre los espacios de nombres
XML en http://www.w3.org/TR/REC-xml-names/ .
Los nombres de atributos y elementos XML se pueden calificar mediante nombres de espacio de nombres. Los
espacios de nombres se enlazan como en XML normal, con la excepción de que las importaciones de espacios
de nombres declaradas en el nivel de archivo se declaran en un contexto que incluye la declaración, que se
encuentra entre las importaciones de espacio de nombres declaradas por el entorno de compilación. Si no se
encuentra un nombre de espacio de nombres, se produce un error en tiempo de compilación. Por ejemplo:
Imports System.Xml.Linq
Imports <xmlns:db="http://example.org/database">
Module Test
Sub Main()
' Binds to the imported namespace above.
Dim c1 = <db:customer>Bob</>
Los espacios de nombres XML declarados en un elemento no se aplican a los literales XML dentro de las
expresiones incrustadas. Por ejemplo:
Nota. Esto se debe a que la expresión incrustada puede ser cualquier cosa, incluida una llamada de función. Si la
llamada de función contenía una expresión literal XML, no queda claro si los programadores esperan que el
espacio de nombres XML se aplique o se omita.
Instrucciones de procesamiento de XML
Una instrucción de procesamiento XML da como resultado un valor con tipo
System.Xml.Linq.XProcessingInstruction . Las instrucciones de procesamiento XML no pueden contener
expresiones incrustadas, ya que son una sintaxis válida dentro de la instrucción de procesamiento.
XMLProcessingInstruction
: '<' '?' XMLProcessingTarget ( XMLWhitespace XMLProcessingValue? )? '?' '>'
;
XMLProcessingTarget
: '<Any XMLName except a casing permutation of the string "xml">'
;
XMLProcessingValue
: '<Any XMLString that does not contain a question-mark followed by ">">'
;
Comentarios XML
Un comentario XML da como resultado un valor con tipo System.Xml.Linq.XComment . Los comentarios XML no
pueden contener expresiones incrustadas, ya que son una sintaxis válida dentro del comentario.
XMLComment
: '<' '!' '-' '-' XMLCommentCharacter* '-' '-' '>'
;
XMLCommentCharacter
: '<Any XMLCharacter except dash (0x002D)>'
| '-' '<Any XMLCharacter except dash (0x002D)>'
;
secciones CDATA
Una sección CDATA da como resultado un valor con tipo System.Xml.Linq.XCData . Las secciones CDATA no
pueden contener expresiones incrustadas, ya que son una sintaxis válida dentro de la sección CDATA.
XMLCDATASection
: '<' '!' ( 'CDATA' '[' XMLCDATASectionString? ']' )? '>'
;
XMLCDATASectionString
: '<Any XMLString that does not contain the string "]]>">'
;
XMLMemberAccessExpression
: Expression '.' LineTerminator? '<' XMLQualifiedName '>'
| Expression '.' LineTerminator? '@' LineTerminator? '<' XMLQualifiedName '>'
| Expression '.' LineTerminator? '@' LineTerminator? IdentifierOrKeyword
| Expression '.' '.' '.' LineTerminator? '<' XMLQualifiedName '>'
;
Acceso a atributos, en el que un identificador Visual Basic sigue a un punto y a una arroba, o a un nombre
XML después de un punto y un signo de arroba. Por ejemplo:
Nota. El AttributeValue método de extensión (así como la propiedad de extensión relacionada Value )
no está definido actualmente en ningún ensamblado. Si los miembros de extensión son necesarios, se
definen automáticamente en el ensamblado que se está generando.
Acceso a los descendientes, donde los nombres XML siguen tres puntos. Por ejemplo:
Dim company = _
<company>
<customers>
<customer>Bob</>
<customer>Mary</>
<customer>Joe</>
</>
</>
Dim customers = company...<customer>
La expresión base de una expresión de acceso a miembros XML debe ser un valor de y debe ser del tipo:
Si un elemento o descendientes tienen acceso, System.Xml.Linq.XContainer o un tipo derivado, o
System.Collections.Generic.IEnumerable(Of T) bien un tipo derivado, donde T es
System.Xml.Linq.XContainer o un tipo derivado.
Si el acceso a un atributo, System.Xml.Linq.XElement o un tipo derivado, o
System.Collections.Generic.IEnumerable(Of T) o un tipo derivado, donde T es System.Xml.Linq.XElement
o un tipo derivado.
Los nombres de las expresiones de acceso a miembros XML no pueden estar vacíos. Puede ser un espacio de
nombres calificado, con cualquier espacio de nombres definido por las importaciones. Por ejemplo:
Imports <xmlns:db="http://example.org/database">
Module Test
Sub Main()
Dim customer = _
<db:customer>
<db:name>Bob</>
</>
Dim name = customer.<db:name>
End Sub
End Module
No se permite el espacio en blanco después de los puntos de una expresión de acceso a miembros XML o entre
corchetes angulares y el nombre. Por ejemplo:
Dim customer = _
<customer age="30">
<name>Bob</>
</>
' All the following are error cases
Dim age = customer.@ age
Dim name = customer.< name >
Dim names = customer...< name >
Si los tipos del System.Xml.Linq espacio de nombres no están disponibles, una expresión de acceso a miembros
XML producirá un error en tiempo de compilación.
Await (Operador)
El operador Await está relacionado con métodos asincrónicos, que se describen en la sección métodos
asincrónicos.
AwaitOperatorExpression
: 'Await' Expression
;
Await es una palabra reservada si el método de inclusión inmediato o la expresión lambda en la que aparece
tiene un Async modificador y si Await aparece después de ese Async modificador; no se reserva en otro lugar.
También está sin reservar en las directivas de preprocesador. El operador Await solo se permite en el cuerpo de
un método o expresiones lambda donde es una palabra reservada. Dentro del método o la expresión lambda de
inclusión inmediata, es posible que una expresión Await no se produzca dentro del cuerpo de un Catch
Finally bloque o, ni dentro del cuerpo de una SyncLock instrucción ni dentro de una expresión de consulta.
El operador Await toma una expresión única que se debe clasificar como un valor y cuyo tipo debe ser un tipo
que se puede esperar , o Object . Si su tipo es Object , todo el procesamiento se aplaza hasta el tiempo de
ejecución. C Se dice que un tipo es Await a si se cumplen todas las condiciones siguientes:
C contiene una instancia accesible o un método de extensión denominado GetAwaiter que no tiene
ningún argumento y que devuelve algún tipo E ;
E contiene una instancia legible o una propiedad de extensión denominada IsCompleted que no toma
ningún argumento y tiene el tipo Boolean;
E contiene una instancia accesible o un método de extensión denominado GetResult que no toma
ningún argumento;
E implementa System.Runtime.CompilerServices.INotifyCompletion o ICriticalNotifyCompletion .
Si es GetResult Sub , la expresión Await se clasifica como void. De lo contrario, la expresión Await se clasifica
como un valor y su tipo es el tipo de valor devuelto del GetResult método.
A continuación se muestra un ejemplo de una clase que se puede esperar:
Class MyTask(Of T)
Function GetAwaiter() As MyTaskAwaiter(Of T)
Return New MyTaskAwaiter With {.m_Task = Me}
End Function
...
End Class
Structure MyTaskAwaiter(Of T)
Implements INotifyCompletion
Function GetResult() As T
If m_Task.IsCanceled Then Throw New TaskCanceledException(m_Task)
If m_Task.IsFaulted Then Throw m_Task.Exception.InnerException
Return m_Task.Result
End Function
End Structure
Nota. Se recomienda a los autores de la biblioteca que sigan el patrón en el que invocan el delegado de
continuación en la misma medida en que SynchronizationContext OnCompleted se invocó en. Además, el
delegado de reanudación no debe ejecutarse sincrónicamente en el OnCompleted método, ya que puede
provocar el desbordamiento de la pila: en su lugar, el delegado se debe poner en cola para su posterior
ejecución.
Cuando el flujo de control alcanza un Await operador, el comportamiento es el siguiente.
1. GetAwaiter Se invoca el método del operando Await. El resultado de esta Invocación se denomina Await.
2. IsCompleted Se recupera la propiedad del Await. Si el resultado es true, entonces:
u. Se invoca el método del Await. Si es
GetResult GetResult una función, el valor de la expresión Await
es el valor devuelto de esta función.
3. Si la propiedad IsCompleted no es true, entonces:
ae. Se invoca en el Await (si el tipo de Await E
ICriticalNotifyCompletion.UnsafeOnCompleted
implementa ICriticalNotifyCompletion ) o INotifyCompletion.OnCompleted (de lo contrario). En
ambos casos, pasa un delegado de reanudación asociado a la instancia actual del método
asincrónico.
af. El punto de control de la instancia de método asincrónico actual se suspende y el flujo de control
se reanuda en el llamador actual (definido en la sección métodos asincrónicos).
ag. Si es posterior, se invoca al delegado de reanudación,
ls. en primer lugar, el delegado de reanudación restaura
System.Threading.Thread.CurrentThread.ExecutionContext en lo que se encontraba en el
momento en que se llamó a él. OnCompleted
lt. a continuación, reanuda el flujo de control en el punto de control de la instancia de método
asincrónico (consulte la sección métodos asincrónicos).
lu. donde llama al GetResult método del Await, como en 2,1 anteriores.
Si el operando Await tiene el tipo Object, este comportamiento se aplaza hasta el tiempo de ejecución:
El paso 1 se realiza mediante una llamada a GetAwaiter () sin argumentos; por tanto, puede enlazar en
tiempo de ejecución a métodos de instancia que toman parámetros opcionales.
El paso 2 se logra recuperando la propiedad IsCompleted () sin argumentos y intentando realizar una
conversión intrínseca en un valor booleano.
Paso 3. se realiza un intento de TryCast(awaiter, ICriticalNotifyCompletion) y, si se produce un error,
DirectCast(awaiter, INotifyCompletion) .
El delegado de reanudación pasado en 3. solo se puede invocar una vez. Si se invoca más de una vez, el
comportamiento es indefinido.
Comentarios de documentación
15/11/2021 • 27 minutes to read
Los comentarios de documentación son comentarios con formato especial en el origen que se pueden analizar
para generar documentación sobre el código al que se adjuntan. El formato básico de los comentarios de
documentación es XML. Al compilar el código con comentarios de documentación, el compilador puede emitir
opcionalmente un archivo XML que represente la suma total de los comentarios de documentación en el origen.
Este archivo XML puede ser utilizado por otras herramientas para generar documentación impresa o en línea.
En este capítulo se describen los comentarios de documento y las etiquetas XML recomendadas que se deben
usar con los comentarios de documento.
''' <remarks>
''' Class <c>Point</c> models a point in a two-dimensional plane.
''' </remarks>
Public Class Point
''' <remarks>
''' Method <c>Draw</c> renders the point.
''' </remarks>
Sub Draw()
End Sub
End Class
Los comentarios de documentación deben tener un formato XML correcto según https://www.w3.org/TR/REC-
xml . Si el código XML no es correcto, se genera una advertencia y el archivo de documentación contendrá un
comentario que indica que se ha detectado un error.
Aunque los desarrolladores pueden crear su propio conjunto de etiquetas, se define un conjunto recomendado
en la sección siguiente. Algunas de las etiquetas recomendadas tienen significados especiales:
La etiqueta <param> se usa para describir parámetros. El parámetro especificado por una <param>
etiqueta debe existir y todos los parámetros del miembro de tipo deben describirse en el comentario de
la documentación. Si alguna de las condiciones no es true, el compilador emite una advertencia.
El atributo cref se puede asociar a cualquier etiqueta para proporcionar una referencia a un elemento
de código. El elemento de código debe existir; en tiempo de compilación, el compilador reemplaza el
nombre por la cadena de identificador que representa el miembro. Si el elemento de código no existe, el
compilador emite una advertencia. Al buscar un nombre descrito en un cref atributo, el compilador
respeta Imports las instrucciones que aparecen en el archivo de código fuente contenedor.
La <summary> etiqueta está pensada para que la use un visor de documentación para mostrar
información adicional sobre un tipo o miembro.
Tenga en cuenta que el archivo de documentación no proporciona información completa sobre un tipo y los
miembros, solo lo que se incluye en los comentarios del documento. Para obtener más información acerca de
un tipo o miembro, el archivo de documentación debe utilizarse junto con la reflexión en el tipo o miembro real.
Etiquetas recomendadas
El generador de documentación debe aceptar y procesar cualquier etiqueta que sea válida de acuerdo con las
reglas de XML. Las etiquetas siguientes proporcionan funciones de uso general en la documentación de usuario:
<c> Establece texto en una fuente similar a código
<code> Establece una o más líneas de código fuente o de salida de programa en una fuente similar a código
<example> Indica un ejemplo
<exception> Identifica las excepciones que un método puede producir
<include> Incluye un documento XML externo
<list> Crea una lista o una tabla
<para> Permite agregar la estructura al texto
<param> Describe un parámetro para un método o constructor.
<paramref> Identifica que una palabra es un nombre de parámetro
<permission> Documenta la accesibilidad de seguridad de un miembro
<remarks> Describe un tipo.
<returns> Describe el valor devuelto de un método.
<see> Especifica un vínculo
<seealso> Genera una entrada See también
<summary> Describe un miembro de un tipo.
<typeparam> Describe un parámetro de tipo
<value> Describe una propiedad
<c>
Esta etiqueta especifica que un fragmento de texto dentro de una descripción debe utilizar una fuente como la
que se usa para un bloque de código. (Para las líneas de código real, use <code> ).
Sintaxis:
Ejemplo :
''' <remarks>
''' Class <c>Point</c> models a point in a two-dimensional plane.
''' </remarks>
Public Class Point
End Class
<code>
Esta etiqueta especifica que una o varias líneas de código fuente o de salida de programa deben usar una fuente
de ancho fijo. (Para fragmentos de código pequeños, use <c> ).
Sintaxis:
Ejemplo :
''' <summary>
''' This method changes the point's location by the given x- and
''' y-offsets.
''' <example>
''' For example:
''' <code>
''' Dim p As Point = New Point(3,5)
''' p.Translate(-1,3)
''' </code>
''' results in <c>p</c>'s having the value (2,8).
''' </example>
''' </summary>
Public Sub Translate(x As Integer, y As Integer)
Me.x += x
Me.y += y
End Sub
<example>
Esta etiqueta permite el código de ejemplo de un comentario para mostrar cómo se puede usar un elemento.
Normalmente, esto implicará también el uso de la etiqueta <code> .
Sintaxis:
<example>description</example>
Ejemplo :
Vea <code> para obtener un ejemplo.
<exception>
Esta etiqueta proporciona una manera de documentar las excepciones que un método puede iniciar.
Sintaxis:
<exception cref="member">description</exception>
Ejemplo :
Public Module DataBaseOperations
''' <exception cref="MasterFileFormatCorruptException"></exception>
''' <exception cref="MasterFileLockedOpenException"></exception>
Public Sub ReadRecord(flag As Integer)
If Flag = 1 Then
Throw New MasterFileFormatCorruptException()
ElseIf Flag = 2 Then
Throw New MasterFileLockedOpenException()
End If
' ...
End Sub
End Module
<include>
Esta etiqueta se usa para incluir información de un documento XML con formato correcto. Se aplica una
expresión XPath al documento XML para especificar qué XML se debe incluir en el documento. <include> A
continuación, la etiqueta se reemplaza con el XML seleccionado del documento externo.
Sintaxis:
Ejemplo :
Si el código fuente contiene una declaración como la siguiente:
<?xml version="1.0"?>
<extra>
<class name="IntList">
<summary>
Contains a list of integers.
</summary>
</class>
<class name="StringList">
<summary>
Contains a list of strings.
</summary>
</class>
</extra>
''' <summary>
''' Contains a list of integers.
''' </summary>
<list>
Esta etiqueta se usa para crear una lista o una tabla de elementos. Puede contener un <listheader> bloque para
definir la fila de encabezado de una tabla o de una lista de definiciones. (Al definir una tabla, solo es necesario
proporcionar una entrada para el término en el encabezado).
Cada elemento de la lista se especifica con un bloque <item> . Al crear una lista de definiciones, se deben
especificar los términos y la descripción. Sin embargo, para una tabla, una lista con viñetas o una lista
numerada, solo es necesario especificar la descripción.
Sintaxis:
Ejemplo :
<para>
Esta etiqueta se usa dentro de otras etiquetas, como <remarks> o <returns> , y permite agregar la estructura al
texto.
Sintaxis:
<para>content</para>
Ejemplo :
''' <summary>
''' This is the entry point of the Point class testing program.
''' <para>This program tests each method and operator, and
''' is intended to be run after any non-trvial maintenance has
''' been performed on the Point class.</para>
''' </summary>
Public Shared Sub Main()
End Sub
<param>
Esta etiqueta describe un parámetro para un método, un constructor o una propiedad indizada.
Sintaxis:
<param name="name">description</param>
Ejemplo :
''' <summary>
''' This method changes the point's location to the given
''' coordinates.
''' </summary>
''' <param name="x"><c>x</c> is the new x-coordinate.</param>
''' <param name="y"><c>y</c> is the new y-coordinate.</param>
Public Sub Move(x As Integer, y As Integer)
Me.x = x
Me.y = y
End Sub
<paramref>
Esta etiqueta indica que una palabra es un parámetro. El archivo de documentación se puede procesar para dar
formato a este parámetro de alguna manera distinta.
Sintaxis:
<paramref name="name"/>
Ejemplo :
''' <summary>
''' This constructor initializes the new Point to
''' (<paramref name="x"/>,<paramref name="y"/>).
''' </summary>
''' <param name="x"><c>x</c> is the new Point's x-coordinate.</param>
''' <param name="y"><c>y</c> is the new Point's y-coordinate.</param>
Public Sub New(x As Integer, y As Integer)
Me.x = x
Me.y = y
End Sub
<permission>
Esta etiqueta documenta la accesibilidad de seguridad de un miembro
Sintaxis:
<permission cref="member">description</permission>
Ejemplo :
<remarks>
Esta etiqueta especifica información general sobre un tipo. ( <summary> Se usa para describir los miembros de
un tipo).
Sintaxis:
<remarks>description</remarks>
Ejemplo :
''' <remarks>
''' Class <c>Point</c> models a point in a two-dimensional plane.
''' </remarks>
Public Class Point
End Class
<returns>
Esta etiqueta describe el valor devuelto de un método.
Sintaxis:
<returns>description</returns>
Ejemplo :
''' <summary>
''' Report a point's location as a string.
''' </summary>
''' <returns>
''' A string representing a point's location, in the form (x,y), without
''' any leading, training, or embedded whitespace.
''' </returns>
Public Overrides Function ToString() As String
Return "(" & x & "," & y & ")"
End Sub
<see>
Esta etiqueta permite especificar un vínculo dentro del texto. ( <seealso> Se usa para indicar texto que debe
aparecer en una sección Vea también).
Sintaxis:
<see cref="member"/>
Ejemplo :
''' <summary>
''' This method changes the point's location to the given
''' coordinates.
''' </summary>
''' <see cref="Translate"/>
Public Sub Move(x As Integer, y As Integer)
Me.x = x
Me.y = y
End Sub
''' <summary>
''' This method changes the point's location by the given x- and
''' y-offsets.
''' </summary>
''' <see cref="Move"/>
Public Sub Translate(x As Integer, y As Integer)
Me.x += x
Me.y += y
End Sub
<seealso>
Esta etiqueta genera una entrada para la sección Vea también. ( <see> Se usa para especificar un vínculo desde
dentro del texto).
Sintaxis:
<seealso cref="member"/>
Ejemplo :
''' <summary>
''' This method determines whether two Points have the same location.
''' </summary>
''' <seealso cref="operator=="/>
''' <seealso cref="operator!="/>
Public Overrides Function Equals(o As Object) As Boolean
' ...
End Function
<summary>
Esta etiqueta describe un miembro de tipo. ( <remarks> Se usa para describir un tipo en sí).
Sintaxis:
<summary>description</summary>
Ejemplo :
''' <summary>
''' This constructor initializes the new Point to (0,0).
''' </summary>
Public Sub New()
Me.New(0,0)
End Sub
<typeparam>
Esta etiqueta describe un parámetro de tipo.
Sintaxis:
<typeparam name="name">description</typeparam>
Ejemplo :
<value>
Esta etiqueta describe una propiedad.
Sintaxis:
<value>property description</value>
Ejemplo :
''' <value>
''' Property <c>X</c> represents the point's x-coordinate.
''' </value>
Public Property X() As Integer
Get
Return _x
End Get
Set (Value As Integer)
_x = Value
End Set
End Property
Cadenas de identificador
Al generar el archivo de documentación, el compilador genera una cadena de identificador para cada elemento
del código fuente que se etiqueta con un Comentario de documentación que lo identifica de forma única. Las
herramientas externas pueden usar esta cadena de identificador para identificar qué elemento de un
ensamblado compilado corresponde al comentario del documento.
Las cadenas de identificador se generan como sigue:
No se coloca espacio en blanco en la cadena.
La primera parte de la cadena identifica el tipo de miembro que se va a documentar, a través de un solo carácter
seguido de un signo de dos puntos. Se definen los siguientes tipos de miembros, con el carácter
correspondiente entre paréntesis después: Events (E), Fields (F), métodos incluidos constructores y operadores
(M), espacios de nombres (N), propiedades (P) y tipos (T). Un signo de exclamación (!) indica que se produjo un
error al generar la cadena de identificador y el resto de la cadena proporciona información sobre el error.
La segunda parte de la cadena es el nombre completo del elemento, comenzando por el espacio de nombres
global. El nombre del elemento, sus tipos envolventes y su espacio de nombres se separan por puntos. Si el
nombre del propio elemento tiene puntos, se reemplazan por el signo de almohadilla (#). (Se supone que
ningún elemento tiene este carácter en su nombre). El nombre de un tipo con parámetros de tipo termina con
una comilla (') seguida de un número que representa el número de parámetros de tipo en el tipo. Es importante
recordar que, dado que los tipos anidados tienen acceso a los parámetros de tipo de los tipos que los contienen,
los tipos anidados contienen implícitamente los parámetros de tipo de sus tipos contenedores y esos tipos se
cuentan en sus totales de parámetro de tipo en este caso.
En el caso de los métodos y propiedades con argumentos, la lista de argumentos sigue entre paréntesis. Para
aquellos que no tienen argumentos, se omiten los paréntesis. Los argumentos están separados por comas. La
codificación de cada argumento es la misma que la firma de la CLI, como se indica a continuación: los
argumentos se representan mediante su nombre completo. Por ejemplo, se convierte en, se convierte en,
Integer System.Int32 String System.String Object System.Object y así sucesivamente. Los argumentos
que tienen el ByRef modificador tienen un ' @ ' después del nombre de tipo. Los argumentos que tienen el
ByVal Optional ParamArray modificador, o no tienen ninguna notación especial. Los argumentos que son
matrices se representan como [lowerbound:size, ..., lowerbound:size] donde el número de comas es el
rango-1, y los límites inferiores y el tamaño de cada dimensión, si se conocen, se representan en formato
decimal. Si no se especifica un límite inferior o un tamaño, se omite. Si se omiten el límite inferior y el tamaño
de una dimensión determinada, también se omite ":". Las matrices de matrices se representan mediante un "
[] " por nivel.
Namespace Acme
Interface IProcess
End Interface
Structure ValueType
...
End Structure
Class Widget
Public Class NestedClass
End Class
"T:Color"
"T:Acme.IProcess"
"T:Acme.ValueType"
"T:Acme.Widget"
"T:Acme.Widget.NestedClass"
"T:Acme.Widget.IMenuItem"
"T:Acme.Widget.Del"
"T:Acme.Widget.Direction"
Class Widget
Public Class NestedClass
Private value As Integer
End Class
"F:Acme.ValueType.total"
"F:Acme.Widget.NestedClass.value"
"F:Acme.Widget.message"
"F:Acme.Widget.defaultColor"
"F:Acme.Widget.PI"
"F:Acme.Widget.monthlyAverage"
"F:Acme.Widget.array1"
"F:Acme.Widget.array2"
Constructores.
Namespace Acme
Class Widget
Shared Sub New()
End Sub
"M:Acme.Widget.#cctor"
"M:Acme.Widget.#ctor"
"M:Acme.Widget.#ctor(System.String)"
Modalidades.
Namespace Acme
Structure ValueType
Public Sub M(i As Integer)
End Sub
End Structure
Class Widget
Public Class NestedClass
Public Sub M(i As Integer)
End Sub
End Class
"M:Acme.ValueType.M(System.Int32)"
"M:Acme.Widget.NestedClass.M(System.Int32)"
"M:Acme.Widget.M0"
"M:Acme.Widget.M1(System.Char,System.Single@,Acme.ValueType@)"
"M:Acme.Widget.M2(System.Int16[],System.Int32[0:,0:],System.Int64[][])"
"M:Acme.Widget.M3(System.Int64[][],Acme.Widget[0:,0:,0:][])"
"M:Acme.Widget.M4(System.Int32)"
"M:Acme.Widget.M5(System.Object[])"
Propiedades.
Namespace Acme
Class Widget
Public Property Width() As Integer
Get
End Get
Set (Value As Integer)
End Set
End Property
"P:Acme.Widget.Width"
"P:Acme.Widget.Item(System.Int32)"
"P:Acme.Widget.Item(System.String,System.Int32)"
Events
Namespace Acme
Class Widget
Public Event AnEvent As EventHandler
Public Event AnotherEvent()
End Class
End Namespace
"E:Acme.Widget.AnEvent"
"E:Acme.Widget.AnotherEvent"
Operadores.
Namespace Acme
Class Widget
Public Shared Operator +(x As Widget) As Widget
End Operator
"M:Acme.Widget.op_UnaryPlus(Acme.Widget)"
"M:Acme.Widget.op_Addition(Acme.Widget,Acme.Widget)"
Los operadores de conversión tienen un final ~ seguido del tipo de valor devuelto.
Namespace Acme
Class Widget
Public Shared Narrowing Operator CType(x As Widget) As _
Integer
End Operator
"M:Acme.Widget.op_Explicit(Acme.Widget)~System.Int32"
"M:Acme.Widget.op_Implicit(Acme.Widget)~System.Int64"
Namespace Graphics
''' <remarks>
''' Class <c>Point</c> models a point in a two-dimensional
''' plane.
''' </remarks>
Public Class Point
''' <summary>
''' Instance variable <c>x</c> represents the point's x-coordinate.
''' </summary>
Private _x As Integer
''' <summary>
''' Instance variable <c>y</c> represents the point's y-coordinate.
''' </summary>
Private _y As Integer
''' <value>
''' Property <c>X</c> represents the point's x-coordinate.
''' </value>
Public Property X() As Integer
Get
Return _x
End Get
Set(Value As Integer)
_x = Value
End Set
End Property
''' <value>
''' Property <c>Y</c> represents the point's y-coordinate.
''' </value>
Public Property Y() As Integer
Get
Return _y
End Get
Set(Value As Integer)
_y = Value
End Set
End Property
''' <summary>
''' This constructor initializes the new Point to (0,0).
''' </summary>
Public Sub New()
Me.New(0, 0)
End Sub
''' <summary>
''' This constructor initializes the new Point to
''' (<paramref name="x"/>,<paramref name="y"/>).
''' </summary>
''' <param name="x"><c>x</c> is the new Point's
''' x-coordinate.</param>
''' <param name="y"><c>y</c> is the new Point's
''' y-coordinate.</param>
Public Sub New(x As Integer, y As Integer)
Me.X = x
Me.Y = y
End Sub
''' <summary>
''' This method changes the point's location to the given
''' coordinates.
''' </summary>
''' <param name="x"><c>x</c> is the new x-coordinate.</param>
''' <param name="y"><c>y</c> is the new y-coordinate.</param>
''' <see cref="Translate"/>
Public Sub Move(x As Integer, y As Integer)
Me.X = x
Me.Y = y
End Sub
''' <summary>
''' This method changes the point's location by the given x- and
''' y-offsets.
''' <example>
''' For example:
''' <code>
''' Dim p As Point = New Point(3, 5)
''' p.Translate(-1, 3)
''' </code>
''' results in <c>p</c>'s having the value (2,8).
''' </example>
''' </summary>
''' <param name="x"><c>x</c> is the relative x-offset.</param>
''' <param name="y"><c>y</c> is the relative y-offset.</param>
''' <see cref="Move"/>
Public Sub Translate(x As Integer, y As Integer)
Me.X += x
Me.Y += y
End Sub
''' <summary>
''' This method determines whether two Points have the same
''' location.
''' </summary>
''' <param name="o"><c>o</c> is the object to be compared to the
''' current object.</param>
''' <returns>
''' True if the Points have the same location and they have the
''' exact same type; otherwise, false.
''' </returns>
''' <seealso cref="Operator op_Equality"/>
''' <seealso cref="Operator op_Inequality"/>
Public Overrides Function Equals(o As Object) As Boolean
If o Is Nothing Then
Return False
End If
If o Is Me Then
Return True
End If
If Me.GetType() Is o.GetType() Then
Dim p As Point = CType(o, Point)
Return (X = p.X) AndAlso (Y = p.Y)
End If
Return False
Return False
End Function
''' <summary>
''' Report a point's location as a string.
''' </summary>
''' <returns>
''' A string representing a point's location, in the form
''' (x,y), without any leading, training, or embedded whitespace.
''' </returns>
Public Overrides Function ToString() As String
Return "(" & X & "," & Y & ")"
End Function
''' <summary>
''' This operator determines whether two Points have the
''' same location.
''' </summary>
''' <param name="p1"><c>p1</c> is the first Point to be compared.
''' </param>
''' <param name="p2"><c>p2</c> is the second Point to be compared.
''' </param>
''' <returns>
''' True if the Points have the same location and they
''' have the exact same type; otherwise, false.
''' </returns>
''' <seealso cref="Equals"/>
''' <seealso cref="op_Inequality"/>
Public Shared Operator =(p1 As Point, p2 As Point) As Boolean
If p1 Is Nothing OrElse p2 Is Nothing Then
Return False
End If
If p1.GetType() Is p2.GetType() Then
Return (p1.X = p2.X) AndAlso (p1.Y = p2.Y)
End If
Return False
End Operator
''' <summary>
''' This operator determines whether two Points have the
''' same location.
''' </summary>
''' <param name="p1"><c>p1</c> is the first Point to be comapred.
''' </param>
''' <param name="p2"><c>p2</c> is the second Point to be compared.
''' </param>
''' <returns>
''' True if the Points do not have the same location and
''' the exact same type; otherwise, false.
''' </returns>
''' <seealso cref="Equals"/>
''' <seealso cref="op_Equality"/>
Public Shared Operator <>(p1 As Point, p2 As Point) As Boolean
Return Not p1 = p2
End Operator
''' <summary>
''' This is the entry point of the Point class testing program.
''' <para>This program tests each method and operator, and
''' is intended to be run after any non-trvial maintenance has
''' been performed on the Point class.</para>
''' </summary>
Public Shared Sub Main()
' class test code goes here
End Sub
End Class
End Namespace
Este es el resultado que se genera cuando se proporciona el código fuente de la clase Point , que se muestra
arriba:
<?xml version="1.0"?>
<doc>
<assembly>
<name>Point</name>
</assembly>
<members>
<member name="T:Graphics.Point">
<remarks>Class <c>Point</c> models a point in a
two-dimensional plane. </remarks>
</member>
<member name="F:Graphics.Point.x">
<summary>Instance variable <c>x</c> represents the point's
x-coordinate.</summary>
</member>
<member name="F:Graphics.Point.y">
<summary>Instance variable <c>y</c> represents the point's
y-coordinate.</summary>
</member>
<member name="M:Graphics.Point.#ctor">
<summary>This constructor initializes the new Point to
(0,0).</summary>
</member>
<member name="M:Graphics.Point.#ctor(System.Int32,System.Int32)">
<summary>This constructor initializes the new Point to
(<paramref name="x"/>,<paramref name="y"/>).</summary>
<param><c>x</c> is the new Point's x-coordinate.</param>
<param><c>y</c> is the new Point's y-coordinate.</param>
</member>
<member name="M:Graphics.Point.Move(System.Int32,System.Int32)">
<summary>This method changes the point's location to
the given coordinates.</summary>
<param><c>x</c> is the new x-coordinate.</param>
<param><c>y</c> is the new y-coordinate.</param>
<see cref=
"M:Graphics.Point.Translate(System.Int32,System.Int32)"/>
</member>
<member name=
"M:Graphics.Point.Translate(System.Int32,System.Int32)">
<summary>This method changes the point's location by the given
x- and y-offsets.
<example>For example:
<code>
Point p = new Point(3,5);
p.Translate(-1,3);
</code>
results in <c>p</c>'s having the value (2,8).
</example>
</summary>
<param><c>x</c> is the relative x-offset.</param>
<param><c>y</c> is the relative y-offset.</param>
<see cref="M:Graphics.Point.Move(System.Int32,System.Int32)"/>
</member>
<member name="M:Graphics.Point.Equals(System.Object)">
<summary>This method determines whether two Points have the
same location.</summary>
<param><c>o</c> is the object to be compared to the current
object.</param>
<returns>True if the Points have the same location and they
have the exact same type; otherwise, false.</returns>
<seealso cref=
"M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)"
/>
<seealso cref=
"M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)"
/>
/>
</member>
<member name="M:Graphics.Point.ToString">
<summary>Report a point's location as a string.</summary>
<returns>A string representing a point's location, in the form
(x,y), without any leading, training, or embedded
whitespace.</returns>
</member>
<member name=
"M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)">
<summary>This operator determines whether two Points have the
same location.</summary>
<param><c>p1</c> is the first Point to be compared.</param>
<param><c>p2</c> is the second Point to be compared.</param>
<returns>True if the Points have the same location and they
have the exact same type; otherwise, false.</returns>
<seealso cref="M:Graphics.Point.Equals(System.Object)"/>
<seealso cref=
"M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)"
/>
</member>
<member name=
"M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)">
<summary>This operator determines whether two Points have the
same location.</summary>
<param><c>p1</c> is the first Point to be compared.</param>
<param><c>p2</c> is the second Point to be compared.</param>
<returns>True if the Points do not have the same location and
the exact same type; otherwise, false.</returns>
<seealso cref="M:Graphics.Point.Equals(System.Object)"/>
<seealso cref=
"M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)"
/>
</member>
<member name="M:Graphics.Point.Main">
<summary>This is the entry point of the Point class testing
program.
<para>This program tests each method and operator, and
is intended to be run after any non-trvial maintenance has
been performed on the Point class.</para>
</summary>
</member>
<member name="P:Graphics.Point.X">
<value>Property <c>X</c> represents the point's
x-coordinate.</value>
</member>
<member name="P:Graphics.Point.Y">
<value>Property <c>Y</c> represents the point's
y-coordinate.</value>
</member>
</members>
</doc>
Resolución de métodos sobrecargados
15/11/2021 • 59 minutes to read
En la práctica, las reglas para determinar la resolución de sobrecarga están pensadas para encontrar la
sobrecarga "más cercana" a los argumentos reales proporcionados. Si hay un método cuyos tipos de parámetro
coinciden con los tipos de argumento, ese método es obviamente el más cercano. Salvo que, un método está
más cerca que otro si todos sus tipos de parámetro son más estrechos que (o igual que) los tipos de parámetros
del otro método. Si ninguno de los parámetros del método es más estrecho que el otro, no hay forma de
determinar qué método está más cerca de los argumentos.
Nota. La resolución de sobrecarga no tiene en cuenta el tipo de valor devuelto esperado del método.
Tenga en cuenta también que, debido a la sintaxis de parámetro con nombre, el orden de los parámetros reales
y formales puede no ser el mismo.
Dado un grupo de métodos, el método más aplicable en el grupo para una lista de argumentos se determina
mediante los pasos siguientes. Si, después de aplicar un paso determinado, no quedan miembros en el conjunto,
se produce un error en tiempo de compilación. Si solo un miembro permanece en el conjunto, ese miembro es
el miembro más aplicable. Los pasos son:
1. En primer lugar, si no se ha proporcionado ningún argumento de tipo, aplique la inferencia de tipos a los
métodos que tienen parámetros de tipo. Si la inferencia de tipos se realiza correctamente para un
método, se usan los argumentos de tipo deducidos para ese método concreto. Si se produce un error en
la inferencia de tipos para un método, ese método se elimina del conjunto.
2. A continuación, elimine todos los miembros del conjunto que sean inaccesibles o no sean aplicables
(sección aplicabilidad en lista de argumentos) a la lista de argumentos.
3. A continuación, si uno o más argumentos son AddressOf o expresiones lambda, calcule los niveles de
flexibilización de los delegados para cada argumento de este tipo, como se indica a continuación. Si el
peor nivel de flexibilización del delegado (más bajo) en N es peor que el nivel de flexibilización del
delegado más bajo en M , elimine N del conjunto. Los niveles de flexibilización de los delegados son los
siguientes:
ae. Nivel de flexibilización del delegado de error : Si la AddressOf expresión lambda o no se puede
convertir en el tipo de delegado.
af. Restricción de la flexibilización del delegado del tipo de valor devuelto o de los parámetros : Si el
argumento es AddressOf o una expresión lambda con un tipo declarado y la conversión de su tipo
de valor devuelto al tipo de valor devuelto del delegado es narrowing; o bien, si el argumento es
una expresión lambda normal y la conversión de cualquiera de sus expresiones devueltas al tipo
de valor devuelto o bien, si el argumento es una expresión lambda asincrónica y el tipo de valor
devuelto del delegado es Task(Of T) y la conversión de cualquiera de sus expresiones devueltas a
T es de restricción; o bien, si el argumento es una expresión lambda de iterador y el tipo de valor
devuelto de delegado IEnumerator(Of T) o IEnumerable(Of T) y la conversión de cualquiera de
sus operandos de rendimiento a T es de restricción.
ag. Flexibilizar la relajación del delegado para delegar sin firma : Si el tipo de delegado es
System.Delegate o System.MultiCastDelegate o System.Object .
ah. Drop Return o arguments Delegate relajación : Si el argumento es AddressOf o una expresión
lambda con un tipo de valor devuelto declarado y el tipo de delegado no tiene un tipo de valor
devuelto; o bien, si el argumento es una expresión lambda con una o más expresiones de retorno y
el tipo de delegado AddressOf no tiene parámetros.
ai. Flexibilizar la flexibilización del tipo de valor devuelto : Si el argumento es AddressOf o una
expresión lambda con un tipo de valor devuelto declarado, y hay una conversión de ampliación de
su tipo de valor devuelto al delegado; o bien, si el argumento es una expresión lambda regular en
la que la conversión de todas las expresiones de devolución al tipo de valor devuelto del delegado
es de ampliación o de o bien, si el argumento es una expresión lambda asincrónica y el delegado
es Task(Of T) o Task , y la conversión de todas las expresiones devueltas a T / Object
respectivamente es de ampliación o de identidad con al menos una ampliación; o bien, si el
argumento es una expresión lambda de iterador y el delegado es IEnumerator(Of T) o o
IEnumerable(Of T) IEnumerator IEnumerable , y la conversión de todas las expresiones devueltas
a T / Object es de ampliación o de identidad con al
aj. Relajación de delegación de identidad : Si el argumento es AddressOf o una expresión lambda que
coincide exactamente con el delegado, sin ampliación o reducción o eliminación de parámetros, o
devuelve o produce. Después, si algunos miembros del conjunto no requieren conversiones de
restricción para que se apliquen a ninguno de los argumentos, elimine todos los miembros que sí
lo hacen. Por ejemplo:
Imports System.Runtime.CompilerServices
Class C3
Sub M1(d As Integer)
End Sub
End Class
Module C3Extensions
<Extension> _
Sub M1(c3 As C3, c As Long)
End Sub
<Extension> _
Sub M1(c3 As C3, c As Short)
End Sub
End Module
Module Test
Sub Main()
Dim c As New C3()
Dim sVal As Short = 10
Dim lVal As Long = 20
Nota. Los métodos de extensión se omiten si hay métodos de instancia aplicables para garantizar que la
adición de una importación (que puede traer nuevos métodos de extensión en el ámbito) no provocará
que una llamada a un método de instancia existente se vuelva a enlazar a un método de extensión. Dado
el amplio ámbito de algunos métodos de extensión (es decir, los definidos en las interfaces y/o los
parámetros de tipo), se trata de un enfoque más seguro para enlazar a los métodos de extensión.
6. Después, si, dados dos miembros del conjunto M y N , M es más específico (la especificidad de la
sección de miembros o tipos dados una lista de argumentos) que N la lista de argumentos, elimine N
del conjunto. Si hay más de un miembro en el conjunto y los miembros restantes no son igualmente
específicos según la lista de argumentos, se genera un error en tiempo de compilación.
7. De lo contrario, dados dos miembros del conjunto, M y N , aplique las siguientes reglas de separación
de elementos de enlace, en orden:
bs. Si no M tiene un parámetro paramarray pero sí lo hace N , o si ambos sí lo hacen, pero M pasa
menos argumentos al parámetro paramarray que N , después, elimine N del conjunto. Por
ejemplo:
Module Test
Sub F(a As Object, ParamArray b As Object())
Console.WriteLine("F(Object, Object())")
End Sub
F(Object, Object())
F(Object, Object, Object())
F(Object, Object, Object())
G(Object)
Nota. Cuando una clase declara un método con un parámetro paramarray, no es raro incluir
también algunos de los formularios expandidos como métodos normales. Al hacerlo, es posible
evitar la asignación de una instancia de matriz que se produce cuando se invoca una forma
expandida de un método con un parámetro paramarray.
bt. Si M se define en un tipo más derivado que N , elimine N del conjunto. Por ejemplo:
Class Base
Sub F(Of T, U)(x As T, y As U)
End Sub
End Class
Class Derived
Inherits Base
Module Test
Sub Main()
Dim d As New Derived()
Esta regla también se aplica a los tipos en los que se definen los métodos de extensión. Por
ejemplo:
Imports System.Runtime.CompilerServices
Class Base
End Class
Class Derived
Inherits Base
End Class
Module BaseExt
<Extension> _
Sub M(b As Base, x As Integer)
End Sub
End Module
Module DerivedExt
<Extension> _
Sub M(d As Derived, x As Integer)
End Sub
End Module
Module Test
Sub Main()
Dim b As New Base()
Dim d As New Derived()
bu. Si M y N son métodos de extensión y el tipo de destino de M es una clase o estructura y el tipo
de destino de N es una interfaz, elimine N del conjunto. Por ejemplo:
Imports System.Runtime.CompilerServices
Interface I1
End Interface
Class C1
Implements I1
End Class
Module Ext1
<Extension> _
Sub M(i As I1, x As Integer)
End Sub
End Module
Module Ext2
<Extension> _
Sub M(c As C1, y As Integer)
End Sub
End Module
Module Test
Sub Main()
Dim c As New C1()
Imports System.Runtime.CompilerServices
Module Module1
Sub Main()
Dim x As Integer = 1
x.f(1) ' Calls first "f" extension method
Imports System.Runtime.CompilerServices
Class C1
End Class
Namespace N1
Module N1C1Extensions
<Extension> _
Sub M1(c As C1, x As Integer)
End Sub
End Module
End Namespace
Namespace N1.N2
Module N2C1Extensions
<Extension> _
Sub M1(c As C1, y As Integer)
End Sub
End Module
End Namespace
Namespace N1.N2.N3
Module Test
Sub Main()
Dim x As New C1()
Si los métodos de extensión se encuentran en el mismo paso, esos métodos de extensión son
ambiguos. Siempre se puede eliminar la ambigüedad de la llamada mediante el nombre del
módulo estándar que contiene el método de extensión y llamar al método de extensión como si
fuera un miembro normal. Por ejemplo:
Imports System.Runtime.CompilerServices
Class C1
End Class
Module C1ExtA
<Extension> _
Sub M(c As C1)
End Sub
End Module
Module C1ExtB
<Extension> _
Sub M(c As C1)
End Sub
End Module
Module Main
Sub Test()
Dim c As New C1()
M Se considera que un parámetro es igualmente genérico para un parámetro N si sus tipos Mt y Nt ambos
hacen referencia a los parámetros de tipo o ambos no hacen referencia a los parámetros de tipo. M se
considera menos genérico que si no N Mt hace referencia a un parámetro de tipo y lo Nt hace.
Por ejemplo:
Class C1(Of T)
Sub S1(Of U)(x As U, y As T)
End Sub
Sub S2(x As T, y As T)
End Sub
End Class
Module Test
Sub Main()
Dim x As C1(Of Integer) = New C1(Of Integer)
Los parámetros de tipo del método de extensión que se corrigieron durante currificación se consideran
parámetros de tipo en el tipo, no parámetros de tipo en el método. Por ejemplo:
Imports System.Runtime.CompilerServices
Module Ext1
<Extension> _
Sub M1(Of T, U)(x As T, y As U, z As U)
End Sub
End Module
Module Ext2
<Extension> _
Sub M1(Of T, U)(x As T, y As U, z As T)
End Sub
End Module
Module Test
Sub Main()
Dim i As Integer = 10
i.M1(10, 10)
End Sub
End Module
Profundidad de la genéricoidad
Se determina que un miembro M tiene mayor profundidad de genérico que un miembro N si, para cada par
de parámetros coincidentes Mj y Nj , tiene una Mj profundidad mayor o igual que, Nj y al menos uno Mj
tiene mayor profundidad de genérico. La profundidad de la genérica se define de la siguiente manera:
Cualquier cosa que no sea un parámetro de tipo tiene mayor profundidad de la genérica que un
parámetro de tipo;
De forma recursiva, un tipo construido tiene mayor profundidad de genérico que otro tipo construido
(con el mismo número de argumentos de tipo) si al menos un argumento de tipo tiene mayor
profundidad de genérico y ningún argumento de tipo tiene menos profundidad que el argumento de tipo
correspondiente en el otro.
Un tipo de matriz tiene mayor profundidad de genérico que otro tipo de matriz (con el mismo número de
dimensiones) si el tipo de elemento de la primera tiene mayor profundidad de genérico que el tipo de
elemento del segundo.
Por ejemplo:
Module Test
Sub Main()
Dim x As Task(Of Integer) = Nothing
f(x) ' Calls the first overload
End Sub
End Module
Si una expresión de un solo argumento coincide con un parámetro paramarray y el tipo de la expresión de
argumento se puede convertir en el tipo del parámetro paramarray y en el tipo de elemento ParamArray, el
método es aplicable tanto en su forma expandida como sin expandir, con dos excepciones. Si la conversión del
tipo de la expresión de argumento al tipo ParamArray es narrowing, el método solo se puede aplicar en su
forma expandida. Si la expresión de argumento es el literal Nothing , el método solo es aplicable en su forma
sin expandir. Por ejemplo:
Module Test
Sub F(ParamArray a As Object())
Dim o As Object
For Each o In a
Console.Write(o.GetType().FullName)
Console.Write(" ")
Next o
Console.WriteLine()
End Sub
Sub Main()
Dim a As Object() = { 1, "Hello", 123.456 }
Dim o As Object = a
F(a)
F(CType(a, Object))
F(o)
F(CType(o, Object()))
End Sub
End Module
En la primera y última invocación de F , la forma normal de F es aplicable porque existe una conversión de
ampliación desde el tipo de argumento al tipo de parámetro (ambos son del tipo Object() ) y el argumento se
pasa como un parámetro de valor normal. En la segunda y tercera invocación, la forma normal de F no es
aplicable porque no existe una conversión de ampliación desde el tipo de argumento al tipo de parámetro (las
conversiones de Object a son de Object() restricción). Sin embargo, la forma expandida de F es aplicable y
Object() la invocación crea un elemento. El único elemento de la matriz se inicializa con el valor de argumento
especificado (que a su vez es una referencia a un Object() ).
Pasar argumentos y seleccionar argumentos para los parámetros opcionales
Si un parámetro es un parámetro de valor, la expresión de argumento coincidente debe clasificarse como un
valor. El valor se convierte al tipo del parámetro y se pasa como el parámetro en tiempo de ejecución. Si el
parámetro es un parámetro de referencia y la expresión de argumento coincidente se clasifica como una
variable cuyo tipo es el mismo que el parámetro, se pasa una referencia a la variable como parámetro en tiempo
de ejecución.
De lo contrario, si la expresión de argumento coincidente se clasifica como una variable, un valor o un acceso de
propiedad, se asigna una variable temporal del tipo del parámetro. Antes de la invocación del método en tiempo
de ejecución, la expresión de argumento se reclasifica como un valor, se convierte al tipo del parámetro y se
asigna a la variable temporal. A continuación, se pasa una referencia a la variable temporal como parámetro.
Una vez evaluada la invocación del método, si la expresión de argumento se clasifica como una variable o un
acceso de propiedad, la variable temporal se asigna a la expresión de variable o a la expresión de acceso de
propiedad. Si la expresión de acceso a la propiedad no tiene ningún Set descriptor de acceso, no se realiza la
asignación.
Para los parámetros opcionales en los que no se ha proporcionado un argumento, el compilador elige los
argumentos como se describe a continuación. En todos los casos, se prueba con el tipo de parámetro después
de la sustitución de tipos genéricos.
Si el parámetro opcional tiene el atributo System.Runtime.CompilerServices.CallerLineNumber , y la
invocación procede de una ubicación en el código fuente, y un literal numérico que representa el número
de línea de esa ubicación tiene una conversión intrínseca en el tipo de parámetro, se utiliza el literal
numérico. Si la invocación abarca varias líneas, la elección de la línea que se va a usar depende de la
implementación.
Si el parámetro opcional tiene el atributo System.Runtime.CompilerServices.CallerFilePath , y la
invocación procede de una ubicación en el código fuente, y un literal de cadena que representa la ruta de
acceso del archivo de esa ubicación tiene una conversión intrínseca en el tipo de parámetro, se utiliza el
literal de cadena. El formato de la ruta de acceso del archivo depende de la implementación.
Si el parámetro opcional tiene el atributo System.Runtime.CompilerServices.CallerMemberName , y la
invocación está dentro del cuerpo de un miembro de tipo o en un atributo aplicado a cualquier parte de
ese miembro de tipo, y un literal de cadena que representa ese nombre de miembro tiene una conversión
intrínseca en el tipo de parámetro, se utiliza el literal de cadena. En el caso de las invocaciones que
forman parte de los descriptores de acceso de propiedad o los controladores de eventos personalizados,
el nombre de miembro utilizado es el de la propiedad o el propio evento. En el caso de las invocaciones
que forman parte de un operador o un constructor, se usa un nombre específico de la implementación.
Si no se aplica ninguno de los anteriores, se usa el valor predeterminado del parámetro opcional (o Nothing si
no se proporciona ningún valor predeterminado). Y si se aplica más de una de las anteriores, la elección del que
se va a usar depende de la implementación.
Los CallerLineNumber CallerFilePath atributos y son útiles para el registro. CallerMemberName Es útil para
implementar INotifyPropertyChanged . Estos son algunos ejemplos.
Sub Log(msg As String,
<CallerFilePath> Optional file As String = Nothing,
<CallerLineNumber> Optional line As Integer? = Nothing)
Console.WriteLine("{0}:{1} - {2}", file, line, msg)
End Sub
Private _p As Integer
Además de los parámetros opcionales anteriores, Microsoft Visual Basic también reconoce algunos parámetros
opcionales adicionales si se importan desde metadatos (es decir, desde una referencia de DLL):
Al importar desde metadatos, Visual Basic también trata el parámetro <Optional> como indicativo de
que el parámetro es opcional: de esta manera es posible importar una declaración que tenga un
parámetro opcional pero no un valor predeterminado, aunque no se pueda expresar mediante la
Optional palabra clave.
Si el parámetro opcional tiene el tipo Object y no especifica un valor predeterminado, el compilador usa
el argumento System.Reflection.Missing.Value .
Métodos condicionales
Si el método de destino al que hace referencia una expresión de invocación es una subrutina que no es
miembro de una interfaz y si el método tiene uno o más System.Diagnostics.ConditionalAttribute atributos, la
evaluación de la expresión depende de las constantes de compilación condicional definidas en ese punto del
archivo de código fuente. Cada instancia del atributo especifica una cadena, que asigna un nombre a una
constante de compilación condicional. Cada constante de compilación condicional se evalúa como si formara
parte de una instrucción de compilación condicional. Si la constante se evalúa como True , la expresión se
evalúa normalmente en tiempo de ejecución. Si la constante se evalúa como False , la expresión no se evalúa
en absoluto.
Al buscar el atributo, se comprueba la declaración más derivada de un método reemplazable.
Nota. El atributo no es válido en funciones o métodos de interfaz y se omite si se especifica en cualquier tipo de
método. Por lo tanto, los métodos condicionales solo aparecerán en las instrucciones de invocación.
Inferencia de argumentos de tipo
Cuando se llama a un método con parámetros de tipo sin especificar argumentos de tipo, se usa la inferencia de
argumentos de tipo para probar y deducir los argumentos de tipo de la llamada. Esto permite usar una sintaxis
más natural para llamar a un método con parámetros de tipo cuando los argumentos de tipo se pueden inferir
de forma trivial. Por ejemplo, dada la siguiente declaración de método:
Module Util
Function Choose(Of T)(b As Boolean, first As T, second As T) As T
If b Then
Return first
Else
Return second
End If
End Function
End Class
A través de la inferencia de argumentos de tipo, los argumentos de tipo Integer y String se determinan a
partir de los argumentos del método.
La inferencia del argumento de tipo se produce antes de que se realice la reclasificación de la expresión en
métodos lambda o punteros de método en la lista de argumentos, ya que la reclasificación de estos dos tipos de
expresiones puede requerir el tipo del parámetro que se va a conocer. Dado un conjunto de argumentos
A1,...,An , un conjunto de parámetros coincidentes P1,...,Pn y un conjunto de parámetros de tipo de
método T1,...,Tn , las dependencias entre los argumentos y los parámetros de tipo de método se recopilan
primero de la siguiente manera:
Si An es el Nothing literal, no se genera ninguna dependencia.
Si An es un método lambda y el tipo de Pn es un tipo de delegado construido o
System.Linq.Expressions.Expression(Of T) , donde T es un tipo de delegado construido,
Si el tipo de un parámetro de método lambda se deduce del tipo del parámetro correspondiente Pn y el
tipo del parámetro depende de un parámetro de tipo de método Tn , An tiene una dependencia en Tn
.
Si se especifica el tipo de un parámetro de método lambda y el tipo del parámetro correspondiente Pn
depende de un parámetro de tipo de método Tn , Tn tiene una dependencia en An .
Si el tipo de valor devuelto de Pn depende de un parámetro de tipo de método Tn , Tn tiene una
dependencia en An .
Si An es un puntero de método y el tipo de Pn es un tipo de delegado construido,
Si el tipo de valor devuelto de Pn depende de un parámetro de tipo de método Tn , Tn tiene una
dependencia en An .
Si Pn es un tipo construido y el tipo de Pn depende de un parámetro de tipo de método Tn , Tn tiene
una dependencia en An .
De lo contrario, no se genera ninguna dependencia.
Después de recopilar las dependencias, se eliminan todos los argumentos que no tienen dependencias. Si algún
parámetro de tipo de método no tiene dependencias de salida (es decir, el parámetro de tipo de método no
depende de un argumento), se produce un error en la inferencia de tipos. De lo contrario, los argumentos
restantes y los parámetros de tipo de método se agrupan en componentes fuertemente conectados. Un
componente con conexión segura es un conjunto de argumentos y parámetros de tipo de método, donde se
puede tener acceso a cualquier elemento del componente a través de las dependencias de otros elementos.
Los componentes fuertemente conectados se ordenan topológicamente y se procesan en orden topológico:
Si el componente fuertemente tipado contiene solo un elemento,
Si el elemento ya se ha marcado como completo, omítalo.
Si el elemento es un argumento, agregue sugerencias de tipo del argumento a los parámetros de
tipo de método que dependen de él y marque el elemento como completo. Si el argumento es un
método Lambda con parámetros que todavía necesitan tipos inferidos, deduzca Object para los
tipos de esos parámetros.
Si el elemento es un parámetro de tipo de método, inferir el parámetro de tipo de método para
que sea el tipo dominante entre las sugerencias de tipo de argumento y marcar el elemento como
completo. Si una sugerencia de tipo tiene una restricción de elemento de matriz, solo se tienen en
cuenta las conversiones válidas entre las matrices del tipo especificado (es decir, covariantes y
conversiones de matriz intrínseca). Si una sugerencia de tipo tiene una restricción de argumento
genérico en ella, solo se tienen en cuenta las conversiones de identidad. Si no se puede elegir
ningún tipo dominante, se produce un error en la inferencia. Si algún tipo de argumento de
método lambda depende de este parámetro de tipo de método, el tipo se propaga al método
lambda.
Si el componente fuertemente tipado contiene más de un elemento, el componente contiene un ciclo.
Para cada parámetro de tipo de método que sea un elemento del componente, si el parámetro de
tipo de método depende de un argumento que no está marcado como completo, convierta esa
dependencia en una aserción que se comprobará al final del proceso de inferencia.
Reinicie el proceso de inferencia en el punto en el que se determinaron los componentes
fuertemente tipados.
Si la inferencia de tipos se realiza correctamente para todos los parámetros de tipo de método, se comprueban
las dependencias que se cambiaron a aserciones. Una aserción se realiza correctamente si el tipo del argumento
es implícitamente convertible al tipo deducido del parámetro de tipo de método. Si se produce un error en una
aserción, se produce un error en la inferencia del argumento de tipo.
Dado un tipo Ta de argumento para un argumento A y un tipo Tp de parámetro para un parámetro P , las
sugerencias de tipo se generan como sigue:
Si no Tp implica ningún parámetro de tipo de método, no se genera ninguna sugerencia.
Si Tp y Ta son tipos de matriz del mismo rango, reemplace Ta y Tp por los tipos de elemento de Ta
y Tp y reinicie este proceso con una restricción de elemento de matriz.
Si Tp es un parámetro de tipo de método, Ta se agrega como una sugerencia de tipo con la restricción
actual, si existe.
Si A es un método lambda y es un tipo de delegado construido o
Tp
System.Linq.Expressions.Expression(Of T) , donde T es un tipo de delegado construido, para cada tipo
de parámetro de método lambda TL y el tipo de parámetro de delegado correspondiente TD ,
reemplace Ta por TL y Tp por TD y reinicie el proceso sin restricción. A continuación, reemplace Ta
por el tipo de valor devuelto del método lambda y:
Si Aes un método lambda normal, reemplace Tp por el tipo de valor devuelto del tipo de delegado;
Si A es un método lambda asincrónico y el tipo de valor devuelto del tipo de delegado tiene
Task(Of T) un formato para algunos T , reemplace Tp con eso T ;
Si A es un método lambda de iterador y el tipo de valor devuelto del tipo de delegado tiene el
formato IEnumerator(Of T) o IEnumerable(Of T) para algunos T , reemplace Tp por ese T .
A continuación, reinicie el proceso sin ninguna restricción.
Si A es un puntero de método y Tp es un tipo de delegado construido, use los tipos de parámetro de
Tp para determinar a qué método apunta más aplicable Tp . Si hay un método que es más aplicable,
reemplace Ta por el tipo de valor devuelto del método y Tp por el tipo de valor devuelto del tipo de
delegado y reinicie el proceso sin ninguna restricción.
De lo contrario, Tp debe ser un tipo construido. Dado TG , el tipo genérico de Tp ,
Si Ta es TG , hereda de TG o implementa el tipo TG exactamente una vez, para cada
argumento de tipo coincidente Tax de Ta y Tpx de Tp , reemplace Ta por Tax y Tp por
Tpx y reinicie el proceso con una restricción de argumento genérico.