Istruzioni - Visual Basic

Le istruzioni rappresentano il codice eseguibile.

Statement
    : LabelDeclarationStatement
    | LocalDeclarationStatement
    | WithStatement
    | SyncLockStatement
    | EventStatement
    | AssignmentStatement
    | InvocationStatement
    | ConditionalStatement
    | LoopStatement
    | ErrorHandlingStatement
    | BranchStatement
    | ArrayHandlingStatement
    | UsingStatement
	| AwaitStatement
	| YieldStatement
    ;

Nota. Il compilatore Microsoft Visual Basic consente solo istruzioni che iniziano con una parola chiave o un identificatore. Pertanto, ad esempio, l'istruzione di chiamata "Call (Console).WriteLine" è consentita, ma l'istruzione di chiamata "(Console).WriteLine" non è .

Flusso di controllo

Il flusso di controllo è la sequenza in cui vengono eseguite istruzioni ed espressioni. L'ordine di esecuzione dipende dall'istruzione o dall'espressione specifica.

Ad esempio, quando si valuta un operatore di addizione (operatore di addizione sezione), viene valutato prima l'operando sinistro, quindi l'operando destro e quindi l'operatore stesso. I blocchi vengono eseguiti ( blocchi di sezione ed etichette) eseguendo prima il primo sottostatement e quindi procedendo uno alla volta tramite le istruzioni del blocco.

In questo ordine si intende il concetto di punto di controllo, ovvero l'operazione successiva da eseguire. Quando viene richiamato un metodo (o "chiamato"), si dice che crea un'istanza del metodo . Un'istanza del metodo è costituita dalla propria copia dei parametri del metodo e delle variabili locali e del proprio punto di controllo.

Metodi regolari

Di seguito è riportato un esempio di metodo regolare

Function Test() As Integer
    Console.WriteLine("hello")
    Return 1
End Function

Dim x = Test()    ' invokes the function, prints "hello", assigns 1 to x

Quando viene richiamato un metodo regolare,

  1. Per prima cosa viene creata un'istanza del metodo specifica per tale chiamata. Questa istanza include una copia di tutti i parametri e delle variabili locali del metodo .
  2. Tutti i relativi parametri vengono quindi inizializzati nei valori forniti e tutte le variabili locali ai valori predefiniti dei relativi tipi.
  3. Nel caso di , viene inizializzata anche una Functionvariabile locale implicita denominata variabile restituita dalla funzione il cui nome è il nome della funzione, il cui tipo è il tipo restituito della funzione e il cui valore iniziale è l'impostazione predefinita del relativo tipo.
  4. Il punto di controllo dell'istanza del metodo viene quindi impostato alla prima istruzione del corpo del metodo e il corpo del metodo inizia immediatamente a essere eseguito da questa posizione ( Blocchi di sezione ed etichette).

Quando il flusso di controllo esce normalmente dal corpo del metodo, attraverso il raggiungimento dell'oggetto End Function o End Sub che ne contrassegna la fine o tramite un'istruzione o Exit esplicitaReturn, il flusso di controllo torna al chiamante dell'istanza del metodo. Se è presente una variabile restituita dalla funzione, il risultato della chiamata è il valore finale di questa variabile.

Quando il flusso di controllo esce dal corpo del metodo tramite un'eccezione non gestita, tale eccezione viene propagata al chiamante.

Dopo l'uscita del flusso di controllo, non sono più presenti riferimenti in tempo reale all'istanza del metodo. Se l'istanza del metodo ha mantenuto gli unici riferimenti alla relativa copia di variabili o parametri locali, è possibile che vengano eseguite operazioni di Garbage Collection.

Metodi iteratore

I metodi iteratori vengono usati come modo pratico per generare una sequenza, una che può essere utilizzata dall'istruzione For Each . I metodi iteratore usano l'istruzione Yield ( Istruzione Yield section) per fornire elementi della sequenza. Un metodo iteratore senza Yield istruzioni produrrà una sequenza vuota. Di seguito è riportato un esempio di metodo iteratore:

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

Quando viene richiamato un metodo iteratore il cui tipo restituito è IEnumerator(Of T),

  1. Prima viene creata un'istanza del metodo iteratore specifica per tale chiamata. Questa istanza include una copia di tutti i parametri e delle variabili locali del metodo .
  2. Tutti i relativi parametri vengono quindi inizializzati nei valori forniti e tutte le variabili locali ai valori predefiniti dei relativi tipi.
  3. Viene inizializzata anche una variabile locale implicita denominata variabile corrente iteratore, il cui tipo è T e il cui valore iniziale è l'impostazione predefinita del relativo tipo.
  4. Il punto di controllo dell'istanza del metodo viene quindi impostato nella prima istruzione del corpo del metodo.
  5. Viene quindi creato un oggetto iteratore , associato a questa istanza del metodo. L'oggetto iteratore implementa il tipo restituito dichiarato e ha un comportamento come descritto di seguito.
  6. Il flusso di controllo viene quindi ripreso immediatamente nel chiamante e il risultato della chiamata è l'oggetto iteratore. Si noti che questo trasferimento viene eseguito senza uscire dall'istanza del metodo iteratore e non fa sì che i gestori finalmente vengano eseguiti. L'istanza del metodo fa ancora riferimento all'oggetto iteratore e non verrà sottoposto a Garbage Collection finché esiste un riferimento attivo all'oggetto iteratore.

Quando si accede alla proprietà dell'oggetto Current iteratore, viene restituita la variabile corrente della chiamata.

Quando viene richiamato il metodo dell'oggetto MoveNext iteratore, la chiamata non crea una nuova istanza del metodo. Viene invece usata l'istanza del metodo esistente (e il relativo punto di controllo e variabili e parametri locali): l'istanza creata quando è stato richiamato per la prima volta il metodo iteratore. Il flusso di controllo riprende l'esecuzione nel punto di controllo dell'istanza del metodo e procede nel corpo del metodo iteratore come di consueto.

Quando viene richiamato il metodo dell'oggetto Dispose iteratore, viene usata di nuovo l'istanza del metodo esistente. Il flusso di controllo riprende al punto di controllo dell'istanza del metodo, ma si comporta immediatamente come se un'istruzione Exit Function fosse l'operazione successiva.

Le descrizioni precedenti del comportamento per la chiamata di MoveNext o Dispose su un oggetto iteratore si applicano solo se tutte le chiamate precedenti di MoveNext o Dispose su tale oggetto iteratore sono già state restituite ai chiamanti. In caso contrario, il comportamento non è definito.

Quando il flusso di controllo esce normalmente dal corpo del metodo iteratore, attraverso il raggiungimento dell'estremità End Function finale o tramite un'istruzione o Exit esplicitaReturn, deve aver eseguito questa operazione nel contesto di una chiamata di MoveNext o Dispose funzione su un oggetto iteratore per riprendere l'istanza del metodo iteratore ed è stata usata l'istanza del metodo creata al primo richiamo del metodo iteratore. Il punto di controllo dell'istanza viene lasciato nell'istruzione End Function e il flusso di controllo riprende nel chiamante e, se è stato ripreso da una chiamata a MoveNext , il valore False viene restituito al chiamante.

Quando il flusso di controllo esce dal corpo del metodo iteratore tramite un'eccezione non gestita, l'eccezione viene propagata al chiamante, che sarà di nuovo una chiamata di MoveNext o di Dispose.

Per quanto riguarda gli altri possibili tipi restituiti di una funzione iteratore,

  • Quando viene richiamato un metodo iteratore il cui tipo restituito è IEnumerable(Of T) per alcuni T, viene creata per la prima volta un'istanza specifica di tale chiamata al metodo iteratore , di tutti i parametri nel metodo e vengono inizializzati con i valori specificati. Il risultato della chiamata è un oggetto che implementa il tipo restituito. Se il metodo di GetEnumerator questo oggetto deve essere chiamato, crea un'istanza, specifica di quella chiamata di GetEnumerator , di tutti i parametri e delle variabili locali nel metodo . Inizializza i parametri sui valori già salvati e procede come per il metodo iteratore precedente.
  • Quando viene richiamato un metodo iteratore il cui tipo restituito è l'interfaccia IEnumeratornon generica , il comportamento è esattamente come per IEnumerator(Of Object).
  • Quando viene richiamato un metodo iteratore il cui tipo restituito è l'interfaccia IEnumerablenon generica , il comportamento è esattamente come per IEnumerable(Of Object).

Metodi asincroni

I metodi asincroni sono un modo pratico per eseguire operazioni a esecuzione prolungata senza ad esempio bloccare l'interfaccia utente di un'applicazione. Async è breve per Asincrona : significa che il chiamante del metodo asincrono riprenderà immediatamente l'esecuzione, ma il completamento finale dell'istanza del metodo asincrono potrebbe verificarsi in un secondo momento in futuro. Per convenzione, i metodi asincroni vengono denominati con il suffisso "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. I metodi asincroni non vengono eseguiti in un thread in background. Consentono invece a un metodo di sospendere se stesso tramite l'operatore Await e di pianificare se stesso in risposta a un evento.

Quando viene richiamato un metodo asincrono

  1. Per prima cosa viene creata un'istanza del metodo asincrono specifica per tale chiamata. Questa istanza include una copia di tutti i parametri e delle variabili locali del metodo .
  2. Tutti i relativi parametri vengono quindi inizializzati nei valori forniti e tutte le variabili locali ai valori predefiniti dei relativi tipi.
  3. Nel caso di un metodo asincrono con tipo Task(Of T) restituito per alcuni T, viene inizializzata anche una variabile locale implicita denominata variabile restituita dall'attività, il cui tipo è T e il cui valore iniziale è l'impostazione predefinita di T.
  4. Se il metodo asincrono è un Function oggetto con tipo Task restituito o Task(Of T) per alcuni T, un oggetto di quel tipo creato in modo implicito, associato alla chiamata corrente. Si tratta di un oggetto asincrono e rappresenta il lavoro futuro che verrà eseguito eseguendo l'istanza del metodo asincrono. Quando il controllo riprende nel chiamante di questa istanza del metodo asincrono, il chiamante riceverà questo oggetto asincrono come risultato della chiamata.
  5. Il punto di controllo dell'istanza viene quindi impostato alla prima istruzione del corpo del metodo asincrono e inizia immediatamente a eseguire il corpo del metodo da questa posizione ( Blocchi di sezione ed etichette).

