Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Las instrucciones representan código ejecutable.
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 con 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 instrucciones y expresiones. El orden de ejecución depende de la instrucción o expresión en particular.
Por ejemplo, al evaluar un operador de suma ( Section Addition Operator), primero se evalúa el operando izquierdo, después el operando derecho y, a continuación, el propio operador. Los bloques se ejecutan (bloques de sección y etiquetas) ejecutando primero su primera subestado y, a continuación, continúa uno por uno a través de las instrucciones del bloque.
Implícito en este orden es el concepto de un punto de control, que es la siguiente operación que se va a ejecutar. Cuando se invoca un método (o "llamado"), decimos que crea una instancia del método . Una instancia de método consta de su propia copia de los parámetros y variables locales del método, y su propio punto de control.
Métodos normales
Este es un ejemplo de un método normal
Function Test() As Integer
Console.WriteLine("hello")
Return 1
End Function
Dim x = Test() ' invokes the function, prints "hello", assigns 1 to x
Cuando se invoca un método normal,
- En primer lugar, se crea una instancia del método específica de esa invocación. Esta instancia incluye una copia de todos los parámetros y variables locales del método .
- A continuación, todos sus parámetros se inicializan en los valores proporcionados y todas sus variables locales a los valores predeterminados de sus tipos.
- En el caso de ,
Functiontambién se inicializa una variable local implícita denominada variable de devolución de función cuyo nombre es el nombre de la función, cuyo tipo es el tipo devuelto de la función y cuyo valor inicial es el valor predeterminado de su tipo. - A continuación, el punto de control de la instancia del método se establece en la primera instrucción del cuerpo del método y el cuerpo del método comienza inmediatamente a ejecutarse desde allí (Bloques de sección y etiquetas).
Cuando el flujo de control sale normalmente del cuerpo del método , a través de alcanzar o End FunctionEnd Sub que marca su final, o a través de una instrucción o Exit explícitaReturn, el flujo de control vuelve al autor de la llamada de la instancia del método. Si hay una variable de devolución de función, el resultado de la invocación es el valor final de esta variable.
Cuando el flujo de control sale del cuerpo del método a través de una excepción no controlada, esa excepción se propaga al autor de la llamada.
Después de salir del flujo de control, ya no hay referencias dinámicas a la instancia del método. Si la instancia del método mantiene las únicas referencias a su copia de variables o parámetros locales, puede que se recopilen elementos no utilizados.
Métodos de iterador
Los métodos de iterador se usan como una manera cómoda de generar una secuencia, una que la instrucción puede consumir For Each . Los métodos de iterador usan la Yield instrucción (Section Yield Statement) para proporcionar elementos de la secuencia. (Un método de iterador sin instrucciones Yield producirá una secuencia vacía). Este es un ejemplo de un método de iterador:
Iterator Function Test() As IEnumerable(Of Integer)
Console.WriteLine("hello")
Yield 1
Yield 2
End Function
Dim en = Test()
For Each x In en ' prints "hello" before the first x
Console.WriteLine(x) ' prints "1" and then "2"
Next
Cuando se invoca un método iterador cuyo tipo de valor devuelto es IEnumerator(Of T),
- En primer lugar, se crea una instancia del método iterador específica de esa invocación. Esta instancia incluye una copia de todos los parámetros y variables locales del método .
- A continuación, todos sus parámetros se inicializan en los valores proporcionados y todas sus variables locales a los valores predeterminados de sus tipos.
- Una variable local implícita también se inicializa denominada variable actual del iterador, cuyo tipo es
Ty cuyo valor inicial es el valor predeterminado de su tipo. - A continuación, el punto de control de la instancia del método se establece en la primera instrucción del cuerpo del método.
- A continuación, se crea un objeto iterador , asociado a esta instancia de método. El objeto iterador implementa el tipo de valor devuelto declarado y tiene comportamiento como se describe a continuación.
- El flujo de control se reanuda inmediatamente en el autor de la llamada y el resultado de la invocación es el objeto iterador. Tenga en cuenta que esta transferencia se realiza sin salir de la instancia del método iterador y no hace que los controladores finalmente se ejecuten. El objeto iterador sigue haciendo referencia a la instancia del método y no se recopilará ningún elemento no utilizado siempre que exista una referencia activa al objeto iterador.
Cuando se accede a la propiedad del Current objeto iterador, se devuelve la variable actual de la invocación.
Cuando se invoca el método del MoveNext objeto iterador, la invocación no crea una nueva instancia de método. En su lugar, se usa la instancia de método existente (y su punto de control y variables y parámetros locales): la instancia que se creó cuando se invocó por primera vez el método iterador. El flujo de control reanuda la ejecución en el punto de control de esa instancia de método y continúa por el cuerpo del método iterador como normal.
Cuando se invoca el método del Dispose objeto iterador, se usa de nuevo la instancia de método existente. El flujo de control se reanuda en el punto de control de esa instancia de método, pero después se comporta inmediatamente como si una Exit Function instrucción fuera la siguiente operación.
Las descripciones anteriores del comportamiento para la invocación de MoveNext o Dispose en un objeto iterador solo se aplican si todas las invocaciones anteriores de MoveNext o Dispose en ese objeto iterador ya han devuelto a sus autores de llamada. Si no lo han hecho, el comportamiento no está definido.
Cuando el flujo de control sale normalmente del cuerpo del método de iterador , a través de alcanzar el End Function final que marca su final, o a través de una instrucción explícita Return , Exit debe haberlo hecho en el contexto de una invocación de MoveNext o Dispose función en un objeto iterador para reanudar la instancia del método iterador, y habrá estado usando la instancia de método que se creó cuando se invocó el método iterador por primera vez. El punto de control de esa instancia se deja en la End Function instrucción y el flujo de control se reanuda en el autor de la llamada; y si se había reanudado mediante una llamada a MoveNext , el valor False se devuelve al autor de la llamada.
Cuando el flujo de control sale del cuerpo del método iterador a través de una excepción no controlada, la excepción se propaga al autor de la llamada, que de nuevo será una invocación de MoveNext o de Dispose.
En cuanto a los otros tipos de valor devuelto posibles de una función de iterador,
- Cuando se invoca un método iterador cuyo tipo de valor devuelto es
IEnumerable(Of T)para algunosT, se crea una instancia de , específica de esa invocación del método iterador, de todos los parámetros del método y se inicializan con los valores proporcionados. El resultado de la invocación es un objeto que implementa el tipo de valor devuelto. Si se llama al método deGetEnumeratoreste objeto, crea una instancia específica de esa invocación deGetEnumerator, de todos los parámetros y variables locales del método . Inicializa los parámetros en los valores ya guardados y continúa como para el método iterador anterior. - Cuando se invoca un método iterador cuyo tipo de valor devuelto es la interfaz
IEnumeratorno genérica , el comportamiento es exactamente igual que paraIEnumerator(Of Object). - Cuando se invoca un método iterador cuyo tipo de valor devuelto es la interfaz
IEnumerableno genérica , el comportamiento es exactamente igual que paraIEnumerable(Of Object).
Métodos asincrónicos
Los métodos asincrónicos son una manera cómoda de realizar trabajos de larga duración sin bloquear, por ejemplo, la interfaz de usuario de una aplicación. Async es breve para Asincrónico : significa que el autor de la llamada del método asincrónico reanudará su ejecución rápidamente, pero la finalización final de la instancia del método asincrónico puede ocurrir en algún momento posterior en el futuro. Por convención, los métodos asincrónicos se denominan con el sufijo "Async".
Async Function TestAsync() As Task(Of String)
Console.WriteLine("hello")
Await Task.Delay(100)
Return "world"
End Function
Dim t = TestAsync() ' prints "hello"
Console.WriteLine(Await t) ' prints "world"
Nota. Los métodos asincrónicos 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 programe que se reanude en respuesta a algún evento.
Cuando se invoca un método asincrónico
- En primer lugar, se crea una instancia del método asincrónico específica de esa invocación. Esta instancia incluye una copia de todos los parámetros y variables locales del método .
- A continuación, todos sus parámetros se inicializan en los valores proporcionados y todas sus variables locales a los valores predeterminados de sus tipos.
- En el caso de un método asincrónico con tipo
Task(Of T)de valor devuelto para algunosT, también se inicializa una variable local implícita denominada variable de devolución de tarea, cuyo tipo esTy cuyo valor inicial es el valor predeterminado deT. - Si el método asincrónico es un
Functioncon tipoTaskde valor devuelto oTask(Of T)para algunosT, 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 su invocación. - A continuación, el punto de control de la instancia se establece en la primera instrucción del cuerpo del método asincrónico e inmediatamente comienza a ejecutar el cuerpo del método desde allí (Bloques de sección y etiquetas).
Delegado de reanudación y llamador actual
Como se detalla en Section Await Operator, la ejecución de una Await expresión tiene la capacidad de suspender el punto de control de la instancia del método dejando el flujo de control para ir a otro lugar. Posteriormente, el flujo de control se puede reanudar en el punto de control de la misma instancia mediante 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 finalmente se ejecuten. El delegado de reanudación sigue haciendo referencia a la instancia de método y al Task resultado ( Task(Of T) si existe) y no se recopilará ningún elemento no utilizado siempre que exista una referencia activa a delegado o resultado.
Resulta útil imaginar la instrucción Dim x = Await WorkAsync() aproximadamente como abreviada sintáctica para lo siguiente:
Dim temp = WorkAsync().GetAwaiter()
If Not temp.IsCompleted Then
temp.OnCompleted(resumptionDelegate)
Return [task]
CONT: ' invocation of 'resumptionDelegate' will resume here
End If
Dim x = temp.GetResult()
A continuación, el autor de la llamada actual de la instancia del método se define como el autor de la llamada original o el autor de la llamada del delegado de reanudación, lo que sea más reciente.
Cuando el flujo de control sale del cuerpo del método asincrónico , a través de alcanzar o End SubEnd Function que marca su final, o a través de una instrucción o Exit explícitaReturn, o a través de una excepción no controlada, el punto de control de la instancia se establece al final del método. El comportamiento depende del tipo de valor devuelto del método asincrónico.
En el caso de un
Async Functioncon tipoTaskde valor devuelto :Si el flujo de control sale a través de una excepción no controlada, el estado del objeto asincrónico se establece
TaskStatus.Faulteden y suException.InnerExceptionpropiedad se establece en la excepción (excepto: determinadas excepciones definidas por la implementación, comoOperationCanceledExceptioncambiarla aTaskStatus.Canceled). El flujo de control se reanuda en el llamador actual.De lo contrario, el estado del objeto asincrónico se establece en
TaskStatus.Completed. El flujo de control se reanuda en el llamador actual.(Nota. Todo el punto de Task y lo que hace interesantes los métodos asincrónicos es que, cuando una tarea se convierte en Completado, los métodos que estaban esperando tendrán actualmente sus delegados de reanudación ejecutados, es decir, se desbloquearán).
En el caso de un
Async Functioncon tipoTask(Of T)de valor devuelto para algunosT: el comportamiento es el anterior, excepto que, en casos que no son de excepción, la propiedad delResultobjeto asincrónico también se establece en el valor final de la variable de devolución de tarea.En el caso de :
Async Sub- Si el flujo de control sale a través de una excepción no controlada, esa excepción se propaga al entorno de alguna manera específica de la implementación. El flujo de control se reanuda en el llamador actual.
- De lo contrario, el flujo de control simplemente se reanuda en el llamador actual.
Sub asincrónico
Hay algún comportamiento específico de Microsoft de un Async Sub.
Si SynchronizationContext.Current está Nothing al principio de la invocación, las excepciones no controladas de un Sub asincrónico se publicarán en el grupo de subprocesos.
Si SynchronizationContext.Current no Nothing está al principio de la invocación, OperationStarted() se invoca en ese contexto antes del inicio del método y OperationCompleted() después del final. Además, las excepciones no controladas se publicarán para volver a iniciarse en el contexto de sincronización.
Esto significa que, en las aplicaciones de interfaz de usuario, para un Async Sub que se invoca en el subproceso de interfaz de usuario, las excepciones que no se pueden controlar se volverán a rellenar el subproceso de la interfaz de usuario.
Estructuras mutables en métodos asincrónicos e iteradores
Las estructuras mutables en general se consideran malas prácticas y no son compatibles con métodos asincrónicos o iteradores. En concreto, cada invocación de un método asincrónico o iterador en una estructura funcionará implícitamente en una copia de esa estructura que se copia en su momento de invocación. Por lo tanto, por ejemplo,
Structure S
Dim x As Integer
Async Sub Mutate()
x = 2
End Sub
End Structure
Dim s As New S With {.x = 1}
s.Mutate()
Console.WriteLine(s.x) ' prints "1"
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 ejecutada 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 a excepción de las instrucciones de declaración de etiqueta. Una etiqueta es un identificador que identifica una posición determinada dentro del bloque de instrucciones que se puede usar como destino de una instrucción de rama 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 tanto las instrucciones de declaración de etiqueta como las instrucciones de invocación pueden constar 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 dos puntos, aunque no se siga ninguna instrucción 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 usa la variable x name como parámetro y como etiqueta.
Function F(x As Integer) As Integer
If x >= 0 Then
GoTo x
End If
x = -x
x:
Return x
End Function
El ámbito de una etiqueta es el cuerpo del método que lo contiene.
Por motivos de legibilidad, las producciones de instrucciones que implican varios subestados se tratan como una sola producción en esta especificación, aunque las subestaciones pueden estar cada una por sí mismas en una línea etiquetada.
Variables y parámetros locales
En las secciones anteriores se detalla cómo y cuándo se crean las instancias de método y con ellas las copias de las variables y parámetros locales de un método. Además, cada vez que se escribe el cuerpo de un bucle, se realiza una nueva copia de cada variable local declarada dentro de ese bucle, como se describe en Instrucciones de bucle de sección, y la instancia del método ahora contiene esta copia de su variable local en lugar de la copia anterior.
Todos los locales se inicializan en el valor predeterminado de su tipo. Las variables locales y los parámetros siempre son accesibles públicamente. Se trata de un error para hacer referencia a una variable local en una posición textual que precede a su declaración, como se muestra en el ejemplo siguiente:
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
En el F método anterior, la primera asignación a i específicamente no hace referencia al campo declarado en el ámbito externo. En su lugar, hace referencia a la variable local y se produce un error porque precede textualmente a la declaración de la variable. En el G método , una declaración de variable posterior hace referencia a una variable local declarada en una declaración de variable anterior dentro de la misma declaración de variable local.
Cada bloque de un método crea un espacio de declaración para las variables locales. Los nombres se introducen en este espacio de declaración a través de declaraciones de variables locales en el cuerpo del método y a través de la lista de parámetros del método , que introduce nombres en el espacio de declaración del bloque más externo. Los bloques no permiten sombras de nombres a través del anidamiento: una vez que se ha declarado un nombre en un bloque, es posible que el nombre no se vuelva a declarar en ningún bloque anidado.
Por lo tanto, en el ejemplo siguiente, los F métodos y G están en error porque el nombre i se declara en el bloque externo y no se puede volver a declarar en el bloque interno. Sin embargo, los H métodos y I son válidos porque los dos ise declaran en bloques no anidados independientes.
Class A
Sub F()
Dim i As Integer = 0
If True Then
Dim i As Integer = 1
End If
End Sub
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, una variable local especial se declara implícitamente 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 semántica de resolución de nombres especial cuando se usa en expresiones. Si la variable local se usa en un contexto que espera una expresión clasificada como un grupo de métodos, como una expresión de invocación, el nombre se resuelve en la función en lugar de en la variable local. Por ejemplo:
Function F(i As Integer) As Integer
If i = 0 Then
F = 1 ' Sets the return value.
Else
F = F(i - 1) ' Recursive call.
End If
End Function
El uso de paréntesis puede provocar 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 en la función en lugar de en 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 deja el 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.
Instrucciones de declaración local
Una instrucción de declaración local declara una nueva variable local, una constante local o una variable estática.
Las variables locales y las constantes locales son equivalentes a las variables y constantes de instancia cuyo ámbito es el método y se declaran de la misma manera.
Las variables estáticas son similares a las Shared variables y se declaran mediante el Static modificador .
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 dentro Shared de los 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 en el método, las variables estáticas solo se inicializan con el valor predeterminado de su tipo cuando se inicializa la instancia de tipo o tipo. Es posible que las variables estáticas no se declaren en estructuras o métodos genéricos.
Las variables locales, las constantes locales y las variables estáticas siempre tienen accesibilidad pública y es posible que no especifiquen modificadores de accesibilidad. Si no se especifica ningún tipo en una instrucción de declaración local, los pasos siguientes determinan el tipo de la declaración local:
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.
Si la declaración local es una constante local o si la declaración local es una variable local con un inicializador y una inferencia de tipo de variable local se usa, 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 son necesarias para tener inicializadores).
Si no se usa la semántica estricta, el tipo de la instrucción de declaración local es implícitamente
Object.De lo contrario, se produce un error en tiempo de compilación.
Si no se especifica ningún tipo en una instrucción de declaración local que tiene un modificador de tipo de matriz o tamaño de matriz, el tipo de la declaración local es una matriz con el rango especificado y los pasos anteriores se usan para determinar el tipo de elemento de la matriz. Si se usa la inferencia de tipo de variable local, el tipo del inicializador debe ser un tipo de matriz con la misma forma de matriz (es decir, modificadores de tipo de matriz) que la instrucción de declaración local. Tenga en cuenta que es posible que el tipo de elemento inferido siga siendo un tipo de matriz. Por ejemplo:
Option Infer On
Module Test
Sub Main()
' Error: initializer is not an array type
Dim x() = 1
' Type is Integer()
Dim y() = New Integer() {}
' Type is Integer()()
Dim z() = New Integer()() {}
' Type is Integer()()()
Dim a()() = New Integer()()() {}
' Error: initializer does not have same array shape
Dim b()() = New Integer(,)() {}
End Sub
End Module
Si no se especifica ningún tipo en una instrucción de declaración local que tiene 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 inferido o el propio tipo inferido si ya es un tipo de valor que acepta valores NULL. Si el tipo inferido no es un tipo de valor que se puede hacer que acepta 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 de matriz o tamaño de matriz se colocan en una instrucción de declaración local sin tipo, el modificador de tipo que acepta valores NULL se considera que se aplica al tipo de elemento de la matriz y se usan los pasos anteriores para determinar el tipo de elemento.
Los inicializadores variables en las instrucciones de declaración local son equivalentes a las instrucciones de asignación colocadas en la ubicación textual de la declaración. Por lo tanto, si la ejecución se bifurca sobre la instrucción de declaración local, no se ejecuta el inicializador de variable. Si la instrucción de declaración local se ejecuta más de una vez, el inicializador de variable se ejecuta un número igual de veces. Las variables estáticas solo ejecutan su inicializador la 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
Console.WriteLine("Static variable x = " & x)
x += 1
End Sub
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
Este programa imprime:
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, el local estático tendrá su valor predeterminado y no se inicializará. 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
Private xInitFlag As InitFlag = New InitFlag()
Sub F()
Dim x As Integer
If xInitFlag.State <> 1 Then
Monitor.Enter(xInitFlag)
Try
If xInitFlag.State = 0 Then
xInitFlag.State = 2
x = 5
Else If xInitFlag.State = 2 Then
Throw New IncompleteInitialization()
End If
Finally
xInitFlag.State = 1
Monitor.Exit(xInitFlag)
End Try
End If
End Sub
End Module
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 en 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. Expresión de nombre simple que usa un nombre que no se resuelve en otra cosa declara una variable local por ese nombre. Por ejemplo:
Option Explicit Off
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 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 de miembro.
Las variables locales implícitas se tratan como si se declaran al principio del método contenedor. Por lo tanto, siempre se limitan a todo el cuerpo del método, 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. Las variables locales implícitas se escriben como Object si no se adjuntó ningún carácter de tipo al nombre de la variable; de lo contrario, el tipo de la variable es el tipo del carácter de tipo. La inferencia de tipos de variable local no se usa para variables locales implícitas.
Si el entorno de compilación especifica la declaración local explícita o Option Explicit, todas las variables locales deben declararse explícitamente y no se permite la declaración de variable implícita.
With (instrucción)
Una With instrucción permite varias referencias a los miembros de una expresión sin especificar la expresión varias veces.
WithStatement
: 'With' Expression StatementTerminator
Block?
'End' 'With' StatementTerminator
;
La expresión debe clasificarse como un valor y se evalúa una vez, tras la entrada en el bloque. Dentro del bloque de With instrucciones, una expresión de acceso de miembro o una expresión de acceso de diccionario a partir de 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
Function F() As Integer
Return 10
End Function
End Structure
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 bifurcar en un With bloque de instrucciones desde fuera del bloque.
SyncLock (instrucción)
Una SyncLock instrucción permite sincronizar instrucciones 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 debe clasificarse como un valor y se evalúa una vez al entrar en el bloque. Al escribir el SyncLock bloque, se llama al Shared método System.Threading.Monitor.Enter en la expresión especificada, que se bloquea hasta que el subproceso de ejecución tenga 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
Public Function Add() As Integer
SyncLock Me
count += 1
Add = count
End SyncLock
End Function
Public Function Subtract() As Integer
SyncLock Me
count -= 1
Subtract = count
End SyncLock
End Function
End Class
El ejemplo anterior se sincroniza en la instancia específica de la clase Test para asegurarse de que no puede agregar o restar más de un subproceso de ejecución de la variable count a la vez para una instancia determinada.
El SyncLock bloque está incluido implícitamente por una Try instrucción cuyo Finally bloque llama al Shared método System.Threading.Monitor.Exit en la expresión. Esto garantiza que el bloqueo se libere incluso cuando se produce 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 sola 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
Public Function Add() As Integer
Try
System.Threading.Monitor.Enter(Me)
count += 1
Add = count
Finally
System.Threading.Monitor.Exit(Me)
End Try
End Function
Public Function Subtract() As Integer
Try
System.Threading.Monitor.Enter(Me)
count -= 1
Subtract = count
Finally
System.Threading.Monitor.Exit(Me)
End Try
End Function
End Class
Instrucciones de evento
Las RaiseEventinstrucciones , AddHandlery RemoveHandler generan eventos y controlan los eventos de forma dinámica.
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 de 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 debe clasificarse como 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, mediante 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)
Public Sub Raise()
Static RaiseCount As Integer = 0
RaiseCount += 1
RaiseEvent E1(RaiseCount)
End Sub
End Class
Module Test
Private WithEvents x As Raiser
Private Sub E1Handler(Count As Integer) Handles x.E1
Console.WriteLine("Raise #" & Count)
End Sub
Public Sub Main()
x = New Raiser
x.Raise() ' Prints "Raise #1".
x.Raise() ' Prints "Raise #2".
x.Raise() ' Prints "Raise #3".
End Sub
End Module
La clase Raiser anterior es equivalente a:
Class Raiser
Public Event E1(Count As Integer)
Public Sub Raise()
Static RaiseCount As Integer = 0
Dim TemporaryDelegate As E1EventHandler
RaiseCount += 1
' Use a temporary to avoid a race condition.
TemporaryDelegate = E1Event
If Not TemporaryDelegate Is Nothing Then
TemporaryDelegate.Invoke(RaiseCount)
End If
End Sub
End Class
Instrucciones AddHandler y RemoveHandler
Aunque la mayoría de los controladores de eventos se enlazan automáticamente a través WithEvents de variables, puede ser necesario agregar y quitar dinámicamente controladores de eventos en tiempo de ejecución.
AddHandler y RemoveHandler las instrucciones hacen esto.
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 clasificada como acceso a eventos y el segundo argumento debe ser una expresión clasificada 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
Private Button1 As Button = New Button()
Sub Button1_Click(sender As Object, e As EventArgs)
Console.WriteLine("Button1 was clicked!")
End Sub
Public Sub Disconnect()
RemoveHandler Button1.Click, AddressOf Button1_Click
End Sub
End Class
Dado un eventoE,, la instrucción llama al método o remove_E pertinente add_E en la instancia para agregar o quitar el delegado como controlador del evento. Por lo tanto, el código anterior es equivalente a:
Public Class Form1
Public Sub New()
Button1.add_Click(AddressOf Button1_Click)
End Sub
Private Button1 As Button = New Button()
Sub Button1_Click(sender As Object, e As EventArgs)
Console.WriteLine("Button1 was clicked!")
End Sub
Public Sub Disconnect()
Button1.remove_Click(AddressOf Button1_Click)
End Sub
End Class
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
;
Instrucciones de asignación normales
Una instrucción de asignación simple almacena el resultado de una expresión en una variable.
RegularAssignmentStatement
: Expression Equals Expression StatementTerminator
;
La expresión del lado izquierdo del operador de asignación debe clasificarse como una variable o un acceso a propiedades, mientras que la expresión del lado derecho del operador de asignación debe clasificarse como un valor. El tipo de la expresión debe convertirse implícitamente en el tipo de acceso a la variable o propiedad.
Si la variable a la que se asigna 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 array. En el ejemplo siguiente, la última asignación hace que se produzca una System.ArrayTypeMismatchException excepción porque una instancia de ArrayList no se puede almacenar en un elemento de una String matriz.
Dim sa(10) As String
Dim oa As Object() = sa
oa(0) = Nothing ' This is allowed.
oa(1) = "Hello" ' This is allowed.
oa(2) = New ArrayList() ' System.ArrayTypeMismatchException is thrown.
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 acceso a propiedades, la instrucción de asignación convierte el acceso de propiedad en una invocación del descriptor de acceso de Set la propiedad con el valor sustituido por el parámetro value. Por ejemplo:
Module Test
Private PValue As Integer
Public Property P As Integer
Get
Return PValue
End Get
Set (Value As Integer)
PValue = Value
End Set
End Property
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 propiedades 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()
' Compile-time error: ct.P not classified as variable.
ct.P.F = 10
' Run-time exception.
rt.P.F = 10
End Sub
End Module
Tenga en cuenta que la semántica de la asignación depende del tipo de la variable o propiedad a la que se va a asignar. Si la variable a la que se va a asignar 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 va a asignar 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 viene determinada por si el tipo del valor es un tipo de valor o un tipo de referencia en tiempo de ejecución.
Nota. Para los tipos intrínsecos como Integer y Date, la semántica de asignación de valores y referencias son iguales porque los tipos son inmutables. Como resultado, el lenguaje es libre de usar la asignación de referencia en tipos intrínsecos boxed como optimización. Desde una perspectiva de valor, el resultado es el mismo.
Dado que el carácter igual (=) se usa tanto para la asignación como para la igualdad, hay 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 compuestas
Una instrucción de asignación compuesta toma el formato 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 debe clasificarse como una variable o acceso a propiedades, mientras que la expresión del lado derecho del operador de asignación debe clasificarse 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 del 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
Mid Assignment (instrucción)
Una Mid instrucción de asignación asigna una cadena a otra cadena. El lado izquierdo de la asignación tiene la misma sintaxis que una llamada a la función Microsoft.VisualBasic.Strings.Mid.
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 un acceso de propiedad cuyo tipo se puede convertir implícitamente a y desde String. El segundo parámetro es la posición inicial basada en 1 que corresponde a dónde debe comenzar la asignación en la cadena de destino y debe clasificarse como un valor cuyo tipo debe convertirse 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 puede convertir implícitamente en Integer. El lado derecho es la cadena de origen y debe clasificarse como un valor cuyo tipo se puede convertir implícitamente en String. El lado derecho se trunca al parámetro length, si se especifica, y reemplaza los caracteres de la cadena del lado izquierdo, empezando por la posición inicial. Si la cadena del lado derecho contenía 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
Nota.
Mid no es una palabra reservada.
Instrucciones de invocación
Una instrucción de invocación invoca un método precedido por la palabra clave Callopcional . 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 clasificarse como un valor o void. Se descarta cualquier valor resultante de la evaluación de la expresión de invocación.
Si se omite la Call 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 con un identificador. Por ejemplo, "Dim x = 1.ToString()" es una instrucción válida).
Hay 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, 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 ({Function() 15}(0))
' valid, since the invocation statement has no argument list
Dim x = {Function() 15}(0)
' valid as an expression, since (0) is taken as an array-indexing
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
Sub f() As Func(Of String,String)
Return Function(x) x
End Sub
End Module
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
;
Si... Entonces... Instrucciones Else
Una If...Then...Else instrucción es la instrucción condicional básica.
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 booleanas de sección. (Nota: esto no requiere que la expresión tenga un 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, se ejecuta el Else bloque . Una vez que un bloque termina de ejecutarse, 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
' Block If statement.
If a < b Then
a = b
Else
b = a
End If
' Line If statement
If a < b Then a = b Else b = a
End Sub
End Module
La versión de línea de la instrucción If enlaza menos estrechamente que ":" y sus Else enlaces al anterior If más cercano léxico permitido 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 line If , incluidas las instrucciones block. Sin embargo, es posible que no usen LineTerminators como StatementTerminators excepto en expresiones lambda de varias líneas. Por ejemplo:
' Allowed, since it uses : instead of LineTerminator to separate statements
If b Then With New String("a"(0),5) : Console.WriteLine(.Length) : End With
' Disallowed, since it uses a LineTerminator
If b then With New String("a"(0), 5)
Console.WriteLine(.Length)
End With
' Allowed, since it only uses LineTerminator inside a multi-line lambda
If b Then Call Sub()
Console.WriteLine("a")
End Sub.Invoke()
Seleccionar instrucciones Case
Una Select Case instrucción ejecuta instrucciones basadas en el valor de una expresión.
SelectStatement
: 'Select' 'Case'? Expression StatementTerminator
CaseStatement*
CaseElseStatement?
'End' 'Select' StatementTerminator
;
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 debe clasificarse como un valor. Cuando se ejecuta una Select Case 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 , ese bloque se ejecuta. Una vez que un bloque ha terminado de ejecutarse, la ejecución pasa al final de la Select instrucción .
No se permite la ejecución de un Case bloque para "caer" en la siguiente sección del modificador. Esto evita una clase común de errores que se producen en otros lenguajes cuando se omite accidentalmente una Case 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 seleccione para el mismo valor, Case 10 se ejecuta porque precede Case 20 - 10 textualmente. Cuando se alcanza el siguiente Case , la ejecución continúa después de la Select instrucción .
Una Case cláusula puede tener dos formas. Un formulario es una palabra clave opcional Is , un operador de comparación y una expresión. La expresión se convierte en el tipo de la Select expresión; si la expresión no se convierte implícitamente en el 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; De lo contrario, se produce un error en tiempo de compilación.
El otro formulario es una expresión seguida opcionalmente por la palabra clave To y una segunda expresión. Ambas expresiones se convierten en el tipo de la Select expresión; si cualquiera de las dos expresiones no se puede convertir implícitamente en el 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 E1y la segunda Case expresión es E2, se Case evalúa como E = E1 (si no se especifica) E2 o (E >= E1) And (E <= E2). Los operadores deben ser válidos para los tipos de las dos expresiones; De lo 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
For Each lambda In lambdas
lambda()
Next
End Sub
End Module
El código genera la salida:
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 a la copia original de x. Y cuando se crea una expresión lambda, recuerda la copia de una variable actual en el momento en que se creó. Por lo tanto, cada lambda usa la misma copia compartida de x, pero una copia diferente de y. Al final del programa, cuando ejecuta las expresiones lambda, esa copia compartida de x la que hacen referencia está ahora en su valor final 3.
Tenga en cuenta que si no hay expresiones lambda o LINQ, es imposible saber que se realiza una copia nueva en la entrada de bucle. De hecho, las optimizaciones del compilador evitarán realizar copias en este caso. Tenga en cuenta también que no es válido en GoTo un bucle que contenga expresiones lambda o LINQ.
Mientras... End While y Do... Instrucciones de bucle
Bucles While de instrucción de bucle o Do basados en 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 bucles siempre que la expresión booleana se evalúe como true; una Do instrucción loop 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 booleanas de sección. (Nota: esto no requiere que la expresión tenga un tipo booleano). También es válido especificar ninguna expresión en absoluto; en ese caso, el bucle nunca se cerrará. Si la expresión se coloca después Dode , se evaluará antes de que se ejecute el bloque de bucle en cada iteración. Si la expresión se coloca después Loopde , se evaluará después de que el bloque de bucle se haya ejecutado en cada iteración. Al colocar la expresión después Loop , se generará un bucle más que la colocación después Dode . El siguiente ejemplo demuestra 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
El código genera la salida:
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 tener como prefijo una While palabra clave o una Until palabra clave . El primero interrumpe el bucle si la condición se evalúa como false, esta última cuando la condición se evalúa como true.
Nota.
Until no es una palabra reservada.
Para... Instrucciones Next
Bucles For...Next de instrucción basados en 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 enlazada superior y una expresión de valor de paso opcional. La variable de control de bucle se especifica a través de un identificador seguido de una cláusula opcional As 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 preexistente o a una expresión.
Si la variable de control de bucle es un identificador con una
Ascláusula , el identificador define una nueva variable local del tipo especificado en laAscláusula , con ámbito a todoForel bucle.Si la variable de control de bucle es un identificador sin una
Ascláusula , el identificador se resuelve primero mediante las reglas de resolución de nombres simples (vea Expresiones de nombre simple de sección), excepto que esta aparición del identificador no estaría en y de sí mismo hará que se cree una variable local implícita (Declaraciones locales implícitas de sección).Si esta resolución se realiza correctamente y el resultado se clasifica como una variable, la variable de control de bucle es esa variable preexistente.
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, a continuación:
- si se usa la inferencia de tipos de variable local, el identificador define una nueva variable local cuyo tipo se deduce de las expresiones enlazadas y de paso, con ámbito a todo
Forel bucle; - si la inferencia de tipos de variable local no se usa, pero la declaración local implícita es , se crea una variable local implícita cuyo ámbito es todo el método (Declaraciones locales implícitas de sección) y la variable de control de bucle hace referencia a esta variable preexistente;
- Si no se usan inferencias de tipo de variable local ni declaraciones locales implícitas, se produce un error.
- si se usa la inferencia de tipos de variable local, el identificador define una nueva variable local cuyo tipo se deduce de las expresiones enlazadas y de paso, con ámbito a todo
Si la resolución se realiza correctamente con algo clasificado como un tipo ni una variable, se trata de un error.
Si la variable de control de bucle es una expresión, la expresión debe clasificarse como una variable.
Otra instrucción envolvente For...Next no puede usar una variable de control de bucle. El tipo de la variable de control de bucle de una For instrucción determina el tipo de iteración y debe ser uno de los siguientes:
-
Byte,SByte,UShort,Short,UInteger,Integer,ULong,Long,Decimal, ,SingleDouble - Tipo enumerado
Object- Tipo
Tque tiene los operadores siguientes, dondeBes un tipo que se puede usar en una expresión booleana:
Public Shared Operator >= (op1 As T, op2 As T) As B
Public Shared Operator <= (op1 As T, op2 As T) As B
Public Shared Operator - (op1 As T, op2 As T) As T
Public Shared Operator + (op1 As T, op2 As T) As T
Las expresiones enlazadas y de paso deben convertirse implícitamente en el 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 paso. Si no hay ninguna 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 igual que en tiempo de compilación, con dos excepciones. En primer lugar, si las expresiones enlazadas y de paso son todos de tipos enteros, pero no tienen ningún 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, Double 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, se producirá una System.InvalidCastException excepción . Una vez elegido un tipo de control de bucle al principio del bucle, se usará el mismo tipo en toda 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 instrucción coincidente Next . Una Next instrucción sin una variable coincide con la instrucción open For más interna, mientras que una Next instrucción con una o más variables de control de bucles coincidirá, de izquierda a derecha, 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 produce un error en tiempo de compilación.
Al principio del bucle, las tres expresiones se evalúan en orden textual y la expresión enlazada inferior se asigna a la variable de control de bucle. Si se omite el valor del paso, se convierte implícitamente en el tipo 1de la variable de control de bucle. Las tres expresiones solo se evalúan al 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 así, 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 viene determinado por si la expresión step >= step - step es true o false. En la Next instrucción , el valor del paso 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. A este respecto, la For instrucción difiere de For Each (Sección Para cada... Instrucciones Siguientes).
No es válido bifurcar en un For bucle desde fuera del bucle.
Para cada... Instrucciones Next
Bucles For Each...Next de instrucciones basados en 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 a través de un identificador seguido de una cláusula opcional As o una expresión.
ForEachStatement
: 'For' 'Each' LoopControlVariable 'In' LineTerminator? Expression StatementTerminator
Block?
( 'Next' NextExpressionList? StatementTerminator )?
;
Siguiendo las mismas reglas que For...Next las instrucciones (sección Para... Instrucciones siguientes), la variable de control de bucle hace referencia a una nueva variable local específica de esta variable For Each... Siguiente instrucción, o a una variable preexistente, o a una expresión.
La expresión del enumerador debe clasificarse 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 desde el tipo de elemento de la colección hasta el tipo de la variable de control de bucle.
Otra instrucción envolvente For Each no puede usar la variable de control de bucle. Una For Each instrucción debe cerrarse mediante una instrucción coincidente Next . Una Next instrucción sin una variable de control de bucle coincide con el más interno abierto For Each. Una Next instrucción con una o varias variables de control de bucle coincidirá, de izquierda a derecha, 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.
Se dice que un tipo C es un tipo de colección si uno de:
Todas las siguientes son verdaderas:
-
Ccontiene una instancia accesible, un método compartido o de extensión con la firmaGetEnumerator()que devuelve un tipoE. -
Econtiene una instancia accesible, un método compartido o de extensión con la firmaMoveNext()y el tipoBooleande valor devuelto . -
Econtiene una instancia accesible o una propiedad compartida denominadaCurrentque 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 el tipo de elemento de la colección se consideraT.Implementa la interfaz
System.Collections.IEnumerable, en cuyo caso el tipo de elemento de la colección se consideraObject.
A continuación se muestra un ejemplo de una clase que se puede enumerar:
Public Class IntegerCollection
Private integers(10) As Integer
Public Class IntegerCollectionEnumerator
Private collection As IntegerCollection
Private index As Integer = -1
Friend Sub New(c As IntegerCollection)
collection = c
End Sub
Public Function MoveNext() As Boolean
index += 1
Return index <= 10
End Function
Public ReadOnly Property Current As Integer
Get
If index < 0 OrElse index > 10 Then
Throw New System.InvalidOperationException()
End If
Return collection.integers(index)
End Get
End Property
End Class
Public Sub New()
Dim i As Integer
For i = 0 To 10
integers(i) = I
Next i
End Sub
Public Function GetEnumerator() As IntegerCollectionEnumerator
Return New IntegerCollectionEnumerator(Me)
End Function
End Class
Antes de que comience el bucle, se evalúa la expresión del enumerador. Si el tipo de la expresión no satisface el patrón 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 la interfaz no genérica es preferible 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. La interfaz no genérica se prefiere en el caso enlazado en tiempo de ejecución, ya que seleccionar la interfaz genérica significaría que todas las llamadas a los métodos de interfaz implicarían parámetros de tipo. Dado que no es posible conocer los argumentos de tipo coincidente 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 podrí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 valor 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:
- Si la variable de control de bucle identificó una nueva variable local (en lugar de una existente previamente), se crea una nueva copia de esta variable local. Para la iteración actual, todas las referencias dentro del bloque de bucles harán referencia a esta copia.
- La
Currentpropiedad se recupera, se convierte en el 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. - Se ejecuta el bloque de bucle.
Nota. Hay un ligero cambio en el comportamiento entre la versión 10.0 y la 11.0 del idioma. Antes de la versión 11.0, no se creó una variable de iteración nueva para cada iteración del bucle. Esta diferencia solo es observable si una expresión lambda o LINQ captura la variable de iteración, que se invoca después del bucle:
Dim lambdas As New List(Of Action)
For Each x In {1,2,3}
lambdas.Add(Sub() Console.WriteLine(x)
Next
lambdas(0).Invoke()
lambdas(1).Invoke()
lambdas(2).Invoke()
Hasta Visual Basic 10.0, esto produjo una advertencia en tiempo de compilación e imprimió "3" tres veces. Esto se debe a que solo había una sola variable "x" compartida por todas las iteraciones del bucle, y las tres expresiones lambda capturaron la misma "x", y en el momento en que las lambdas se ejecutaron entonces tenían el número 3. A partir de Visual Basic 11.0, imprime "1, 2, 3". Esto se debe a que cada lambda captura una variable diferente "x".
Nota. El elemento actual de la iteración se convierte en el tipo de la variable de control de bucle incluso si la conversión es explícita porque no hay ningún lugar conveniente para introducir un operador de conversión en la instrucción . Esto se volvió especialmente problemático al trabajar con el tipo System.Collections.ArrayListobsoleto ahora , porque su tipo de elemento es Object. Esto habría requerido conversiones en un gran número de bucles, algo que sentíamos que no era ideal. Curiosamente, los genéricos habilitaron la creación de una colección fuertemente tipada, , System.Collections.Generic.List(Of T)que podría haber hecho que replanteemos este punto de diseño, pero por motivos de compatibilidad, esto no se puede cambiar ahora.
Cuando se alcanza la instrucción , la Next 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 código siguiente:
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
Es equivalente al código siguiente:
Module Test
Sub Main()
Dim i As Integer
Dim c As IntegerCollection = New IntegerCollection()
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 usa el control de errores no estructurado, la For Each instrucción se ajusta en una Try instrucción con el Dispose método llamado en Finally para garantizar la limpieza.
Nota. El System.Array tipo es un tipo de colección y, dado que todos los tipos de matriz derivan de System.Array, cualquier expresión de tipo de matriz se permite en una For Each instrucción . En el caso de las matrices unidimensionales, la For Each instrucción enumera los elementos de matriz en el orden creciente del índice, empezando por el índice 0 y terminando con la longitud del índice - 1. En el caso de las matrices multidimensionales, los índices de la dimensión situada más a la derecha se incrementan primero.
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 bifurcar en un For Each bloque de instrucciones desde fuera del bloque.
Instrucciones Exception-Handling
Visual Basic admite el control estructurado de excepciones y el control de excepciones no estructurados. Solo se puede usar un estilo de control de excepciones en un método, pero la Error instrucción se puede usar en el control de excepciones estructurado. Si un método usa ambos estilos de control de excepciones, se produce un error en tiempo de compilación.
ErrorHandlingStatement
: StructuredErrorStatement
| UnstructuredErrorStatement
;
Instrucciones Exception-Handling estructuradas
El control de excepciones estructurado es un método de control de errores mediante la declaración de bloques explícitos dentro de los que se controlarán determinadas excepciones. El control de excepciones estructurado se realiza a través de una Try instrucción .
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: try blocks, catch blocks y finally blocks. 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 van a ejecutar cuando se sale de la Try 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 finalmente. No es válido transferir explícitamente la ejecución a un bloque try excepto dentro de un bloque catch en la misma instrucción.
Bloques Finally
Un Finally bloque siempre se ejecuta cuando la ejecución deja cualquier 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 bloque y, a continuación, transferirá la Finally ejecución a su destino previsto. El Finally bloque se ejecuta independientemente de cómo sale la ejecución de la Try instrucción: hasta el final del Try bloque, hasta el final de un Catch bloque, a través de una Exit Try instrucción, a través de una GoTo instrucción o no controlando una excepción iniciada.
Tenga en cuenta que la expresión en un método asincrónico y la Yield instrucción de un método iterador pueden hacer que el Await flujo de control se suspenda en la instancia del método asincrónico o iterador y reanudarse en alguna otra instancia de método. Sin embargo, esto es simplemente una suspensión de la ejecución y no implica salir del método asincrónico o la instancia del método iterador correspondientes, por lo que no hace Finally que se ejecuten bloques.
No es válido transferir explícitamente la ejecución a un Finally bloque; también no es válido transferir la ejecución fuera de un Finally bloque, excepto a través de una excepción.
FinallyStatement
: 'Finally' StatementTerminator
Block?
;
Bloques catch
Si se produce una excepción al procesar el Try bloque, cada Catch instrucción se examina en texto para determinar si controla la excepción.
CatchStatement
: 'Catch' ( Identifier ( 'As' NonArrayTypeName )? )?
( 'When' BooleanExpression )? StatementTerminator
Block?
;
El identificador especificado en una Catch cláusula representa la excepción que se ha producido. Si el identificador contiene una As cláusula , se considera que el identificador se declara dentro Catch del espacio de declaración local del bloque. De lo contrario, el identificador debe ser una variable local (no una variable estática) definida en un bloque contenedor.
Una Catch cláusula sin identificador detectará todas las excepciones derivadas de System.Exception. Una Catch cláusula con un identificador solo detectará excepciones cuyos tipos son iguales o derivados del tipo del identificador. El tipo debe ser System.Exception, o un tipo derivado de System.Exception. Cuando se detecta una excepción que deriva de System.Exception, una referencia al objeto de excepción se almacena en el objeto devuelto por la función Microsoft.VisualBasic.Information.Err.
Una Catch cláusula con una When cláusula solo detectará excepciones cuando la expresión se evalúe como True; el tipo de la expresión debe ser una expresión booleana según las expresiones booleanas de sección. Una When cláusula solo se aplica después de comprobar el tipo de excepción y la expresión puede hacer referencia al identificador que representa la excepción, como se muestra en este ejemplo:
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
En este ejemplo se imprime:
Third handler
Si una Catch cláusula controla la excepción, la Catch ejecución se transfiere al bloque . Al final del bloque, la Catch ejecución se transfiere a la primera instrucción después de la Try instrucción . La Try instrucción no controlará las excepciones producidas 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 código siguiente 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
Function F() As Boolean
Console.WriteLine("Filter")
Return True
End Function
Sin embargo, los métodos Async e Iterator hacen que todos los bloques finales dentro de ellos se ejecuten antes de cualquier filtro fuera. Por ejemplo, si el código anterior tuviera Async Sub Foo(), la salida sería "Finally, Filter, Catch".
Throw (instrucción)
La Throw instrucción genera una excepción, representada por una instancia de un tipo derivado de System.Exception.
ThrowStatement
: 'Throw' Expression? StatementTerminator
;
Si la expresión no se clasifica como un valor o no es un tipo derivado de System.Exception, se produce un error en tiempo de compilación. Si la expresión se evalúa como un valor NULL en tiempo de ejecución, se genera una System.NullReferenceException excepción en su lugar.
Una Throw instrucción puede omitir la expresión dentro de un bloque catch de una Try instrucción, siempre y cuando no intervenga el bloque finally. En ese caso, la instrucción vuelve a generar 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
Instrucciones Exception-Handling no estructuradas
El control de excepciones no estructurado es un método de control de errores indicando instrucciones a las que se bifurca cuando se produce una excepción. El control de excepciones no estructurado se implementa mediante tres instrucciones: la Error instrucción , la On Error instrucción y la Resume instrucción .
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
Cuando un método usa el control de excepciones no estructurados, se establece un único controlador de excepciones estructurado para todo el método que detecta todas las excepciones. (Tenga en cuenta que, en los constructores, este controlador no se extiende a través de la llamada a New al principio del constructor). A continuación, el método realiza un seguimiento de la ubicación del controlador de excepciones más reciente y de la excepción más reciente que se ha producido. En la entrada al método , la ubicación del controlador de excepciones y la excepción se establecen en Nothing. Cuando se produce una excepción en un método que usa el control de excepciones no estructurados, una referencia al objeto de excepción se almacena en el objeto devuelto por la función Microsoft.VisualBasic.Information.Err.
No se permiten instrucciones de control de errores no estructurados en métodos iteradores o asincrónicos.
Error (instrucción)
Una Error instrucción produce una System.Exception excepción que contiene un número de excepción de Visual Basic 6. La expresión debe clasificarse como un valor y su tipo debe convertirse implícitamente en Integer.
ErrorStatement
: 'Error' Expression StatementTerminator
;
On Error (instrucción)
Una On Error instrucción modifica el estado de control de excepciones más reciente.
OnErrorStatement
: 'On' 'Error' ErrorClause StatementTerminator
;
ErrorClause
: 'GoTo' '-' '1'
| 'GoTo' '0'
| GoToStatement
| 'Resume' 'Next'
;
Se puede usar de una de estas cuatro maneras:
On Error GoTo -1restablece la excepción más reciente aNothing.On Error GoTo 0restablece la ubicación del controlador de excepciones más reciente aNothing.On Error GoTo LabelNameestablece la etiqueta como la ubicación del controlador de excepciones más reciente. Esta instrucción no se puede usar en un método que contenga una expresión lambda o de consulta.On Error Resume Nextestablece elResume Nextcomportamiento como la ubicación más reciente del controlador de excepciones.
Resume (instrucción)
Una Resume instrucción devuelve la ejecución a la instrucción que provocó la excepción más reciente.
ResumeStatement
: 'Resume' ResumeClause? StatementTerminator
;
ResumeClause
: 'Next'
| LabelName
;
Si se especifica el modificador, la Next ejecución vuelve a la instrucción que se habría ejecutado después de la instrucción que provocó la excepción más reciente. Si se especifica un nombre de etiqueta, la ejecución vuelve a la etiqueta.
Dado que la SyncLock instrucción contiene un bloque Resume de control de errores estructurado implícito y Resume Next tiene comportamientos especiales para las excepciones que se producen en SyncLock instrucciones .
Resume devuelve la ejecución al principio de la SyncLock instrucción , mientras que Resume Next devuelve la ejecución a la siguiente instrucción después de la SyncLock instrucción . Por ejemplo, considere el código siguiente:
Class LockClass
End Class
Module Test
Sub Main()
Dim FirstTime As Boolean = True
Dim Lock As LockClass = New LockClass()
On Error GoTo Handler
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
Imprime el resultado siguiente.
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 al final de la SyncLock instrucción .
Resume y Resume Next no se permiten dentro de una SyncLock instrucción .
En todos los casos, cuando se ejecuta una Resume instrucción , la excepción más reciente se establece Nothingen . Si una Resume instrucción se ejecuta sin ninguna excepción más reciente, la instrucción genera una System.Exception excepción que contiene el número 20 de error de Visual Basic (Reanudar sin error).
Instrucciones branch
Las instrucciones de rama modifican el flujo de ejecución en un método . Hay seis instrucciones de rama:
- Una
GoToinstrucción hace que la ejecución se transfiera a la etiqueta especificada en el método . No se permite enGoToun bloque , ,UsingSyncLock, oWithForFor Each, ni en ningún bloque de bucle si se captura una variable local de ese bloque en una expresión lambda o LINQ.Try - Una
Exitinstrucción transfiere la ejecución a la siguiente instrucción después del final de la instrucción block que contiene inmediatamente el tipo especificado. Si el bloque es el bloque de métodos, el flujo de control sale del método tal y como se describe en Flujo de control de sección. Si laExitinstrucción no está incluida en el tipo de bloque especificado en la instrucción , se produce un error en tiempo de compilación. - Una
Continueinstrucción transfiere la ejecución al final de la instrucción de bucle de bloque que contiene inmediatamente el tipo especificado. Si laContinueinstrucción no está incluida en el tipo de bloque especificado en la instrucción , se produce un error en tiempo de compilación. - Una
Stopinstrucción hace que se produzca una excepción del depurador. - Una
Endinstrucción finaliza el programa. Los finalizadores se ejecutan antes del apagado, pero no se ejecutan los bloques finales de las instrucciones que se están ejecutandoTryactualmente. Es posible que esta instrucción no se use en programas que no sean ejecutables (por ejemplo, archivos DLL). - Una
Returninstrucción sin expresión es equivalente a unaExit Subinstrucción oExit Function. UnaReturninstrucción con una expresión solo se permite en un método normal que es una función o en un método asincrónico que es una función con tipoTask(Of T)de valor devuelto para algunosT. La expresión debe clasificarse como un valor que se puede convertir implícitamente en la variable de devolución de función (en el caso de los métodos regulares) o en la variable de devolución de tarea (en el caso de los métodos asincrónicos). Su comportamiento es evaluar su expresión y, a continuación, almacenarla en la variable de retorno y, a continuación, ejecutar una instrucción implícitaExit Function.
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
;
Instrucciones Array-Handling
Dos instrucciones simplifican el trabajo con matrices: ReDim instrucciones e Erase instrucciones.
ArrayHandlingStatement
: RedimStatement
| EraseStatement
;
Instrucción ReDim
Una instrucción crea una ReDim instancia de las nuevas matrices.
RedimStatement
: 'ReDim' 'Preserve'? RedimClauses StatementTerminator
;
RedimClauses
: RedimClause ( Comma RedimClause )*
;
RedimClause
: Expression ArraySizeInitializationModifier
;
Cada cláusula de la instrucción debe clasificarse como una variable o un acceso de propiedad cuyo tipo es un tipo de matriz o Object, y debe ir seguido de una lista de límites de matriz. El número de 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 especificadas y el tipo de elemento de matriz es Object. Si el número determinado de dimensiones no es compatible con la variable o 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
' The next two statements are equivalent.
ReDim o(10,30)
o = New Object(10, 30) {}
' The next two statements are equivalent.
ReDim b(10)
b = New Byte(10) {}
' Error: Incorrect number of dimensions.
ReDim i(10, 30, 40)
End Sub
End Module
Si se especifica la Preserve palabra clave , las expresiones también deben clasificarse como un valor y el nuevo tamaño de cada dimensión excepto el más derecho 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 mayor, los elementos adicionales se inicializarán en el valor predeterminado del tipo de elemento de la matriz. Por ejemplo, considere el código siguiente:
Module Test
Sub Main()
Dim x(5, 5) As Integer
x(3, 3) = 3
ReDim Preserve x(5, 6)
Console.WriteLine(x(3, 3) & ", " & x(3, 6))
End Sub
End Module
Imprime el resultado siguiente:
3, 0
Si la referencia de matriz existente es un valor NULL en tiempo de ejecución, no se da ningún error. Aparte de la dimensión situada más a la derecha, si cambia el tamaño de una dimensión, se producirá una System.ArrayTypeMismatchException excepción .
Nota.
Preserve no es una palabra reservada.
Erase (instrucción)
Una Erase instrucción establece cada una de las variables o propiedades de matriz especificadas en la instrucción Nothingen . Cada expresión de la instrucción debe clasificarse como una variable o acceso de propiedad cuyo tipo es un tipo de matriz o Object. Por ejemplo:
Module Test
Sub Main()
Dim x() As Integer = New Integer(5) {}
' The following two statements are equivalent.
Erase x
x = Nothing
End Sub
End Module
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 recolección y no se encuentran referencias dinámicas a la instancia. Si un tipo se mantiene en un recurso especialmente valioso y escaso (como conexiones de base de datos o identificadores de archivos), puede que no sea conveniente esperar hasta que la siguiente recolección de elementos no utilizados limpie una instancia determinada 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 . Tipo que lo hace expone un Dispose método al que se puede llamar para forzar que los recursos valiosos se liberen inmediatamente, como tal:
Module Test
Sub Main()
Dim x As DBConnection = New DBConnection("...")
' Do some work
...
x.Dispose() ' Free the connection
End Sub
End Module
La Using instrucción automatiza el proceso de adquisición de un recurso, la ejecución de un conjunto de instrucciones y la eliminación del recurso. La instrucción puede adoptar dos formas: en una, el recurso es una variable local declarada como parte de la instrucción y tratada como una instrucción regular de declaración de variable local; en el otro, el recurso es el resultado de una expresión.
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, cuyo ámbito es el bloque de Using instrucciones y deben incluir un inicializador. Si el recurso es el resultado de una expresión, la expresión debe clasificarse como un valor y debe ser de un tipo que se pueda convertir implícitamente en System.IDisposable. La expresión solo se evalúa una vez, al principio de la instrucción .
El Using bloque se incluye 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 produce 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 sola 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:
Using f As C = New C()
...
End Using
es equivalente a:
Dim f As C = New C()
Try
...
Finally
If f IsNot Nothing Then
f.Dispose()
End If
End Try
Una Using instrucción que tiene una instrucción de declaración de variable local puede adquirir varios recursos a la vez, que es equivalente a instrucciones anidadas Using . Por ejemplo, una Using instrucción del formulario:
Using r1 As R = New R(), r2 As R = New R()
r1.F()
r2.F()
End Using
es equivalente a:
Using r1 As R = New R()
Using r2 As R = New R()
r1.F()
r2.F()
End Using
End Using
Await (instrucción)
Una instrucción await tiene la misma sintaxis que una expresión de operador await (Section Await Operator), solo se permite en métodos que también permiten expresiones await y tienen el mismo comportamiento que una expresión de operador await.
Sin embargo, puede clasificarse como un valor o 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 están relacionadas con los métodos de iterador, que se describen en Métodos de iterador de sección.
YieldStatement
: 'Yield' Expression StatementTerminator
;
Yield es una palabra reservada si el método envolvente inmediatamente 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 proporciona en otro lugar. También no se proporciona en directivas de preprocesador. La instrucción yield solo se permite en el cuerpo de un método o expresión lambda donde es una palabra reservada. Dentro del método de inclusión inmediata o lambda, es posible que la instrucción yield no se produzca dentro del cuerpo de un Catch bloque o Finally , ni dentro del cuerpo de una SyncLock instrucción .
La instrucción yield toma una sola expresión que debe clasificarse como un valor y cuyo tipo se puede convertir implícitamente al tipo de la variable actual del iterador ( Métodos de iterador de sección) de su método iterador envolvente.
El flujo de control solo alcanza una Yield instrucción cuando se invoca el MoveNext método en un objeto iterador. (Esto se debe a que una instancia de método iterador solo ejecuta sus instrucciones debido a los métodos o a los MoveNext que se llama en un objeto iterador; y el Dispose método solo ejecutará código en Finally bloques, donde Yield no se Dispose permite).
Cuando se ejecuta una Yield instrucción, su expresión se evalúa y almacena en la variable actual del iterador de la instancia del método iterador asociada a ese objeto iterador. El valor True se devuelve al invocador de y el punto de MoveNextcontrol de esta instancia deja de avanzar hasta la siguiente invocación de MoveNext en el objeto iterador.
Visual Basic language spec