Delegato di ripresa e chiamante corrente

Come descritto in Section Await Operator, l'esecuzione di un'espressione Await ha la possibilità di sospendere il punto di controllo dell'istanza del metodo lasciando il flusso di controllo per andare altrove. Il flusso di controllo può riprendere in un secondo momento nel punto di controllo della stessa istanza tramite la chiamata di un delegato di ripresa. Si noti che questa sospensione viene eseguita senza uscire dal metodo asincrono e non fa sì che i gestori finalmente vengano eseguiti. L'istanza del metodo viene ancora fatto riferimento sia dal delegato di ripresa che dal Task risultato o Task(Of T) (se presente) e non verrà sottoposto a Garbage Collection finché esiste un riferimento attivo al delegato o al risultato.

È utile immaginare l'istruzione Dim x = Await WorkAsync() approssimativamente come sintassi abbreviata per quanto segue:

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()

Nell'esempio seguente il chiamante corrente dell'istanza del metodo viene definito come il chiamante originale o il chiamante del delegato di ripresa, a qualsiasi livello più recente.

Quando il flusso di controllo esce dal corpo del metodo asincrono, tramite il raggiungimento dell'oggetto o End Function che ne contrassegna la End Sub fine o tramite un'istruzione esplicita Return o Exit o tramite un'eccezione non gestita, il punto di controllo dell'istanza viene impostato alla fine del metodo. Il comportamento dipende quindi dal tipo restituito del metodo asincrono.

  • Nel caso di un oggetto con tipo Taskrestituito Async Function :

    1. Se il flusso di controllo viene chiuso tramite un'eccezione non gestita, lo stato dell'oggetto asincrono viene impostato su e la relativa Exception.InnerException proprietà viene impostata TaskStatus.Faulted sull'eccezione (ad eccezione di alcune eccezioni definite dall'implementazione, ad OperationCanceledException esempio modificarlo in TaskStatus.Canceled). Il flusso di controllo riprende nel chiamante corrente.

    2. In caso contrario, lo stato dell'oggetto asincrono è impostato su TaskStatus.Completed. Il flusso di controllo riprende nel chiamante corrente.

      (Nota. L'intero punto di Task, e ciò che rende interessanti i metodi asincroni, è che quando un'attività diventa Completata, tutti i metodi in attesa di esso avranno attualmente i delegati di ripresa eseguiti, ovvero diventano sbloccati.

  • Nel caso di un Async Function oggetto con tipo Task(Of T) restituito per alcuni Telementi : il comportamento è precedente, ad eccezione del fatto che in casi non eccezioni la proprietà dell'oggetto Result asincrono viene impostata anche sul valore finale della variabile restituita dall'attività.

  • Nel caso di un oggetto Async Sub:

    1. Se il flusso di controllo viene chiuso tramite un'eccezione non gestita, tale eccezione viene propagata all'ambiente in modo specifico dell'implementazione. Il flusso di controllo riprende nel chiamante corrente.
    2. In caso contrario, il flusso di controllo riprende semplicemente nel chiamante corrente.

Sottosincrona asincrona

Esiste un comportamento specifico di Microsoft di un oggetto Async Sub.

Se SynchronizationContext.Current si trova Nothing all'inizio della chiamata, tutte le eccezioni non gestite da un sub asincrono verranno pubblicate nel pool di thread.

Se SynchronizationContext.Current non Nothing è all'inizio della chiamata, OperationStarted() viene richiamato su tale contesto prima dell'inizio del metodo e OperationCompleted() dopo la fine. Inoltre, eventuali eccezioni non gestite verranno registrate per essere rigenerate nel contesto di sincronizzazione.

Ciò significa che nelle applicazioni dell'interfaccia utente, per un oggetto Async Sub richiamato nel thread dell'interfaccia utente, tutte le eccezioni che non riesce a gestire verranno ripubblicate nel thread dell'interfaccia utente.

Strutture modificabili in metodi asincroni e iteratori

Le strutture modificabili in generale sono considerate una pratica non valida e non sono supportate dai metodi asincroni o iteratori. In particolare, ogni chiamata di un metodo asincrono o iteratore in una struttura opera in modo implicito su una copia di tale struttura copiata al momento della chiamata. Pertanto, ad esempio,

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"

Blocchi ed etichette

Un gruppo di istruzioni eseguibili è denominato blocco di istruzioni. L'esecuzione di un blocco di istruzioni inizia con la prima istruzione nel blocco. Dopo l'esecuzione di un'istruzione, viene eseguita l'istruzione successiva nell'ordine lessicale, a meno che un'istruzione non trasferisca l'esecuzione altrove o si verifichi un'eccezione.

All'interno di un blocco di istruzioni, la divisione delle istruzioni sulle righe logiche non è significativa, ad eccezione delle istruzioni di dichiarazione di etichetta. Un'etichetta è un identificatore che identifica una particolare posizione all'interno del blocco di istruzioni che può essere usata come destinazione di un'istruzione branch, ad GoToesempio .

Block
    : Statements*
    ;

LabelDeclarationStatement
    : LabelName ':'
    ;

LabelName
    : Identifier
    | IntLiteral
    ;

Statements
    : Statement? ( ':' Statement? )*
    ;

Le istruzioni di dichiarazione di etichetta devono essere visualizzate all'inizio di una riga logica e le etichette possono essere un identificatore o un valore letterale integer. Poiché entrambe le istruzioni di dichiarazione di etichetta e le istruzioni di chiamata possono essere costituite da un singolo identificatore, un singolo identificatore all'inizio di una riga locale viene sempre considerato un'istruzione di dichiarazione di etichetta. Le istruzioni di dichiarazione di etichetta devono essere sempre seguite da due punti, anche se non seguono istruzioni sulla stessa riga logica.

Le etichette hanno uno spazio di dichiarazione personalizzato e non interferiscono con altri identificatori. L'esempio seguente è valido e usa la variabile x name sia come parametro che come etichetta.

Function F(x As Integer) As Integer
    If x >= 0 Then
        GoTo x
    End If
    x = -x
x: 
    Return x
End Function

L'ambito di un'etichetta è il corpo del metodo che lo contiene.

Per motivi di leggibilità, le produzioni di istruzioni che coinvolgono più sottostatement vengono considerate come una singola produzione in questa specifica, anche se le sottostatement possono essere ognuna di esse in una riga etichettata.

Variabili e parametri locali

Le sezioni precedenti illustrano in dettaglio come e quando vengono create le istanze del metodo e con esse le copie delle variabili e dei parametri locali di un metodo. Inoltre, ogni volta che viene immesso il corpo di un ciclo, viene creata una nuova copia di ogni variabile locale dichiarata all'interno di tale ciclo, come descritto in Istruzioni ciclo sezione e l'istanza del metodo contiene ora questa copia della variabile locale anziché la copia precedente.

Tutte le variabili locali vengono inizializzate nel valore predefinito del tipo. Le variabili e i parametri locali sono sempre accessibili pubblicamente. Si tratta di un errore per fare riferimento a una variabile locale in una posizione testuale che precede la relativa dichiarazione, come illustrato nell'esempio seguente:

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

F Nel metodo sopra, la prima assegnazione a i non si riferisce specificamente al campo dichiarato nell'ambito esterno. Piuttosto, fa riferimento alla variabile locale ed è in errore perché precede in modo testuale la dichiarazione della variabile. G Nel metodo, una dichiarazione di variabile successiva fa riferimento a una variabile locale dichiarata in una dichiarazione di variabile precedente all'interno della stessa dichiarazione di variabile locale.

Ogni blocco in un metodo crea uno spazio di dichiarazione per le variabili locali. I nomi vengono introdotti in questo spazio di dichiarazione tramite dichiarazioni di variabili locali nel corpo del metodo e tramite l'elenco di parametri del metodo, che introduce i nomi nello spazio di dichiarazione del blocco più esterno. I blocchi non consentono l'ombreggiatura dei nomi tramite l'annidamento: una volta dichiarato un nome in un blocco, il nome potrebbe non essere dichiarato nuovamente in alcun blocco annidato.

Nell'esempio seguente, pertanto, i F metodi e G sono in errore perché il nome i viene dichiarato nel blocco esterno e non può essere dichiarato nuovamente nel blocco interno. Tuttavia, i H metodi e I sono validi perché i due ivengono dichiarati in blocchi non annidati separati.

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

Quando il metodo è una funzione, una variabile locale speciale viene dichiarata in modo implicito nello spazio di dichiarazione del corpo del metodo con lo stesso nome del metodo che rappresenta il valore restituito della funzione. La variabile locale ha una semantica di risoluzione dei nomi speciale se usata nelle espressioni. Se la variabile locale viene usata in un contesto che prevede un'espressione classificata come gruppo di metodi, ad esempio un'espressione di chiamata, il nome viene risolto nella funzione anziché nella variabile locale. Per esempio:

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

L'uso delle parentesi può causare situazioni ambigue,ad F(1)esempio , dove F è una funzione il cui tipo restituito è una matrice unidimensionale; in tutte le situazioni ambigue, il nome viene risolto nella funzione anziché nella variabile locale. Per esempio:

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

Quando il flusso di controllo lascia il corpo del metodo, il valore della variabile locale viene passato all'espressione di chiamata. Se il metodo è una subroutine, non esiste una variabile locale implicita di questo tipo e il controllo torna semplicemente all'espressione di chiamata.

Istruzioni di dichiarazione locale

Un'istruzione di dichiarazione locale dichiara una nuova variabile locale, una costante locale o una variabile statica. Le variabili locali e le costanti locali sono equivalenti alle variabili di istanza e alle costanti con ambito del metodo e vengono dichiarate nello stesso modo. Le variabili statiche sono simili alle Shared variabili e vengono dichiarate usando il Static modificatore.

LocalDeclarationStatement
    : LocalModifier VariableDeclarators StatementTerminator
    ;

LocalModifier
    : 'Static' | 'Dim' | 'Const'
    ;

Le variabili statiche sono variabili locali che mantengono il valore tra le chiamate del metodo. Le variabili statiche dichiarate all'interno di metodi non condivisi sono per ogni istanza: ogni istanza del tipo che contiene il metodo ha una propria copia della variabile statica. Le variabili statiche dichiarate all'interno Shared dei metodi sono per tipo. Per tutte le istanze è disponibile una sola copia della variabile statica. Mentre le variabili locali vengono inizializzate sul valore predefinito del tipo in ogni voce nel metodo, le variabili statiche vengono inizializzate solo sul valore predefinito del tipo quando viene inizializzata l'istanza del tipo o del tipo. Le variabili statiche non possono essere dichiarate in strutture o metodi generici.

Le variabili locali, le costanti locali e le variabili statiche hanno sempre accessibilità pubblica e potrebbero non specificare modificatori di accessibilità. Se non viene specificato alcun tipo in un'istruzione di dichiarazione locale, i passaggi seguenti determinano il tipo della dichiarazione locale:

  1. Se la dichiarazione ha un carattere di tipo, il tipo del carattere di tipo è il tipo della dichiarazione locale.

  2. Se la dichiarazione locale è una costante locale o se la dichiarazione locale è una variabile locale con un inizializzatore e viene utilizzata l'inferenza del tipo di variabile locale, il tipo della dichiarazione locale viene dedotto dal tipo dell'inizializzatore. Se l'inizializzatore fa riferimento alla dichiarazione locale, si verifica un errore in fase di compilazione. Le costanti locali sono necessarie per avere inizializzatori.

  3. Se la semantica strict non viene usata, il tipo dell'istruzione di dichiarazione locale è implicitamente Object.

  4. In caso contrario, si verifica un errore in fase di compilazione.

Se non viene specificato alcun tipo in un'istruzione di dichiarazione locale con un modificatore di tipo matrice o di matrice, il tipo della dichiarazione locale è una matrice con il rango specificato e i passaggi precedenti vengono usati per determinare il tipo di elemento della matrice. Se viene usata l'inferenza del tipo di variabile locale, il tipo dell'inizializzatore deve essere un tipo di matrice con la stessa forma matrice (ad esempio modificatori di tipo matrice) come istruzione di dichiarazione locale. Si noti che è possibile che il tipo di elemento dedotto sia ancora un tipo di matrice. Per esempio:

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

Se non viene specificato alcun tipo in un'istruzione di dichiarazione locale con un modificatore di tipo nullable, il tipo della dichiarazione locale è la versione nullable del tipo dedotto o del tipo dedotto se è già un tipo di valore nullable. Se il tipo dedotto non è un tipo valore che può essere reso nullable, si verifica un errore in fase di compilazione. Se un modificatore di tipo nullable e un modificatore di tipo matrice o dimensione matrice vengono inseriti in un'istruzione di dichiarazione locale senza alcun tipo, il modificatore di tipo nullable viene considerato applicabile al tipo di elemento della matrice e i passaggi precedenti vengono usati per determinare il tipo di elemento.

Gli inizializzatori di variabili nelle istruzioni di dichiarazione locale sono equivalenti alle istruzioni di assegnazione posizionate nella posizione testuale della dichiarazione. Pertanto, se i rami di esecuzione sull'istruzione di dichiarazione locale, l'inizializzatore di variabile non viene eseguito. Se l'istruzione di dichiarazione locale viene eseguita più volte, l'inizializzatore di variabile viene eseguito un numero uguale di volte. Le variabili statiche eseguono l'inizializzatore solo la prima volta. Se si verifica un'eccezione durante l'inizializzazione di una variabile statica, la variabile statica viene considerata inizializzata con il valore predefinito del tipo della variabile statica.

L'esempio seguente illustra l'uso di inizializzatori:

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

Questo programma stampa:

Static variable x = 5
Static variable x = 6
Static variable x = 7
Local variable y = 8
Local variable y = 8
Local variable y = 8

Gli inizializzatori nelle variabili locali statiche sono thread-safe e protetti da eccezioni durante l'inizializzazione. Se si verifica un'eccezione durante un inizializzatore locale statico, il valore predefinito sarà locale statico e non verrà inizializzato. Inizializzatore locale statico

Module Test
    Sub F()
        Static x As Integer = 5
    End Sub
End Module

equivale 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

Le variabili locali, le costanti locali e le variabili statiche hanno come ambito il blocco di istruzioni in cui sono dichiarate. Le variabili statiche sono speciali in quanto i nomi possono essere usati una sola volta nell'intero metodo. Ad esempio, non è valido specificare due dichiarazioni di variabili statiche con lo stesso nome anche se si trovano in blocchi diversi.

Dichiarazioni locali implicite

Oltre alle istruzioni di dichiarazione locale, anche le variabili locali possono essere dichiarate in modo implicito tramite l'uso. Espressione di nome semplice che usa un nome che non viene risolto in un altro modo dichiara una variabile locale in base a tale nome. Per esempio:

Option Explicit Off

Module Test
    Sub Main()
        x = 10
        y = 20
        Console.WriteLine(x + y)
    End Sub
End Module

La dichiarazione locale implicita si verifica solo nei contesti di espressione che possono accettare un'espressione classificata come variabile. L'eccezione a questa regola è che una variabile locale potrebbe non essere dichiarata in modo implicito quando è la destinazione di un'espressione di chiamata di funzione, un'espressione di indicizzazione o un'espressione di accesso ai membri.

Le variabili locali implicite vengono considerate come se siano dichiarate all'inizio del metodo contenitore. Di conseguenza, vengono sempre inclusi nell'ambito dell'intero corpo del metodo, anche se dichiarati all'interno di un'espressione lambda. Ad esempio, il codice seguente:

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

stamperà il valore 10. Le variabili locali implicite vengono tipizzate come Object se nessun carattere di tipo fosse associato al nome della variabile; in caso contrario, il tipo della variabile è il tipo del carattere di tipo. L'inferenza del tipo di variabile locale non viene usata per le variabili locali implicite.

Se la dichiarazione locale esplicita viene specificata dall'ambiente di compilazione o da Option Explicit, tutte le variabili locali devono essere dichiarate in modo esplicito e la dichiarazione di variabile implicita non è consentita.

Istruzione With

Un'istruzione With consente più riferimenti ai membri di un'espressione senza specificare più volte l'espressione.

WithStatement
    : 'With' Expression StatementTerminator
      Block?
      'End' 'With' StatementTerminator
    ;

L'espressione deve essere classificata come valore e viene valutata una volta, al momento dell'ingresso nel blocco. All'interno del With blocco di istruzioni, un'espressione di accesso ai membri o un'espressione di accesso al dizionario a partire da un punto o un punto esclamativo viene valutata come se l'espressione With lo precedesse. Per esempio:

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

Non è valido per creare un ramo in un With blocco di istruzioni dall'esterno del blocco.

Istruzione SyncLock

Un'istruzione SyncLock consente la sincronizzazione delle istruzioni in un'espressione, che garantisce che più thread di esecuzione non eseseguono contemporaneamente le stesse istruzioni.

SyncLockStatement
    : 'SyncLock' Expression StatementTerminator
      Block?
      'End' 'SyncLock' StatementTerminator
    ;

L'espressione deve essere classificata come valore e viene valutata una volta, al momento dell'immissione nel blocco. Quando si immette il SyncLock blocco, il Shared metodo System.Threading.Monitor.Enter viene chiamato sull'espressione specificata, che blocca fino a quando il thread di esecuzione non ha un blocco esclusivo sull'oggetto restituito dall'espressione. Il tipo dell'espressione in un'istruzione SyncLock deve essere un tipo riferimento. Per esempio:

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

Nell'esempio precedente viene eseguita la sincronizzazione nell'istanza specifica della classe Test per assicurarsi che non più di un thread di esecuzione possa aggiungere o sottrarre dalla variabile count alla volta per una determinata istanza.

Il SyncLock blocco è contenuto in modo implicito da un'istruzione Try il cui Finally blocco chiama il Shared metodo System.Threading.Monitor.Exit sull'espressione. In questo modo il blocco viene liberato anche quando viene generata un'eccezione. Di conseguenza, non è valido per creare un ramo in un SyncLock blocco dall'esterno del blocco e un SyncLock blocco viene considerato come una singola istruzione ai fini di Resume e Resume Next. L'esempio precedente è equivalente al codice seguente:

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

Istruzioni evento

Le RaiseEventistruzioni , AddHandlere RemoveHandler generano eventi e gestiscono eventi in modo dinamico.

EventStatement
    : RaiseEventStatement
    | AddHandlerStatement
    | RemoveHandlerStatement
    ;

Istruzione RaiseEvent

Un'istruzione RaiseEvent notifica ai gestori eventi che si è verificato un determinato evento.

RaiseEventStatement
    : 'RaiseEvent' IdentifierOrKeyword
      ( OpenParenthesis ArgumentList? CloseParenthesis )? StatementTerminator
    ;

L'espressione di nome semplice in un'istruzione RaiseEvent viene interpretata come una ricerca membro in Me. Pertanto, RaiseEvent x viene interpretato come se fosse RaiseEvent Me.x. Il risultato dell'espressione deve essere classificato come accesso agli eventi per un evento definito nella classe stessa; gli eventi definiti sui tipi di base non possono essere utilizzati in un'istruzione RaiseEvent .

L'istruzione RaiseEvent viene elaborata come chiamata al Invoke metodo del delegato dell'evento, utilizzando i parametri forniti, se presenti. Se il valore del delegato è Nothing, non viene generata alcuna eccezione. Se non sono presenti argomenti, è possibile omettere le parentesi. Per esempio:

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 classe Raiser precedente equivale 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

Istruzioni AddHandler e RemoveHandler

Anche se la maggior parte dei gestori eventi viene automaticamente collegato tramite WithEvents variabili, potrebbe essere necessario aggiungere e rimuovere dinamicamente i gestori eventi in fase di esecuzione. AddHandler e RemoveHandler le istruzioni e eseguono questa operazione.

AddHandlerStatement
    : 'AddHandler' Expression Comma Expression StatementTerminator
    ;

RemoveHandlerStatement
    : 'RemoveHandler' Expression Comma Expression StatementTerminator
    ;

Ogni istruzione accetta due argomenti: il primo argomento deve essere un'espressione classificata come accesso agli eventi e il secondo argomento deve essere un'espressione classificata come valore. Il tipo del secondo argomento deve essere il tipo delegato associato all'accesso all'evento. Per esempio:

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

Dato un eventoE,, l'istruzione chiama il metodo o remove_E pertinente add_E nell'istanza per aggiungere o rimuovere il delegato come gestore per l'evento. Di conseguenza, il codice precedente è 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

Istruzioni di assegnazione

Un'istruzione di assegnazione assegna il valore di un'espressione a una variabile. Esistono diversi tipi di assegnazione.

AssignmentStatement
    : RegularAssignmentStatement
    | CompoundAssignmentStatement
    | MidAssignmentStatement
    ;

Istruzioni di assegnazione regolari

Un'istruzione di assegnazione semplice archivia il risultato di un'espressione in una variabile.

RegularAssignmentStatement
    : Expression Equals Expression StatementTerminator
    ;

L'espressione a sinistra dell'operatore di assegnazione deve essere classificata come variabile o accesso alle proprietà, mentre l'espressione a destra dell'operatore di assegnazione deve essere classificata come valore. Il tipo dell'espressione deve essere convertibile in modo implicito nel tipo della variabile o dell'accesso alle proprietà.

Se la variabile assegnata in è un elemento matrice di un tipo riferimento, verrà eseguito un controllo di runtime per assicurarsi che l'espressione sia compatibile con il tipo di elemento matrice. Nell'esempio seguente, l'ultima assegnazione genera un'eccezione System.ArrayTypeMismatchException perché un'istanza di ArrayList non può essere archiviata in un elemento di una String matrice.

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.

Se l'espressione a sinistra dell'operatore di assegnazione viene classificata come variabile, l'istruzione di assegnazione archivia il valore nella variabile. Se l'espressione viene classificata come accesso alle proprietà, l'istruzione di assegnazione trasforma l'accesso alla proprietà in una chiamata della funzione di Set accesso della proprietà con il valore sostituito dal parametro value. Per esempio:

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

Se la destinazione della variabile o dell'accesso alle proprietà viene tipizzata come tipo valore ma non classificata come variabile, si verifica un errore in fase di compilazione. Per esempio:

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

Si noti che la semantica dell'assegnazione dipende dal tipo della variabile o della proprietà a cui viene assegnata. Se la variabile a cui viene assegnata è un tipo valore, l'assegnazione copia il valore dell'espressione nella variabile . Se la variabile a cui viene assegnata è un tipo riferimento, l'assegnazione copia il riferimento, non il valore stesso, nella variabile . Se il tipo della variabile è Object, la semantica di assegnazione viene determinata dal fatto che il tipo del valore sia un tipo valore o un tipo riferimento in fase di esecuzione.

Nota. Per i tipi intrinseci, Integer ad esempio e Date, la semantica di assegnazione di riferimenti e valori è identica perché i tipi non sono modificabili. Di conseguenza, il linguaggio è libero di usare l'assegnazione di riferimento sui tipi intrinseci boxed come ottimizzazione. Dal punto di vista del valore, il risultato è lo stesso.

Poiché il carattere uguale (=) viene usato sia per l'assegnazione che per l'uguaglianza, esiste un'ambiguità tra un'assegnazione semplice e un'istruzione di chiamata in situazioni come x = y.ToString(). In tutti questi casi, l'istruzione di assegnazione ha la precedenza sull'operatore di uguaglianza. Ciò significa che l'espressione di esempio viene interpretata come x = (y.ToString()) anziché (x = y).ToString().

Istruzioni di assegnazione composta

Un'istruzione di assegnazione composta assume il formato V op= E (dove op è un operatore binario valido).

CompoundAssignmentStatement
    : Expression CompoundBinaryOperator LineTerminator? Expression StatementTerminator
    ;

CompoundBinaryOperator
    : '^' '=' | '*' '=' | '/' '=' | '\\' '=' | '+' '=' | '-' '='
    | '&' '=' | '<' '<' '=' | '>' '>' '='
    ;

L'espressione a sinistra dell'operatore di assegnazione deve essere classificata come variabile o accesso alle proprietà, mentre l'espressione a destra dell'operatore di assegnazione deve essere classificata come valore. L'istruzione di assegnazione composta equivale all'istruzione V = V op E con la differenza che la variabile sul lato sinistro dell'operatore di assegnazione composta viene valutata una sola volta. L'esempio seguente illustra questa differenza:

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

L'espressione a(GetIndex()) viene valutata due volte per l'assegnazione semplice, ma solo una volta per l'assegnazione composta, quindi il codice stampa:

Simple assignment
Getting index
Getting index
Compound assignment
Getting index

Istruzione Mid Assignment

Un'istruzione Mid di assegnazione assegna una stringa in un'altra stringa. Il lato sinistro dell'assegnazione ha la stessa sintassi di una chiamata alla funzione Microsoft.VisualBasic.Strings.Mid.

MidAssignmentStatement
    : 'Mid' '$'? OpenParenthesis Expression Comma Expression
      ( Comma Expression )? CloseParenthesis Equals Expression StatementTerminator
    ;

Il primo argomento è la destinazione dell'assegnazione e deve essere classificato come variabile o accesso a proprietà il cui tipo è implicitamente convertibile in e da String. Il secondo parametro è la posizione iniziale basata su 1 che corrisponde a dove deve iniziare l'assegnazione nella stringa di destinazione e deve essere classificata come valore il cui tipo deve essere convertibile in modo implicito in Integer. Il terzo parametro facoltativo è il numero di caratteri del valore a destra da assegnare nella stringa di destinazione e deve essere classificato come valore il cui tipo è implicitamente convertibile in Integer. Il lato destro è la stringa di origine e deve essere classificato come valore il cui tipo è implicitamente convertibile in String. Il lato destro viene troncato al parametro length, se specificato e sostituisce i caratteri nella stringa a sinistra, a partire dalla posizione iniziale. Se la stringa laterale destra contiene meno caratteri del terzo parametro, verranno copiati solo i caratteri della stringa laterale destra.

Nell'esempio seguente viene visualizzato 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 non è una parola riservata.

Istruzioni di chiamata

Un'istruzione di chiamata richiama un metodo preceduto dalla parola chiave Callfacoltativa . L'istruzione di chiamata viene elaborata nello stesso modo dell'espressione di chiamata della funzione, con alcune differenze indicate di seguito. L'espressione di chiamata deve essere classificata come valore o void. Qualsiasi valore risultante dalla valutazione dell'espressione di chiamata viene rimosso.

Se la Call parola chiave viene omessa, l'espressione di chiamata deve iniziare con un identificatore o una parola chiave o con . all'interno di un With blocco. Ad esempio, "Call 1.ToString()" è un'istruzione valida, ma "1.ToString()" non è . Si noti che in un contesto di espressione, anche le espressioni di chiamata non devono iniziare con un identificatore. Ad esempio, "Dim x = 1.ToString()" è un'istruzione valida).

Esiste un'altra differenza tra le istruzioni di chiamata e le espressioni di chiamata: se un'istruzione di chiamata include un elenco di argomenti, questa viene sempre considerata come l'elenco di argomenti della chiamata. L'esempio seguente illustra la differenza:

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
    ;

Istruzioni condizionali

Le istruzioni condizionali consentono l'esecuzione condizionale di istruzioni basate su espressioni valutate in fase di esecuzione.

ConditionalStatement
    : IfStatement
    | SelectStatement
    ;

Se... Allora... Istruzioni Else

Un'istruzione If...Then...Else è l'istruzione condizionale di base.

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'   
   ;

Ogni espressione in un'istruzione If...Then...Else deve essere un'espressione booleana, in base alle espressioni booleane della sezione. Nota: non è necessario che l'espressione abbia un tipo booleano. Se l'espressione nell'istruzione If è true, vengono eseguite le istruzioni racchiuse dal If blocco. Se l'espressione è false, ognuna delle ElseIf espressioni viene valutata. Se una delle ElseIf espressioni restituisce true, viene eseguito il blocco corrispondente. Se nessuna espressione restituisce true ed è presente un Else blocco, il Else blocco viene eseguito. Al termine dell'esecuzione di un blocco, l'esecuzione passa alla fine dell'istruzione If...Then...Else .

La versione della riga dell'istruzione If include un singolo set di istruzioni da eseguire se l'espressione è True e un set facoltativo di istruzioni da eseguire se l'espressione If è False. Per esempio:

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 versione della riga dell'istruzione If associa meno strettamente ":" e le relative Else associazioni al precedente If lessicalmente più vicino consentito dalla sintassi. Ad esempio, le due versioni seguenti sono equivalenti:

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

Tutte le istruzioni diverse dalle istruzioni di dichiarazione label sono consentite all'interno di un'istruzione line If , incluse le istruzioni block. Tuttavia, potrebbero non usare LineTerminators come StatementTerminators tranne che all'interno di espressioni lambda su più righe. Per esempio:

' 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()

Istruzioni Select Case

Un'istruzione Select Case esegue istruzioni basate sul valore di un'espressione.

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?
    ;

L'espressione deve essere classificata come valore. Quando viene eseguita un'istruzione Select Case , l'espressione Select viene valutata per prima e le Case istruzioni vengono quindi valutate in ordine di dichiarazione testuale. La prima Case istruzione che restituisce True ha il relativo blocco eseguito. Se non viene restituita True alcuna Case istruzione e viene eseguita un'istruzione Case Else , tale blocco viene eseguito. Al termine dell'esecuzione di un blocco, l'esecuzione passa alla fine dell'istruzione Select .

L'esecuzione di un Case blocco non è consentita per passare alla sezione switch successiva. Ciò impedisce una classe comune di bug che si verificano in altri linguaggi quando un'istruzione Case di terminazione viene accidentalmente omessa. Il seguente esempio illustra questo comportamento.

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

Il codice stampa:

x = 10

Anche se Case 10 e Case 20 - 10 si seleziona per lo stesso valore, Case 10 viene eseguito perché precede in modo testuale Case 20 - 10 . Quando viene raggiunto il successivo Case , l'esecuzione continua dopo l'istruzione Select .

Una Case clausola può assumere due forme. Una maschera è una parola chiave facoltativa Is , un operatore di confronto e un'espressione. L'espressione viene convertita nel tipo dell'espressione. Se l'espressione Select non è convertibile in modo implicito nel tipo dell'espressione Select , si verifica un errore in fase di compilazione. Se l'espressione Select è E, l'operatore di confronto è Op e l'espressione Case è E1, il case viene valutato come E OP E1. L'operatore deve essere valido per i tipi delle due espressioni; in caso contrario, si verifica un errore in fase di compilazione.

L'altra forma è un'espressione seguita facoltativamente dalla parola chiave To e da una seconda espressione. Entrambe le espressioni vengono convertite nel tipo dell'espressione Select . Se una delle due espressioni non è convertibile in modo implicito nel tipo dell'espressione Select , si verifica un errore in fase di compilazione. Se l'espressione Select è E, la prima Case espressione è E1e la seconda Case espressione è E2, viene Case valutata come E = E1 (se non è specificato) E2 o (E >= E1) And (E <= E2). Gli operatori devono essere validi per i tipi delle due espressioni; in caso contrario, si verifica un errore in fase di compilazione.

Istruzioni loop

Le istruzioni loop consentono l'esecuzione ripetuta delle istruzioni nel corpo.

LoopStatement
    : WhileStatement
    | DoLoopStatement
    | ForStatement
    | ForEachStatement
    ;

Ogni volta che viene immesso un corpo del ciclo, viene creata una nuova copia di tutte le variabili locali dichiarate nel corpo, inizializzate con i valori precedenti delle variabili. Qualsiasi riferimento a una variabile all'interno del corpo del ciclo userà la copia eseguita più di recente. Questo codice mostra un esempio:

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

Il codice produce l'output:

31    32    33

Quando viene eseguito il corpo del ciclo, usa qualsiasi copia della variabile sia corrente. Ad esempio, l'istruzione Dim y = x fa riferimento alla copia più recente di y e alla copia originale di x. Quando viene creata un'espressione lambda, ricorda quale copia di una variabile era corrente al momento della creazione. Pertanto, ogni espressione lambda usa la stessa copia condivisa di x, ma una copia diversa di y. Alla fine del programma, quando esegue le espressioni lambda, la copia condivisa di x cui fanno riferimento è ora al valore finale 3.

Si noti che se non sono presenti espressioni lambda o LINQ, non è possibile sapere che una copia aggiornata viene eseguita sulla voce del ciclo. In questo caso, le ottimizzazioni del compilatore evitano di eseguire copie. Si noti anche che è illegale in GoTo un ciclo che contiene espressioni lambda o LINQ.

Mentre... Fine e fare... Istruzioni loop

Ciclo While o Do ciclo basato su un'espressione 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'
    ;

Un While ciclo di istruzioni viene eseguito fino a quando l'espressione booleana restituisce true. Un'istruzione Do loop può contenere una condizione più complessa. Un'espressione può essere inserita dopo la Do parola chiave o dopo la Loop parola chiave , ma non dopo entrambe. L'espressione booleana viene valutata in base alle espressioni booleane della sezione. Nota: non è necessario che l'espressione abbia un tipo booleano. È inoltre valido specificare nessuna espressione; in tal caso, il ciclo non verrà mai chiuso. Se l'espressione viene posizionata dopo Do, verrà valutata prima dell'esecuzione del blocco di ciclo in ogni iterazione. Se l'espressione viene posizionata dopo Loop, verrà valutata dopo l'esecuzione del blocco di cicli in ogni iterazione. L'inserimento dell'espressione dopo Loop genererà quindi un altro ciclo rispetto al posizionamento dopo Do. L'esempio seguente illustra questo comportamento:

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

Il codice produce l'output:

Second Loop

Nel caso del primo ciclo, la condizione viene valutata prima dell'esecuzione del ciclo. Nel caso del secondo ciclo, la condizione viene eseguita dopo l'esecuzione del ciclo. L'espressione condizionale deve essere preceduta da una While parola chiave o da una Until parola chiave. Il primo interrompe il ciclo se la condizione restituisce false, quest'ultima quando la condizione restituisce true.

Nota. Until non è una parola riservata.

Per... Istruzioni successive

Cicli For...Next di istruzioni basati su un set di limiti. Un'istruzione For specifica una variabile di controllo del ciclo, un'espressione con limite inferiore, un'espressione con limite superiore e un'espressione valore passaggio facoltativa. La variabile di controllo del ciclo viene specificata tramite un identificatore seguito da una clausola facoltativa As o da un'espressione.

ForStatement
    : 'For' LoopControlVariable Equals Expression 'To' Expression
      ( 'Step' Expression )? StatementTerminator
      Block?
      ( 'Next' NextExpressionList? StatementTerminator )?
    ;

LoopControlVariable
    : Identifier ( IdentifierModifiers 'As' TypeName )?
    | Expression
    ;

NextExpressionList
    : Expression ( Comma Expression )*
    ;

In base alle regole seguenti, la variabile di controllo del ciclo fa riferimento a una nuova variabile locale specifica di questa For...Next istruzione o a una variabile preesistente o a un'espressione.

  • Se la variabile di controllo del ciclo è un identificatore con una As clausola , l'identificatore definisce una nuova variabile locale del tipo specificato nella As clausola , con ambito per l'intero For ciclo.

  • Se la variabile di controllo del ciclo è un identificatore senza una As clausola, l'identificatore viene prima risolto usando le regole di risoluzione dei nomi semplici (vedere Espressioni nome semplice sezione), ad eccezione del fatto che questa occorrenza dell'identificatore non si trova e di per sé causa la creazione di una variabile locale implicita ( dichiarazioni locali implicite sezione).

    • Se la risoluzione ha esito positivo e il risultato viene classificato come variabile, la variabile di controllo del ciclo è quella variabile preesistente.

    • Se la risoluzione ha esito negativo o se la risoluzione riesce e il risultato viene classificato come tipo, allora:

      • se viene usata l'inferenza del tipo di variabile locale, l'identificatore definisce una nuova variabile locale il cui tipo viene dedotto dalle espressioni di associazione e passaggio, con ambito all'intero For ciclo;
      • se l'inferenza del tipo di variabile locale non viene usata ma la dichiarazione locale implicita è , viene creata una variabile locale implicita il cui ambito è l'intero metodo ( Dichiarazioni locali implicite sezione) e la variabile di controllo del ciclo fa riferimento a questa variabile preesistente;
      • se non viene usata alcuna inferenza del tipo di variabile locale né dichiarazioni locali implicite, si tratta di un errore.
    • Se la risoluzione ha esito positivo con un elemento classificato come un tipo o una variabile, si tratta di un errore.

  • Se la variabile di controllo del ciclo è un'espressione, l'espressione deve essere classificata come variabile.

Una variabile di controllo del ciclo non può essere usata da un'altra istruzione di inclusione For...Next . Il tipo della variabile di controllo del ciclo di un'istruzione For determina il tipo di iterazione e deve essere uno dei seguenti:

  • Byte, SByte, , Short, UIntegerInteger, LongULongDecimal, SingleUShortDouble
  • Tipo enumerato
  • Object
  • Tipo T con gli operatori seguenti, dove B è un tipo che può essere usato in un'espressione 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

Le espressioni associate e step devono essere convertibili in modo implicito nel tipo della variabile di controllo del ciclo e devono essere classificate come valori. In fase di compilazione, il tipo della variabile di controllo del ciclo viene dedotto scegliendo il tipo più ampio tra i tipi di espressione limite inferiore, limite superiore e passaggio. Se tra due tipi non è presente una conversione più ampia, si verifica un errore in fase di compilazione.

In fase di esecuzione, se il tipo della variabile di controllo del ciclo è Object, il tipo dell'iterazione viene dedotto come in fase di compilazione, con due eccezioni. In primo luogo, se le espressioni di associazione e di passaggio sono tutti di tipi integrali ma non hanno un tipo più ampio, il tipo più ampio che comprende tutti e tre i tipi verrà dedotto. In secondo luogo, se il tipo della variabile di controllo del ciclo viene dedotto come String, Double verrà invece dedotto. Se, in fase di esecuzione, non è possibile determinare alcun tipo di controllo ciclo o se una qualsiasi delle espressioni non può essere convertita nel tipo di controllo ciclo, si verificherà un oggetto System.InvalidCastException . Dopo aver scelto un tipo di controllo ciclo all'inizio del ciclo, lo stesso tipo verrà usato durante l'iterazione, indipendentemente dalle modifiche apportate al valore nella variabile di controllo del ciclo.

Un'istruzione For deve essere chiusa da un'istruzione corrispondente Next . Un'istruzione Next senza una variabile corrisponde all'istruzione aperta For più interna, mentre un'istruzione Next con una o più variabili di controllo del ciclo, da sinistra a destra, corrisponderà ai For cicli corrispondenti a ogni variabile. Se una variabile corrisponde a un For ciclo che non è il ciclo più annidato in quel punto, viene restituito un errore in fase di compilazione.

All'inizio del ciclo, le tre espressioni vengono valutate in ordine testuale e l'espressione con limite inferiore viene assegnata alla variabile di controllo del ciclo. Se il valore del passaggio viene omesso, il valore letterale viene convertito in modo implicito 1nel tipo della variabile di controllo del ciclo. Le tre espressioni vengono valutate solo all'inizio del ciclo.

All'inizio di ogni ciclo, la variabile di controllo viene confrontata per verificare se è maggiore del punto finale se l'espressione del passaggio è positiva o minore del punto finale se l'espressione del passaggio è negativa. In caso affermativo, il For ciclo termina; in caso contrario, viene eseguito il blocco di ciclo. Se la variabile di controllo del ciclo non è un tipo primitivo, l'operatore di confronto viene determinato dal fatto che l'espressione step >= step - step sia true o false. Nell'istruzione Next il valore del passaggio viene aggiunto alla variabile di controllo e l'esecuzione torna all'inizio del ciclo.

Si noti che una nuova copia della variabile di controllo del ciclo non viene creata in ogni iterazione del blocco di ciclo. In questo senso, l'istruzione For è diversa da For Each (Sezione Per ogni... Istruzioni successive).

Non è valido per creare un ramo in un For ciclo dall'esterno del ciclo.

Per ogni... Istruzioni successive

Un'istruzione For Each...Next esegue cicli basati sugli elementi di un'espressione. Un'istruzione For Each specifica una variabile di controllo del ciclo e un'espressione enumeratore. La variabile di controllo del ciclo viene specificata tramite un identificatore seguito da una clausola facoltativa As o da un'espressione.

ForEachStatement
    : 'For' 'Each' LoopControlVariable 'In' LineTerminator? Expression StatementTerminator
      Block?
      ( 'Next' NextExpressionList? StatementTerminator )?
    ;

Seguendo le stesse regole delle For...Next istruzioni (Sezione For... Istruzioni successive), la variabile di controllo del ciclo fa riferimento a una nuova variabile locale specifica per questo For Each... Istruzione successiva o a una variabile preesistente o a un'espressione.

L'espressione enumeratore deve essere classificata come valore e il relativo tipo deve essere un tipo di raccolta o Object. Se il tipo dell'espressione enumeratore è Object, tutte le elaborazioni vengono posticipate fino al runtime. In caso contrario, deve esistere una conversione dal tipo di elemento della raccolta al tipo della variabile di controllo ciclo.

La variabile di controllo del ciclo non può essere usata da un'altra istruzione di inclusione For Each . Un'istruzione For Each deve essere chiusa da un'istruzione corrispondente Next . Un'istruzione Next senza una variabile di controllo ciclo corrisponde all'oggetto più interno aperto For Each. Un'istruzione Next con una o più variabili di controllo del ciclo, da sinistra a destra, corrisponderà ai For Each cicli con la stessa variabile di controllo del ciclo. Se una variabile corrisponde a un For Each ciclo che non è il ciclo più annidato in quel punto, si verifica un errore in fase di compilazione.

Un tipo è detto essere un tipo Cdi raccolta se uno di:

  • Tutte le condizioni seguenti sono vere:

    • C contiene un'istanza accessibile, un metodo condiviso o di estensione con la firma GetEnumerator() che restituisce un tipo E.
    • E contiene un'istanza accessibile, un metodo condiviso o di estensione con la firma MoveNext() e il tipo Booleanrestituito .
    • E contiene un'istanza accessibile o una proprietà condivisa denominata Current con un getter. Il tipo di questa proprietà è il tipo di elemento del tipo di raccolta.
  • Implementa l'interfaccia System.Collections.Generic.IEnumerable(Of T), nel qual caso il tipo di elemento della raccolta viene considerato .T

  • Implementa l'interfaccia System.Collections.IEnumerable, nel qual caso il tipo di elemento della raccolta viene considerato .Object

Di seguito è riportato un esempio di una classe che può essere enumerata:

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

Prima dell'inizio del ciclo, viene valutata l'espressione dell'enumeratore. Se il tipo dell'espressione non soddisfa il criterio di progettazione, viene eseguito il cast dell'espressione su System.Collections.IEnumerable o System.Collections.Generic.IEnumerable(Of T). Se il tipo di espressione implementa l'interfaccia generica, l'interfaccia generica è preferibile in fase di compilazione, ma l'interfaccia non generica è preferibile in fase di esecuzione. Se il tipo di espressione implementa più volte l'interfaccia generica, l'istruzione viene considerata ambigua e si verifica un errore in fase di compilazione.

Nota. L'interfaccia non generica è preferibile nel caso con associazione tardiva, perché la selezione dell'interfaccia generica significa che tutte le chiamate ai metodi di interfaccia comportano parametri di tipo. Poiché non è possibile conoscere gli argomenti di tipo corrispondenti in fase di esecuzione, tutte queste chiamate devono essere effettuate usando chiamate ad associazione tardiva. Questa operazione sarebbe più lenta rispetto alla chiamata dell'interfaccia non generica perché l'interfaccia non generica potrebbe essere chiamata usando chiamate in fase di compilazione.

GetEnumerator viene chiamato sul valore risultante e il valore restituito della funzione viene archiviato in un oggetto temporaneo. All'inizio di ogni iterazione viene MoveNext quindi chiamato sull'oggetto temporaneo. Se restituisce False, il ciclo termina. In caso contrario, ogni iterazione del ciclo viene eseguita nel modo seguente:

  1. Se la variabile di controllo del ciclo ha identificato una nuova variabile locale (anziché una variabile preesistente), viene creata una nuova copia di questa variabile locale. Per l'iterazione corrente, tutti i riferimenti all'interno del blocco di ciclo faranno riferimento a questa copia.
  2. La Current proprietà viene recuperata, forzata al tipo della variabile di controllo del ciclo (indipendentemente dal fatto che la conversione sia implicita o esplicita) e assegnata alla variabile di controllo del ciclo.
  3. Il blocco di ciclo viene eseguito.

Nota. Tra la versione 10.0 e la 11.0 della lingua è stata apportata una leggera modifica del comportamento. Prima della versione 11.0, non è stata creata una nuova variabile di iterazione per ogni iterazione del ciclo. Questa differenza è osservabile solo se la variabile di iterazione viene acquisita da un'espressione lambda o LINQ che viene richiamata dopo il ciclo:

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()

Fino a Visual Basic 10.0, questo ha generato un avviso in fase di compilazione e stampato "3" tre volte. Ciò è dovuto al fatto che era presente solo una singola variabile "x" condivisa da tutte le iterazioni del ciclo e tutte e tre le espressioni lambda hanno acquisito la stessa "x" e al momento dell'esecuzione delle espressioni lambda ha mantenuto il numero 3. A partire da Visual Basic 11.0, stampa "1, 2, 3". Ciò è dovuto al fatto che ogni lambda acquisisce una variabile diversa "x".

Nota. L'elemento corrente dell'iterazione viene convertito nel tipo della variabile di controllo del ciclo anche se la conversione è esplicita perché non esiste un luogo pratico per introdurre un operatore di conversione nell'istruzione . Questo è diventato particolarmente problematico quando si lavora con il tipo System.Collections.ArrayListobsoleto, perché il tipo di elemento è Object. Questo avrebbe richiesto cast in un gran numero di cicli, qualcosa che abbiamo sentito non era ideale. Ironicamente, i generics hanno consentito la creazione di una raccolta fortemente tipizzata, System.Collections.Generic.List(Of T), che potrebbe aver fatto ripensare questo punto di progettazione, ma per motivi di compatibilità, questo non può essere modificato ora.

Quando viene raggiunta l'istruzione, l'esecuzione Next torna all'inizio del ciclo. Se una variabile viene specificata dopo la Next parola chiave , deve essere uguale alla prima variabile dopo .For Each Si consideri ad esempio il codice seguente:

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

Equivale al codice seguente:

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

Se il tipo E dell'enumeratore implementa System.IDisposable, l'enumeratore viene eliminato all'uscita dal ciclo chiamando il Dispose metodo . In questo modo le risorse contenute nell'enumeratore vengono rilasciate. Se il metodo contenente l'istruzione For Each non usa la gestione degli errori non strutturati, l'istruzione viene sottoposta a wrapping in un'istruzione Try con il Dispose metodo chiamato in Finally per garantire la For Each pulizia.

Nota. Il System.Array tipo è un tipo di raccolta e, poiché tutti i tipi di matrice derivano da System.Array, qualsiasi espressione di tipo matrice è consentita in un'istruzione For Each . Per le matrici unidimensionali, l'istruzione For Each enumera gli elementi della matrice in ordine di indice crescente, a partire dall'indice 0 e terminando con index Length - 1. Per le matrici multidimensionali, gli indici della dimensione più a destra vengono aumentati per primi.

Ad esempio, il codice seguente stampa 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

Non è valido per creare un ramo in un For Each blocco di istruzioni dall'esterno del blocco.

Istruzioni Exception-Handling

Visual Basic supporta la gestione strutturata delle eccezioni e la gestione delle eccezioni non strutturate. In un metodo è possibile usare solo uno stile di gestione delle eccezioni, ma l'istruzione Error può essere usata nella gestione strutturata delle eccezioni. Se un metodo usa entrambi gli stili di gestione delle eccezioni, viene restituito un errore in fase di compilazione.

ErrorHandlingStatement
    : StructuredErrorStatement
    | UnstructuredErrorStatement
    ;

Istruzioni Exception-Handling strutturate

La gestione strutturata delle eccezioni è un metodo di gestione degli errori dichiarando blocchi espliciti all'interno dei quali verranno gestite determinate eccezioni. La gestione strutturata delle eccezioni viene eseguita tramite un'istruzione Try .

StructuredErrorStatement
    : ThrowStatement
    | TryStatement
    ;

TryStatement
    : 'Try' StatementTerminator
      Block?
      CatchStatement*
      FinallyStatement?
      'End' 'Try' StatementTerminator
    ;

Per esempio:

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

Un'istruzione Try è costituita da tre tipi di blocchi: blocchi try, blocchi catch e infine blocchi. Un blocco try è un blocco di istruzioni che contiene le istruzioni da eseguire. Un blocco catch è un blocco di istruzioni che gestisce un'eccezione. Un blocco finally è un blocco di istruzioni che contiene istruzioni da eseguire quando l'istruzione Try viene chiusa, indipendentemente dal fatto che si sia verificata un'eccezione e che sia stata gestita. Un'istruzione Try , che può contenere un solo blocco try e un blocco finally, deve contenere almeno un blocco catch o infine. Non è valido trasferire in modo esplicito l'esecuzione in un blocco try, ad eccezione dell'interno di un blocco catch nella stessa istruzione.

Blocchi finally

Un blocco Finally viene sempre eseguito quando l'esecuzione lascia qualsiasi parte dell'istruzione Try. Non è necessaria alcuna azione esplicita per eseguire il Finally blocco. Quando l'esecuzione lascia l'istruzione Try , il sistema eseguirà automaticamente il blocco e quindi trasferirà l'esecuzione Finally nella destinazione desiderata. Il Finally blocco viene eseguito indipendentemente dal modo in cui l'esecuzione lascia l'istruzione Try : fino alla fine del Try blocco, fino alla fine di un Catch blocco, tramite un'istruzione Exit Try , tramite un'istruzione GoTo o senza gestire un'eccezione generata.

Si noti che l'espressione Await in un metodo asincrono e l'istruzione Yield in un metodo iteratore possono causare la sospensione del flusso di controllo nell'istanza del metodo asincrono o iteratore e riprendere in un'altra istanza del metodo. Tuttavia, si tratta semplicemente di una sospensione dell'esecuzione e non comporta l'uscita dal rispettivo metodo asincrono o istanza del metodo iteratore e pertanto non causa Finally l'esecuzione dei blocchi.

Non è valido trasferire in modo esplicito l'esecuzione in un Finally blocco, ma non è valida anche per trasferire l'esecuzione da un Finally blocco, ad eccezione di un'eccezione.

FinallyStatement
    : 'Finally' StatementTerminator
      Block?
    ;

Blocchi catch

Se si verifica un'eccezione durante l'elaborazione del Try blocco, ogni Catch istruzione viene esaminata in ordine testuale per determinare se gestisce l'eccezione.

CatchStatement
    : 'Catch' ( Identifier ( 'As' NonArrayTypeName )? )?
	  ( 'When' BooleanExpression )? StatementTerminator
      Block?
    ;

L'identificatore specificato in una Catch clausola rappresenta l'eccezione generata. Se l'identificatore contiene una As clausola, l'identificatore viene considerato dichiarato all'interno Catch dello spazio di dichiarazione locale del blocco. In caso contrario, l'identificatore deve essere una variabile locale (non una variabile statica) definita in un blocco contenitore.

Una Catch clausola senza identificatore intercetta tutte le eccezioni derivate da System.Exception. Una Catch clausola con un identificatore intercetta solo le eccezioni i cui tipi sono uguali o derivati dal tipo dell'identificatore. Il tipo deve essere System.Exceptiono un tipo derivato da System.Exception. Quando viene rilevata un'eccezione che deriva da System.Exception, un riferimento all'oggetto eccezione viene archiviato nell'oggetto restituito dalla funzione Microsoft.VisualBasic.Information.Err.

Una Catch clausola con una When clausola intercetta solo le eccezioni quando l'espressione restituisce True. Il tipo dell'espressione deve essere un'espressione booleana in base alle espressioni booleane della sezione. Una When clausola viene applicata solo dopo il controllo del tipo dell'eccezione e l'espressione può fare riferimento all'identificatore che rappresenta l'eccezione, come illustrato nell'esempio seguente:

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

Questo esempio stampa:

Third handler

Se una Catch clausola gestisce l'eccezione, l'esecuzione viene trasferita al Catch blocco . Alla fine del Catch blocco, l'esecuzione viene trasferita alla prima istruzione dopo l'istruzione Try . L'istruzione Try non gestirà alcuna eccezione generata in un Catch blocco. Se nessuna Catch clausola gestisce l'eccezione, l'esecuzione viene trasferita a una posizione determinata dal sistema.

Non è possibile trasferire in modo esplicito l'esecuzione in un Catch blocco.

I filtri nelle clausole When vengono in genere valutati prima della generazione dell'eccezione. Ad esempio, il codice seguente stampa "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

Tuttavia, i metodi Async e Iterator causano l'esecuzione di tutti i blocchi finally all'interno di essi prima di qualsiasi filtro all'esterno. Ad esempio, se il codice precedente aveva Async Sub Foo(), l'output sarà "Finally, Filter, Catch".

Istruzione Throw

L'istruzione Throw genera un'eccezione, rappresentata da un'istanza di un tipo derivato da System.Exception.

ThrowStatement
    : 'Throw' Expression? StatementTerminator
    ;

Se l'espressione non è classificata come valore o non è un tipo derivato da System.Exception, si verifica un errore in fase di compilazione. Se l'espressione restituisce un valore Null in fase di esecuzione, viene invece generata un'eccezione System.NullReferenceException .

Un'istruzione Throw può omettere l'espressione all'interno di un blocco catch di un'istruzione Try , purché non sia presente alcun blocco finally. In tal caso, l'istruzione rigenera l'eccezione attualmente gestita all'interno del blocco catch. Per esempio:

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

Istruzioni Exception-Handling non strutturate

La gestione delle eccezioni non strutturate è un metodo di gestione degli errori indicando le istruzioni da diramare a quando si verifica un'eccezione. La gestione delle eccezioni non strutturata viene implementata usando tre istruzioni: l'istruzione Error , l'istruzione On Error e l'istruzione Resume .

UnstructuredErrorStatement
    : ErrorStatement
    | OnErrorStatement
    | ResumeStatement
    ;

Per esempio:

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

Quando un metodo usa la gestione delle eccezioni non strutturate, viene stabilito un singolo gestore eccezioni strutturato per l'intero metodo che intercetta tutte le eccezioni. Si noti che nei costruttori questo gestore non si estende sulla chiamata alla chiamata a New all'inizio del costruttore. Il metodo tiene quindi traccia della posizione del gestore eccezioni più recente e dell'eccezione più recente generata. In corrispondenza della voce del metodo, il percorso del gestore eccezioni e l'eccezione sono entrambi impostati su Nothing. Quando viene generata un'eccezione in un metodo che usa la gestione delle eccezioni non strutturata, un riferimento all'oggetto eccezione viene archiviato nell'oggetto restituito dalla funzione Microsoft.VisualBasic.Information.Err.

Le istruzioni di gestione degli errori non strutturate non sono consentite nei metodi iteratori o asincroni.

Istruzione Error

Un'istruzione Error genera un'eccezione System.Exception contenente un numero di eccezione di Visual Basic 6. L'espressione deve essere classificata come valore e il relativo tipo deve essere convertibile in modo implicito in Integer.

ErrorStatement
    : 'Error' Expression StatementTerminator
    ;

Istruzione On Error

Un'istruzione On Error modifica lo stato di gestione delle eccezioni più recente.

OnErrorStatement
    : 'On' 'Error' ErrorClause StatementTerminator
    ;

ErrorClause
    : 'GoTo' '-' '1'
    | 'GoTo' '0'
    | GoToStatement
    | 'Resume' 'Next'
    ;

Può essere usato in uno dei quattro modi seguenti:

  • On Error GoTo -1 reimposta l'eccezione più recente su Nothing.

  • On Error GoTo 0 reimposta la posizione del gestore eccezioni più recente su Nothing.

  • On Error GoTo LabelName stabilisce l'etichetta come posizione del gestore eccezioni più recente. Questa istruzione non può essere utilizzata in un metodo che contiene un'espressione lambda o di query.

  • On Error Resume Next stabilisce il Resume Next comportamento come percorso del gestore eccezioni più recente.

Istruzione Resume

Un'istruzione Resume restituisce l'esecuzione all'istruzione che ha causato l'eccezione più recente.

ResumeStatement
    : 'Resume' ResumeClause? StatementTerminator
    ;

ResumeClause
    : 'Next'
    | LabelName
    ;

Se viene specificato il modificatore, l'esecuzione Next torna all'istruzione che sarebbe stata eseguita dopo l'istruzione che ha causato l'eccezione più recente. Se viene specificato un nome di etichetta, l'esecuzione torna all'etichetta.

Poiché l'istruzione SyncLock contiene un blocco Resume di gestione degli errori strutturato implicito e Resume Next presenta comportamenti speciali per le eccezioni che si verificano nelle SyncLock istruzioni. Resume restituisce l'esecuzione all'inizio dell'istruzione SyncLock , mentre Resume Next restituisce l'esecuzione all'istruzione successiva dopo l'istruzione SyncLock . Si consideri ad esempio il codice seguente:

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

Stampa il risultato seguente.

Before exception
Before exception
After SyncLock

La prima volta tramite l'istruzione restituisce Resume l'esecuzione SyncLock all'inizio dell'istruzione SyncLock . La seconda volta tramite l'istruzione restituisce l'esecuzione SyncLock alla fine dell'istruzioneSyncLock. Resume Next Resume e Resume Next non sono consentiti all'interno di un'istruzione SyncLock .

In tutti i casi, quando viene eseguita un'istruzione Resume , l'eccezione più recente viene impostata su Nothing. Se un'istruzione Resume viene eseguita senza eccezione più recente, l'istruzione genera un'eccezione System.Exception contenente il numero 20 di errore di Visual Basic (Resume senza errore).

Istruzioni branch

Le istruzioni branch modificano il flusso di esecuzione in un metodo. Esistono sei istruzioni di ramo:

  1. Un'istruzione GoTo causa il trasferimento dell'esecuzione all'etichetta specificata nel metodo . Non è consentito in GoTo un Tryblocco , Using, SyncLock, With, For o For Each in un blocco di ciclo se una variabile locale di tale blocco viene acquisita in un'espressione lambda o LINQ.
  2. Un'istruzione Exit trasferisce l'esecuzione all'istruzione successiva dopo la fine dell'istruzione block che contiene immediatamente il tipo specificato. Se il blocco è il blocco del metodo, il flusso di controllo esce dal metodo come descritto in Flusso di controllo della sezione. Se l'istruzione Exit non è contenuta nel tipo di blocco specificato nell'istruzione , si verifica un errore in fase di compilazione.
  3. Un'istruzione Continue trasferisce l'esecuzione alla fine dell'istruzione ciclo block immediatamente contenente il tipo specificato. Se l'istruzione Continue non è contenuta nel tipo di blocco specificato nell'istruzione , si verifica un errore in fase di compilazione.
  4. Un'istruzione Stop fa sì che si verifichi un'eccezione del debugger.
  5. Un'istruzione End termina il programma. I finalizzatori vengono eseguiti prima dell'arresto, ma i blocchi finally di qualsiasi istruzione attualmente in esecuzione Try non vengono eseguiti. Questa istruzione non può essere usata nei programmi che non sono eseguibili(ad esempio, DLL).
  6. Un'istruzione Return senza espressione equivale a un'istruzione Exit Sub o Exit Function . Un'istruzione Return con un'espressione è consentita solo in un metodo regolare che è una funzione o in un metodo asincrono che è una funzione con tipo Task(Of T) restituito per alcuni T. L'espressione deve essere classificata come valore convertibile in modo implicito nella variabile restituita dalla funzione (nel caso di metodi regolari) o alla variabile restituita dall'attività (nel caso di metodi asincroni). Il comportamento consiste nel valutare l'espressione, quindi archiviarla nella variabile restituita, quindi eseguire un'istruzione implicita Exit 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
    ;

Istruzioni Array-Handling

Due istruzioni semplificano l'uso di matrici: ReDim istruzioni e Erase istruzioni.

ArrayHandlingStatement
    : RedimStatement
    | EraseStatement
    ;

Istruzione ReDim

Un'istruzione ReDim crea un'istanza di nuove matrici.

RedimStatement
    : 'ReDim' 'Preserve'? RedimClauses StatementTerminator
    ;

RedimClauses
    : RedimClause ( Comma RedimClause )*
    ;

RedimClause
    : Expression ArraySizeInitializationModifier
    ;

Ogni clausola nell'istruzione deve essere classificata come variabile o accesso a proprietà il cui tipo è un tipo di matrice o Objecte deve essere seguito da un elenco di limiti di matrice. Il numero dei limiti deve essere coerente con il tipo della variabile; è consentito qualsiasi numero di limiti per Object. In fase di esecuzione viene creata un'istanza di una matrice per ogni espressione da sinistra a destra con i limiti specificati e quindi assegnata alla variabile o alla proprietà . Se il tipo di variabile è Object, il numero di dimensioni è il numero di dimensioni specificate e il tipo di elemento della matrice è Object. Se il numero specificato di dimensioni non è compatibile con la variabile o la proprietà in fase di esecuzione si verifica un errore in fase di compilazione. Per esempio:

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

Se si specifica la Preserve parola chiave , le espressioni devono anche essere classificabili come valore e le nuove dimensioni per ogni dimensione, ad eccezione di quella più a destra, devono essere uguali alle dimensioni della matrice esistente. I valori nella matrice esistente vengono copiati nella nuova matrice: se la nuova matrice è più piccola, i valori esistenti vengono eliminati; se la nuova matrice è più grande, gli elementi aggiuntivi verranno inizializzati sul valore predefinito del tipo di elemento della matrice. Si consideri ad esempio il codice seguente:

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

Stampa il risultato seguente:

3, 0

Se il riferimento alla matrice esistente è un valore Null in fase di esecuzione, non viene specificato alcun errore. Oltre alla dimensione più a destra, se la dimensione di una dimensione cambia, verrà generata un'eccezione System.ArrayTypeMismatchException .

Nota. Preserve non è una parola riservata.

Istruzione Erase

Un'istruzione Erase imposta ognuna delle variabili di matrice o delle proprietà specificate nell'istruzione su Nothing. Ogni espressione nell'istruzione deve essere classificata come variabile o accesso alle proprietà il cui tipo è un tipo di matrice o Object. Per esempio:

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 )*
    ;

Istruzione Using

Le istanze di tipi vengono rilasciate automaticamente dal Garbage Collector quando viene eseguita una raccolta e non vengono trovati riferimenti in tempo reale all'istanza. Se un tipo contiene una risorsa particolarmente preziosa e scarsa (ad esempio connessioni di database o handle di file), potrebbe non essere consigliabile attendere fino alla successiva Garbage Collection per pulire una particolare istanza del tipo che non è più in uso. Per fornire un modo leggero per rilasciare le risorse prima di una raccolta, un tipo può implementare l'interfaccia System.IDisposable . Un tipo che lo fa espone un Dispose metodo che può essere chiamato per forzare il rilascio immediato di risorse preziose, ad esempio:

Module Test
    Sub Main()
        Dim x As DBConnection = New DBConnection("...")

        ' Do some work
        ...

        x.Dispose()        ' Free the connection
    End Sub
End Module

L'istruzione Using automatizza il processo di acquisizione di una risorsa, l'esecuzione di un set di istruzioni e l'eliminazione della risorsa. L'istruzione può assumere due forme: in una, la risorsa è una variabile locale dichiarata come parte dell'istruzione e considerata come un'istruzione di dichiarazione di variabile locale regolare; nell'altra, la risorsa è il risultato di un'espressione.

UsingStatement
    : 'Using' UsingResources StatementTerminator
      Block?
      'End' 'Using' StatementTerminator
    ;

UsingResources
    : VariableDeclarators
    | Expression
    ;

Se la risorsa è un'istruzione di dichiarazione di variabile locale, il tipo della dichiarazione di variabile locale deve essere un tipo che può essere convertito in modo implicito in System.IDisposable. Le variabili locali dichiarate sono di sola lettura, con ambito nel blocco di Using istruzioni e devono includere un inizializzatore. Se la risorsa è il risultato di un'espressione, l'espressione deve essere classificata come valore e deve essere di un tipo che può essere convertito in modo implicito in System.IDisposable. L'espressione viene valutata una sola volta, all'inizio dell'istruzione .

Il Using blocco è contenuto in modo implicito da un'istruzione Try il cui blocco finally chiama il metodo IDisposable.Dispose sulla risorsa. In questo modo la risorsa viene eliminata anche quando viene generata un'eccezione. Di conseguenza, non è valido per creare un ramo in un Using blocco dall'esterno del blocco e un Using blocco viene considerato come una singola istruzione ai fini di Resume e Resume Next. Se la risorsa è Nothing, non viene eseguita alcuna chiamata a Dispose . Di conseguenza, l'esempio:

Using f As C = New C()
    ...
End Using

Equivale a:

Dim f As C = New C()
Try
    ...
Finally
    If f IsNot Nothing Then
        f.Dispose()
    End If
End Try

Un'istruzione Using con un'istruzione di dichiarazione di variabile locale può acquisire più risorse alla volta, che equivale alle istruzioni annidate Using . Ad esempio, un'istruzione Using del formato:

Using r1 As R = New R(), r2 As R = New R()
    r1.F()
    r2.F()
End Using

Equivale a:

Using r1 As R = New R()
    Using r2 As R = New R()
        r1.F()
        r2.F()
    End Using
End Using

Istruzione Await

Un'istruzione await ha la stessa sintassi di un'espressione dell'operatore await (Section Await Operator), è consentita solo nei metodi che consentono anche espressioni await e ha lo stesso comportamento di un'espressione dell'operatore await.

Tuttavia, può essere classificato come valore o void. Qualsiasi valore risultante dalla valutazione dell'espressione dell'operatore await viene rimosso.

AwaitStatement
    : AwaitOperatorExpression StatementTerminator
    ;

Istruzione Yield

Le istruzioni Yield sono correlate ai metodi iteratori, descritti in Metodi iteratore sezione.

YieldStatement
    : 'Yield' Expression StatementTerminator
    ;

Yield è una parola riservata se il metodo di inclusione immediata o l'espressione lambda in cui viene visualizzato ha un Iterator modificatore e, se Yield viene visualizzato dopo tale Iterator modificatore, non è riservato altrove. È anche non disponibile nelle direttive del preprocessore. L'istruzione yield è consentita solo nel corpo di un metodo o di un'espressione lambda in cui si tratta di una parola riservata. All'interno del metodo di inclusione immediata o lambda, l'istruzione yield non può verificarsi all'interno del corpo di un Catch blocco o Finally né all'interno del corpo di un'istruzione SyncLock .

L'istruzione yield accetta una singola espressione che deve essere classificata come valore e il cui tipo è implicitamente convertibile nel tipo della variabile corrente iteratore ( Metodi Iteratore Section) del relativo metodo iteratore contenitore.

Il flusso di controllo raggiunge sempre un'istruzione Yield solo quando il MoveNext metodo viene richiamato su un oggetto iteratore. Questo avviene perché un'istanza del metodo iteratore esegue le istruzioni solo a causa dei MoveNext metodi o Dispose chiamati su un oggetto iteratore e il Dispose metodo eseguirà solo codice in blocchi, dove FinallyYield non è consentito.

Quando viene eseguita un'istruzione Yield , l'espressione viene valutata e archiviata nella variabile corrente iteratore dell'istanza del metodo iteratore associata all'oggetto iteratore. Il valore True viene restituito al invoker di e il punto di MoveNextcontrollo di questa istanza smette di avanzare fino alla chiamata successiva dell'oggetto MoveNext iteratore.