Freigeben über


Anweisungen – Visual Basic

Anweisungen stellen ausführbaren Code dar.

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

Hinweis: Der Microsoft Visual Basic-Compiler lässt nur Anweisungen zu, die mit einem Schlüsselwort oder einem Bezeichner beginnen. So ist beispielsweise die Aufrufsanweisung "Call (Console).WriteLine" zulässig, aber die Aufrufsanweisung "(Console).WriteLine" ist nicht.

Kontrollfluss

Der Steuerungsfluss ist die Sequenz, in der Anweisungen und Ausdrücke ausgeführt werden. Die Reihenfolge der Ausführung hängt von der jeweiligen Anweisung oder dem jeweiligen Ausdruck ab.

Wenn Sie beispielsweise einen Additionsoperator (Section Addition Operator) auswerten, wird zuerst der linke Operand ausgewertet, dann der rechte Operand und dann der Operator selbst. Blöcke werden ausgeführt ( Abschnittsblöcke und Beschriftungen), indem sie zuerst ihre erste Unterausrichtung ausführen und dann nacheinander durch die Anweisungen des Blocks fortfahren.

Implizit in dieser Reihenfolge ist das Konzept eines Kontrollpunkts, bei dem es sich um den nächsten auszuführenden Vorgang handelt. Wenn eine Methode aufgerufen wird (oder "aufgerufen"), wird angenommen, dass sie eine Instanz der Methode erstellt. Eine Methodeninstanz besteht aus einer eigenen Kopie der Parameter und lokalen Variablen der Methode und ihrem eigenen Kontrollpunkt.

Reguläre Methoden

Nachfolgend sehen Sie ein Beispiel für eine reguläre Methode.

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

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

Wenn eine reguläre Methode aufgerufen wird,

  1. Zuerst wird eine Instanz der Methode speziell für diesen Aufruf erstellt. Diese Instanz enthält eine Kopie aller Parameter und lokalen Variablen der Methode.
  2. Dann werden alle Parameter für die angegebenen Werte initialisiert, und alle lokalen Variablen werden auf die Standardwerte ihrer Typen initialisiert.
  3. Im Fall einer Function, eine implizite lokale Variable wird auch als Funktionsrückgabevariable initialisiert, deren Name der Funktion name ist, deren Typ der Rückgabetyp der Funktion und deren Anfangswert der Standardwert des Typs ist.
  4. Der Kontrollpunkt der Methodeninstanz wird dann an der ersten Anweisung des Methodentexts festgelegt, und der Methodentext beginnt sofort mit der Ausführung von dort ( Abschnittsblöcke und Bezeichnungen).

Wenn der Steuerungsfluss den Methodentext normal verlässt – durch Erreichen des End Function Endes oder End Sub durch eine explizite Return Oder Exit Anweisung – wird der Kontrollfluss an den Aufrufer der Methodeninstanz zurückgegeben. Wenn eine Funktionsrückgabevariable vorhanden ist, ist das Ergebnis des Aufrufs der Endwert dieser Variablen.

Wenn der Steuerfluss den Methodentext über eine unbehandelte Ausnahme verlässt, wird diese Ausnahme an den Aufrufer weitergegeben.

Nachdem der Steuerungsfluss beendet wurde, gibt es keine Liveverweise mehr auf die Methodeninstanz. Wenn die Methodeninstanz die einzigen Verweise auf die Kopie lokaler Variablen oder Parameter enthält, werden sie möglicherweise garbage collection.

Iteratormethoden

Iteratormethoden werden als bequeme Möglichkeit zum Generieren einer Sequenz verwendet, die von der For Each Anweisung verwendet werden kann. Iteratormethoden verwenden die Yield Anweisung (Section Yield Statement), um Elemente der Sequenz bereitzustellen. (Eine Iteratormethode ohne Yield Anweisungen erzeugt eine leere Sequenz). Hier ist ein Beispiel für eine Iteratormethode:

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

Wenn eine Iteratormethode aufgerufen wird, deren Rückgabetyp lautet IEnumerator(Of T),

  1. Zuerst wird eine Instanz der Iteratormethode speziell für diesen Aufruf erstellt. Diese Instanz enthält eine Kopie aller Parameter und lokalen Variablen der Methode.
  2. Dann werden alle Parameter für die angegebenen Werte initialisiert, und alle lokalen Variablen werden auf die Standardwerte ihrer Typen initialisiert.
  3. Eine implizite lokale Variable wird auch als iterator aktuelle Variable initialisiert, deren Typ und T dessen Anfangswert der Standardwert des Typs ist.
  4. Der Kontrollpunkt der Methodeninstanz wird dann an der ersten Anweisung des Methodentexts festgelegt.
  5. Anschließend wird ein Iteratorobjekt erstellt, das dieser Methodeninstanz zugeordnet ist. Das Iteratorobjekt implementiert den deklarierten Rückgabetyp und weist wie unten beschrieben ein Verhalten auf.
  6. Der Steuerungsfluss wird dann sofort im Aufrufer fortgesetzt, und das Ergebnis des Aufrufs ist das Iteratorobjekt. Beachten Sie, dass diese Übertragung durchgeführt wird, ohne die Iteratormethodeninstanz zu beenden, und führt nicht dazu, dass schließlich Handler ausgeführt werden. Auf die Methodeninstanz wird weiterhin vom Iteratorobjekt verwiesen, und es wird keine Garbage Collection ausgeführt, solange ein Liveverweis auf das Iteratorobjekt vorhanden ist.

Wenn auf die Eigenschaft des Iteratorobjekts Current zugegriffen wird, wird die aktuelle Variable des Aufrufs zurückgegeben.

Wenn die Methode des Iteratorobjekts MoveNext aufgerufen wird, erstellt der Aufruf keine neue Methodeninstanz. Stattdessen wird die vorhandene Methodeninstanz verwendet (und ihr Kontrollpunkt und lokale Variablen und Parameter) – die Instanz, die beim ersten Aufrufen der Iteratormethode erstellt wurde. Der Steuerungsfluss setzt die Ausführung am Kontrollpunkt dieser Methodeninstanz fort und durchläuft den Textkörper der Iteratormethode normal.

Wenn die Methode des Iteratorobjekts Dispose aufgerufen wird, wird erneut die vorhandene Methodeninstanz verwendet. Der Steuerungsfluss wird am Kontrollpunkt dieser Methodeninstanz fortgesetzt, verhält sich dann aber sofort so, als ob eine Exit Function Anweisung der nächste Vorgang wäre.

Die obigen Beschreibungen des Verhaltens für den Aufruf oder MoveNextDispose für ein Iteratorobjekt gelten nur, wenn alle vorherigen Aufrufe des MoveNext iterator-Objekts Dispose bereits an ihre Aufrufer zurückgegeben wurden. Andernfalls ist das Verhalten nicht definiert.

Wenn der Steuerungsfluss den Iteratormethodentext normal verlässt - durch Erreichen des End Function zugehörigen Endes oder durch eine explizite Return oder Exit Anweisung -- muss dies im Kontext eines Aufrufs oder MoveNextDispose einer Funktion für ein Iterator-Objekt erfolgen, um die Iterator-Methodeninstanz fortzusetzen, und es wurde die Methodeninstanz verwendet, die erstellt wurde, als die Iteratormethode zum ersten Mal aufgerufen wurde. Der Kontrollpunkt dieser Instanz befindet sich in der End Function Anweisung, und der Steuerungsfluss wird im Aufrufer fortgesetzt. Wenn er von einem Aufruf MoveNext fortgesetzt wurde, wird der Wert False an den Aufrufer zurückgegeben.

Wenn der Steuerungsfluss den Text der Iteratormethode über eine unbehandelte Ausnahme verlässt, wird die Ausnahme an den Aufrufer weitergegeben, der wiederum ein Aufruf von MoveNext oder von .Dispose

Wie für die anderen möglichen Rückgabetypen einer Iteratorfunktion,

  • Wenn eine Iteratormethode aufgerufen wird, deren Rückgabetyp für einige Tbestimmt istIEnumerable(Of T), wird eine Instanz zuerst erstellt – spezifisch für diesen Aufruf der Iteratormethode - aller Parameter in der Methode, und sie werden mit den angegebenen Werten initialisiert. Das Ergebnis des Aufrufs ist ein Objekt, das den Rückgabetyp implementiert. Sollte die Methode dieses Objekts GetEnumerator aufgerufen werden, erstellt es eine Instanz , die für diesen Aufruf GetEnumerator spezifisch ist – aller Parameter und lokalen Variablen in der Methode. Er initialisiert die Parameter für die bereits gespeicherten Werte und wird wie für die oben beschriebene Iteratormethode fortgesetzt.
  • Wenn eine Iteratormethode aufgerufen wird, deren Rückgabetyp die nicht generische Schnittstelle IEnumeratorist, ist das Verhalten genau wie für IEnumerator(Of Object).
  • Wenn eine Iteratormethode aufgerufen wird, deren Rückgabetyp die nicht generische Schnittstelle IEnumerableist, ist das Verhalten genau wie für IEnumerable(Of Object).

Asynchrone Methoden

Asynchrone Methoden sind eine bequeme Möglichkeit, lange ausgeführte Aufgaben auszuführen, ohne z. B. die Benutzeroberfläche einer Anwendung zu blockieren. Async ist kurz für asynchron – das bedeutet, dass der Aufrufer der asynchronen Methode die Ausführung umgehend fortsetzen wird, aber der spätere Abschluss der Instanz der asynchronen Methode kann zu einem späteren Zeitpunkt in der Zukunft auftreten. Standardmäßig werden asynchrone Methoden mit dem Suffix "Async" benannt.

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"

Hinweis: Asynchrone Methoden werden nicht in einem Hintergrundthread ausgeführt. Stattdessen ermöglichen sie es einer Methode, sich selbst über den Await Operator anzusetzen, und planen, dass sie als Reaktion auf ein ereignis wieder aufgenommen werden.

Wenn eine asynchrone Methode aufgerufen wird

  1. Zuerst wird eine Instanz der asynchronen Methode speziell für diesen Aufruf erstellt. Diese Instanz enthält eine Kopie aller Parameter und lokalen Variablen der Methode.
  2. Dann werden alle Parameter für die angegebenen Werte initialisiert, und alle lokalen Variablen werden auf die Standardwerte ihrer Typen initialisiert.
  3. Bei einer asynchronen Methode mit Rückgabetyp Task(Of T) für einige Twird eine implizite lokale Variable auch als Task-Rückgabevariable initialisiert, deren Typ und T dessen Anfangswert der Standardwert ist T.
  4. Wenn es sich bei der asynchronen Methode um einen Function Rückgabetyp Task oder Task(Of T) für einige Thandelt, wird ein Objekt dieses Typs implizit erstellt, das dem aktuellen Aufruf zugeordnet ist. Dies wird als asynchrones Objekt bezeichnet und stellt die zukünftige Arbeit dar, die durch Ausführen der Instanz der asynchronen Methode ausgeführt wird. Wenn das Steuerelement im Aufrufer dieser asynchronen Methodeninstanz fortgesetzt wird, empfängt der Aufrufer dieses asynchrone Objekt als Ergebnis des Aufrufs.
  5. Der Kontrollpunkt der Instanz wird dann an der ersten Anweisung des asynchronen Methodentexts festgelegt und beginnt sofort, den Methodentext von dort aus auszuführen ( Abschnittsblöcke und Bezeichnungen).

Reaktivierungsdelegat und aktueller Anrufer

Wie im Abschnitts-Await-Operator beschrieben, hat die Ausführung eines Await Ausdrucks die Möglichkeit, den Kontrollpunkt der Methodeninstanz anzuhalten, sodass der Kontrollfluss an einer anderen Stelle verschoben wird. Der Steuerungsfluss kann später durch Aufruf eines Reaktivierungsdelegats am Kontrollpunkt derselben Instanz fortgesetzt werden. Beachten Sie, dass dieses Anhalten ohne Beenden der asynchronen Methode erfolgt und nicht dazu führt, dass schließlich Handler ausgeführt werden. Auf die Methodeninstanz wird weiterhin sowohl vom Reaufnahmedelegat als Task auch vom Ergebnis Task(Of T) (sofern vorhanden) verwiesen, und es wird keine Garbage Collection durchgeführt, solange ein Liveverweis auf Stellvertretung oder Ergebnis vorhanden ist.

Es ist hilfreich, sich die Aussage Dim x = Await WorkAsync() ungefähr als syntaktische Kurzform für Folgendes vorzustellen:

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

Im Folgenden wird der aktuelle Aufrufer der Methodeninstanz entweder als ursprünglicher Aufrufer oder als Aufrufer des Resummierungsdelegats definiert, je nachdem, welcher Zeitpunkt neuer ist.

Wenn der Steuerfluss den asynchronen Methodentext verlässt – durch Erreichen des End Sub Endes oder End Function durch eine explizite Return Oder Exit Anweisung oder durch eine unbehandelte Ausnahme – wird der Kontrollpunkt der Instanz auf das Ende der Methode festgelegt. Das Verhalten hängt dann vom Rückgabetyp der asynchronen Methode ab.

  • Im Falle eines Async Function Rückgabetyps Task:

    1. Wenn der Steuerungsfluss durch eine nicht behandelte Ausnahme beendet wird, wird der Status des asynchronen Objekts auf die Ausnahme festgelegt TaskStatus.Faulted und seine Exception.InnerException Eigenschaft wird auf die Ausnahme festgelegt (außer: bestimmte implementierungsdefinierte Ausnahmen, z OperationCanceledException . B. ändern sie in TaskStatus.Canceled). Der Steuerungsfluss wird im aktuellen Aufrufer fortgesetzt.

    2. Andernfalls wird der Status des asynchronen Objekts auf TaskStatus.Completed. Der Steuerungsfluss wird im aktuellen Aufrufer fortgesetzt.

      (Hinweis. Der gesamte Punkt von Task und was asynchrone Methoden interessant macht, ist, dass, wenn eine Aufgabe abgeschlossen wird, dann werden alle Methoden, die darauf warten, ihre Fortsetzungsdelegat ausgeführt, d. h. sie werden entsperrt.)

  • Im Falle eines Rückgabetyps Task(Of T) für einige Async FunctionT: das Verhalten ist wie oben dargestellt, außer dass die Eigenschaft des Result asynchronen Objekts auch auf den endgültigen Wert der Task-Rückgabevariable festgelegt ist.

  • Im Falle einer Async Sub:

    1. Wenn der Steuerungsfluss durch eine unbehandelte Ausnahme beendet wird, wird diese Ausnahme auf eine implementierungsspezifische Weise an die Umgebung weitergegeben. Der Steuerungsfluss wird im aktuellen Aufrufer fortgesetzt.
    2. Andernfalls wird der Steuerungsfluss einfach im aktuellen Aufrufer fortgesetzt.

Async Sub

Es gibt ein microsoftspezifisches Verhalten eines Async Sub.

Nothing Wenn SynchronizationContext.Current sich der Aufruf am Anfang befindet, werden alle unbehandelten Ausnahmen von einem Async-Sub in den Threadpool gepostet.

Wenn SynchronizationContext.Current sich der Aufruf nicht Nothing am Anfang des Aufrufs befindet, wird dieser OperationStarted() Kontext vor dem Start der Methode und OperationCompleted() nach dem Ende aufgerufen. Darüber hinaus werden alle unbehandelten Ausnahmen veröffentlicht, um im Synchronisierungskontext erneut zu ernennen.

Dies bedeutet, dass in Benutzeroberflächenanwendungen für einen Async Sub im UI-Thread aufgerufenen Benutzeroberflächenthread alle Ausnahmen, die sie nicht verarbeiten kann, den UI-Thread erneut bereitstellen.

Mutierbare Strukturen in asynchronen und Iteratormethoden

Mutierbare Strukturen werden im Allgemeinen als schlechte Praxis betrachtet, und sie werden nicht von asynchronen oder Iteratormethoden unterstützt. Insbesondere wird jeder Aufruf einer asynchronen oder iterator-Methode in einer Struktur implizit auf eine Kopie dieser Struktur ausgeführt, die zum Zeitpunkt des Aufrufs kopiert wird. So z. B.

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"

Blöcke und Beschriftungen

Eine Gruppe von ausführbaren Anweisungen wird als Anweisungsblock bezeichnet. Die Ausführung eines Anweisungsblocks beginnt mit der ersten Anweisung im Block. Sobald eine Anweisung ausgeführt wurde, wird die nächste Anweisung in lexikalischer Reihenfolge ausgeführt, es sei denn, eine Anweisung überträgt die Ausführung an anderer Stelle oder eine Ausnahme.

Innerhalb eines Anweisungsblocks ist die Division von Anweisungen in logischen Zeilen mit Ausnahme von Bezeichnungsdeklarationsanweisungen nicht signifikant. Eine Bezeichnung ist ein Bezeichner, der eine bestimmte Position innerhalb des Anweisungsblocks identifiziert, die als Ziel einer Verzweigungsanweisung verwendet werden kann, z GoTo. B. .

Block
    : Statements*
    ;

LabelDeclarationStatement
    : LabelName ':'
    ;

LabelName
    : Identifier
    | IntLiteral
    ;

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

Bezeichnungsdeklarationsanweisungen müssen am Anfang einer logischen Zeile angezeigt werden, und Bezeichnungen können entweder ein Bezeichner oder ein ganzzahliges Literal sein. Da sowohl Bezeichnungsdeklarationsanweisungen als auch Aufrufanweisungen aus einem einzelnen Bezeichner bestehen können, wird ein einzelner Bezeichner am Anfang einer lokalen Zeile immer als Bezeichnungsdeklarationsanweisung betrachtet. Bezeichnungsdeklarationsanweisungen müssen immer durch einen Doppelpunkt gefolgt werden, auch wenn keine Anweisungen in derselben logischen Zeile folgen.

Bezeichnungen verfügen über einen eigenen Deklarationsbereich und beeinträchtigen keine anderen Bezeichner. Das folgende Beispiel ist gültig und verwendet die Namensvariable x sowohl als Parameter als auch als Bezeichnung.

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

Der Bereich einer Bezeichnung ist der Textkörper der Methode, die sie enthält.

Aus Gründen der Lesbarkeit werden Anweisungsproduktionen, die mehrere Unterstatements umfassen, in dieser Spezifikation als einzelne Produktion behandelt, auch wenn sich die Unterangaben jeweils einzeln in einer beschrifteten Zeile befinden können.

Lokale Variablen und Parameter

In den vorherigen Abschnitten wird erläutert, wie und wann Methodeninstanzen erstellt werden, und mit ihnen werden die Kopien der lokalen Variablen und Parameter einer Methode kopiert. Außerdem wird jedes Mal, wenn der Textkörper einer Schleife eingegeben wird, eine neue Kopie aus jeder lokalen Variablen erstellt, die in dieser Schleife wie in Section Loop-Anweisungen beschrieben deklariert wird, und die Methodeninstanz enthält jetzt diese Kopie der lokalen Variablen anstelle der vorherigen Kopie.

Alle Gebietsschemas werden auf den Standardwert ihres Typs initialisiert. Lokale Variablen und Parameter sind immer öffentlich zugänglich. Es ist ein Fehler, auf eine lokale Variable an einer textbezogenen Position zu verweisen, die der Deklaration vorausgeht, wie im folgenden Beispiel veranschaulicht wird:

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

In der F obigen Methode bezieht sich die erste Zuordnung i ausdrücklich nicht auf das feld, das im äußeren Bereich deklariert ist. Stattdessen bezieht es sich auf die lokale Variable, und sie befindet sich im Fehler, da sie der Deklaration der Variablen textual vorausgeht. In der G Methode bezieht sich eine nachfolgende Variablendeklaration auf eine lokale Variable, die in einer früheren Variablendeklaration innerhalb derselben lokalen Variablendeklaration deklariert wurde.

Jeder Block in einer Methode erstellt einen Deklarationsbereich für lokale Variablen. Namen werden in diesen Deklarationsbereich über lokale Variablendeklarationen im Methodentext und über die Parameterliste der Methode eingeführt, die Namen in den Deklarationsbereich des äußersten Blocks einführt. Blöcke lassen das Schattieren von Namen durch Schachtelung nicht zu: Sobald ein Name in einem Block deklariert wurde, wird der Name möglicherweise nicht in geschachtelten Blöcken neu deklariert.

Daher sind die F Methoden und G Methoden im folgenden Beispiel fehlerhaft, da der Name i im äußeren Block deklariert und nicht im inneren Block neu deklariert werden kann. Die H Methoden sind I jedoch gültig, da die beiden iBlöcke in separaten, nicht geschachtelten Blöcken deklariert werden.

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

Wenn die Methode eine Funktion ist, wird implizit eine spezielle lokale Variable im Deklarationsraum des Methodentexts mit demselben Namen wie die Methode deklariert, die den Rückgabewert der Funktion darstellt. Die lokale Variable verfügt über spezielle Namensauflösungssemantik, wenn sie in Ausdrücken verwendet wird. Wenn die lokale Variable in einem Kontext verwendet wird, der einen ausdruck erwartet, der als Methodengruppe klassifiziert ist, z. B. einen Aufrufausdruck, wird der Name in die Funktion und nicht in die lokale Variable aufgelöst. Beispiel:

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

Die Verwendung von Klammern kann mehrdeutige Situationen verursachen (z F(1). B. eine Funktion, bei der F es sich um eine Funktion handelt, deren Rückgabetyp ein eindimensionales Array ist); in allen mehrdeutigen Situationen wird der Name in die Funktion anstelle der lokalen Variablen aufgelöst. Beispiel:

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

Wenn der Steuerfluss den Methodentext verlässt, wird der Wert der lokalen Variablen an den Aufrufausdruck übergeben. Wenn es sich bei der Methode um eine Unterroutine handelt, gibt es keine solche implizite lokale Variable, und das Steuerelement kehrt einfach zum Aufrufausdruck zurück.

Lokale Deklarationsanweisungen

Eine lokale Deklarationsanweisung deklariert eine neue lokale Variable, lokale Konstante oder statische Variable. Lokale Variablen und lokale Konstanten entsprechen Instanzenvariablen und Konstanten für die Methode und werden auf die gleiche Weise deklariert. Statische Variablen ähneln Shared Variablen und werden mithilfe des Static Modifizierers deklariert.

LocalDeclarationStatement
    : LocalModifier VariableDeclarators StatementTerminator
    ;

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

Statische Variablen sind Lokale, die ihren Wert über Aufrufe der Methode hinweg beibehalten. Statische Variablen, die in nicht freigegebenen Methoden deklariert sind, sind pro Instanz: Jede Instanz des Typs, der die Methode enthält, verfügt über eine eigene Kopie der statischen Variablen. Statische Variablen, die innerhalb von Shared Methoden deklariert sind, sind pro Typ vorhanden. Es gibt nur eine Kopie der statischen Variablen für alle Instanzen. Während lokale Variablen bei jedem Eintrag in der Methode auf den Standardwert ihres Typs initialisiert werden, werden statische Variablen nur für den Standardwert des Typs initialisiert, wenn der Typ oder die Typinstanz initialisiert wird. Statische Variablen werden möglicherweise nicht in Strukturen oder generischen Methoden deklariert.

Lokale Variablen, lokale Konstanten und statische Variablen weisen immer öffentliche Barrierefreiheit auf und geben möglicherweise keine Barrierefreiheitsmodifizierer an. Wenn für eine lokale Deklarationsanweisung kein Typ angegeben ist, bestimmen die folgenden Schritte den Typ der lokalen Deklaration:

  1. Wenn die Deklaration ein Typzeichen aufweist, ist der Typ des Typzeichens der Typ der lokalen Deklaration.

  2. Wenn es sich bei der lokalen Deklaration um eine lokale Konstante handelt oder die lokale Deklaration eine lokale Variable mit einem Initialisierungs- und lokalen Variablentyp ist, wird der Typ der lokalen Deklaration vom Typ des Initialisierers abgeleitet. Wenn sich der Initialisierer auf die lokale Deklaration bezieht, tritt ein Kompilierungszeitfehler auf. (Lokale Konstanten sind für Initialisierer erforderlich.)

  3. Wenn keine strenge Semantik verwendet wird, wird der Typ der lokalen Deklarationsanweisung implizit Objectverwendet.

  4. Andernfalls tritt ein Kompilierfehler auf.

Wenn kein Typ für eine lokale Deklarationsanweisung angegeben wird, die über eine Arraygröße oder einen Arraytypmodifizierer verfügt, handelt es sich bei dem Typ der lokalen Deklaration um ein Array mit dem angegebenen Rang, und die vorherigen Schritte werden verwendet, um den Elementtyp des Arrays zu bestimmen. Wenn lokale Variablentypausschlüsse verwendet werden, muss der Typ des Initialisierungsprogramms ein Arraytyp mit demselben Array-Shape (d. h. Arraytypmodifizierer) sein wie die lokale Deklarationsanweisung. Beachten Sie, dass der abgeleitete Elementtyp möglicherweise immer noch ein Arraytyp ist. Beispiel:

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

Wenn kein Typ für eine lokale Deklarationsanweisung angegeben wird, die einen Modifizierer mit nullablem Typ aufweist, ist der Typ der lokalen Deklaration die nullable Version des abgeleiteten Typs oder der abgeleitete Typ selbst, wenn er bereits nullablen Werttyp ist. Wenn der abgeleitete Typ kein Werttyp ist, der nullwertebar gemacht werden kann, tritt ein Kompilierungszeitfehler auf. Wenn sowohl ein modifizierbarer Typ als auch ein Arraygrößen- oder Arraytypmodifizierer in einer lokalen Deklarationsanweisung ohne Typ platziert werden, wird der Modifizierer vom Typ nullabler Typ als auf den Elementtyp des Arrays angewendet, und die vorherigen Schritte werden verwendet, um den Elementtyp zu bestimmen.

Variable Initializer für lokale Deklarationsanweisungen entsprechen zuordnungsanweisungen, die an der Textposition der Deklaration platziert werden. Wenn die Ausführung also über die lokale Deklarationsanweisung verzweigt, wird der Variableninitialisierer nicht ausgeführt. Wenn die lokale Deklarationsanweisung mehrmals ausgeführt wird, wird der Variableninitialisierer gleich oft ausgeführt. Statische Variablen führen ihren Initialisierer nur zum ersten Mal aus. Wenn beim Initialisieren einer statischen Variable eine Ausnahme auftritt, wird die statische Variable mit dem Standardwert des Typs der statischen Variablen initialisiert.

Das folgende Beispiel zeigt die Verwendung von Initialisierern:

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

Dieses Programm druckt:

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

Initialisierer für statische Lokale sind threadsicher und während der Initialisierung vor Ausnahmen geschützt. Wenn eine Ausnahme während eines statischen lokalen Initialisierungsprogramms auftritt, hat die statische lokale Datei ihren Standardwert und wird nicht initialisiert. Ein statischer lokaler Initialisierer

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

entspricht

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

Lokale Variablen, lokale Konstanten und statische Variablen sind auf den Anweisungsblock festgelegt, in dem sie deklariert werden. Statische Variablen sind besonders darauf spezialisiert, dass ihre Namen nur einmal während der gesamten Methode verwendet werden können. Beispielsweise ist es ungültig, zwei statische Variablendeklarationen mit demselben Namen anzugeben, auch wenn sie sich in verschiedenen Blöcken befinden.

Implizite lokale Deklarationen

Zusätzlich zu lokalen Deklarationsanweisungen können lokale Variablen implizit über die Verwendung deklariert werden. Ein einfacher Namensausdruck, der einen Namen verwendet, der nicht in eine andere Datei aufgelöst wird, deklariert eine lokale Variable anhand dieses Namens. Beispiel:

Option Explicit Off

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

Implizite lokale Deklaration tritt nur in Ausdruckskontexten auf, die einen ausdruck akzeptieren können, der als Variable klassifiziert ist. Die Ausnahme dieser Regel besteht darin, dass eine lokale Variable möglicherweise nicht implizit deklariert wird, wenn sie das Ziel eines Funktionsaufrufausdrucks, indizierungsausdrucks oder eines Memberzugriffsausdrucks ist.

Implizite Lokale werden behandelt, als ob sie am Anfang der enthaltenden Methode deklariert werden. Daher sind sie immer auf den gesamten Methodentext festgelegt, auch wenn sie innerhalb eines Lambda-Ausdrucks deklariert sind. Beispiel: Der folgende Code:

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

druckt den Wert 10. Implizite Lokale werden so eingegeben, als Object ob kein Typzeichen an den Variablennamen angefügt wurde. Andernfalls ist der Typ der Variablen der Typ des Typzeichens. Lokale Variablentypinference wird nicht für implizite Locals verwendet.

Wenn die explizite lokale Deklaration von der Kompilierungsumgebung oder von der Option ExplicitKompilierung angegeben wird, müssen alle lokalen Variablen explizit deklariert werden, und die implizite Variablendeklaration ist unzulässig.

With-Anweisung

Eine With Anweisung ermöglicht mehrere Verweise auf die Member eines Ausdrucks, ohne den Ausdruck mehrmals anzugeben.

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

Der Ausdruck muss als Wert klassifiziert und einmal ausgewertet werden, wenn er in den Block eintritt. Innerhalb des Anweisungsblocks With wird ein Elementzugriffsausdruck oder ein Wörterbuchzugriffsausdruck, der mit einem Punkt oder einem Ausrufezeichen beginnt, ausgewertet, als ob ihm der With Ausdruck vorausging. Beispiel:

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

Es ist ungültig, in einen With Anweisungsblock außerhalb des Blocks zu verzweigen.

SyncLock-Anweisung

Eine SyncLock Anweisung ermöglicht die Synchronisierung von Anweisungen für einen Ausdruck, wodurch sichergestellt wird, dass mehrere Ausführungsthreads nicht gleichzeitig dieselben Anweisungen ausführen.

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

Der Ausdruck muss als Wert klassifiziert und einmal ausgewertet werden, wenn er in den Block eintritt. Beim Eingeben des SyncLock Blocks wird die Shared Methode System.Threading.Monitor.Enter für den angegebenen Ausdruck aufgerufen, der blockiert, bis der Ausführungsthread eine exklusive Sperre für das vom Ausdruck zurückgegebene Objekt aufweist. Der Typ des Ausdrucks in einer SyncLock Anweisung muss ein Verweistyp sein. Beispiel:

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

Im obigen Beispiel wird eine Synchronisierung für die bestimmte Instanz der Klasse Test ausgeführt, um sicherzustellen, dass für eine bestimmte Instanz nicht mehr als ein Ausführungsthread die Anzahlvariable addiert oder subtrahiert werden kann.

Der SyncLock Block ist implizit in einer Try Anweisung enthalten, deren Finally Block die Shared Methode System.Threading.Monitor.Exit für den Ausdruck aufruft. Dadurch wird sichergestellt, dass die Sperre auch dann freigegeben wird, wenn eine Ausnahme ausgelöst wird. Daher ist es ungültig, einen Block von außerhalb des Blocks zu SyncLock verzweigen, und ein SyncLock Block wird als einzelne Anweisung für die Zwecke Resume und .Resume Next Das obige Beispiel entspricht dem folgenden Code:

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

Event-Anweisungen

Die RaiseEvent, AddHandlerund Anweisungen lösen Ereignisse aus RemoveHandler und behandeln Ereignisse dynamisch.

EventStatement
    : RaiseEventStatement
    | AddHandlerStatement
    | RemoveHandlerStatement
    ;

RaiseEvent-Anweisung

Eine RaiseEvent Anweisung benachrichtigt Ereignishandler, dass ein bestimmtes Ereignis aufgetreten ist.

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

Der einfache Namensausdruck in einer RaiseEvent Anweisung wird als Member-Nachschlagevorgang Meinterpretiert. Daher wird so interpretiert, RaiseEvent x als wäre es RaiseEvent Me.x. Das Ergebnis des Ausdrucks muss als Ereigniszugriff für ein in der Klasse selbst definiertes Ereignis klassifiziert werden. Ereignisse, die für Basistypen definiert sind, können in einer RaiseEvent Anweisung nicht verwendet werden.

Die RaiseEvent Anweisung wird als Aufruf der Invoke Methode des Stellvertretungs des Ereignisses unter Verwendung der angegebenen Parameter verarbeitet. Wenn der Wert der Stellvertretung lautet Nothing, wird keine Ausnahme ausgelöst. Wenn keine Argumente vorhanden sind, werden die Klammern möglicherweise weggelassen. Beispiel:

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

Die obige Klasse Raiser entspricht folgendem:

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

AddHandler- und RemoveHandler-Anweisungen

Obwohl die meisten Ereignishandler automatisch über WithEvents Variablen eingebunden werden, kann es erforderlich sein, Ereignishandler zur Laufzeit dynamisch hinzuzufügen und zu entfernen. AddHandler und RemoveHandler Anweisungen tun dies.

AddHandlerStatement
    : 'AddHandler' Expression Comma Expression StatementTerminator
    ;

RemoveHandlerStatement
    : 'RemoveHandler' Expression Comma Expression StatementTerminator
    ;

Jede Anweisung akzeptiert zwei Argumente: Das erste Argument muss ein Ausdruck sein, der als Ereigniszugriff klassifiziert wird, und das zweite Argument muss ein Ausdruck sein, der als Wert klassifiziert wird. Der Typ des zweiten Arguments muss der Stellvertretungstyp sein, der dem Ereigniszugriff zugeordnet ist. Beispiel:

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

Bei einem Ereignis E, ruft die Anweisung die relevante add_E oder remove_E Methode für die Instanz auf, um den Delegaten als Handler für das Ereignis hinzuzufügen oder zu entfernen. Daher entspricht der obige Code folgendem:

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

Zuordnungsanweisungen

Eine Zuweisungsanweisung weist dem Wert eines Ausdrucks eine Variable zu. Es gibt mehrere Zuordnungstypen.

AssignmentStatement
    : RegularAssignmentStatement
    | CompoundAssignmentStatement
    | MidAssignmentStatement
    ;

Reguläre Zuordnungsanweisungen

Eine einfache Zuweisungsanweisung speichert das Ergebnis eines Ausdrucks in einer Variablen.

RegularAssignmentStatement
    : Expression Equals Expression StatementTerminator
    ;

Der Ausdruck auf der linken Seite des Zuordnungsoperators muss als Variable oder eigenschaftszugriff klassifiziert werden, während der Ausdruck auf der rechten Seite des Zuordnungsoperators als Wert klassifiziert werden muss. Der Typ des Ausdrucks muss implizit in den Typ der Variablen oder des Eigenschaftenzugriffs konvertierbar sein.

Wenn es sich bei der Variablen um ein Arrayelement eines Referenztyps handelt, wird eine Laufzeitüberprüfung durchgeführt, um sicherzustellen, dass der Ausdruck mit dem Arrayelementtyp kompatibel ist. Im folgenden Beispiel wird System.ArrayTypeMismatchException die letzte Zuordnung ausgelöst, da eine Instanz eines ArrayList Arrays nicht in einem Element eines String Arrays gespeichert werden kann.

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.

Wenn der Ausdruck auf der linken Seite des Zuordnungsoperators als Variable klassifiziert wird, speichert die Zuordnungsanweisung den Wert in der Variablen. Wenn der Ausdruck als Eigenschaftszugriff klassifiziert wird, wandelt die Zuordnungsanweisung den Eigenschaftenzugriff in einen Aufruf des Set Accessors der Eigenschaft um, wobei der Wert durch den Wertparameter ersetzt wird. Beispiel:

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

Wenn das Ziel der Variablen- oder Eigenschaftenzugriffsart als Werttyp, jedoch nicht als Variable klassifiziert wird, tritt ein Kompilierungszeitfehler auf. Beispiel:

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

Beachten Sie, dass die Semantik der Zuordnung vom Typ der Variablen oder Eigenschaft abhängt, der sie zugewiesen wird. Wenn die Variable, der sie zugewiesen wird, ein Werttyp ist, kopiert die Zuordnung den Wert des Ausdrucks in die Variable. Wenn die Variable, der sie zugewiesen wird, ein Verweistyp ist, kopiert die Zuordnung den Verweis, nicht den Wert selbst, in die Variable. Wenn der Typ der Variablen lautet Object, werden die Zuordnungssemantik bestimmt, ob der Typ des Werts ein Werttyp oder ein Bezugstyp zur Laufzeit ist.

Hinweis: Für systeminterne Typen wie Integer z. B. Referenz- Dateund Wertzuweisungssemantik sind identisch, da die Typen unveränderlich sind. Daher kann die Sprache die Referenzzuweisung für boxed systeminterne Typen als Optimierung verwenden. Aus Der Sicht eines Werts ist das Ergebnis identisch.

Da das Gleichheitszeichen (=) sowohl für die Zuordnung als auch für die Gleichheit verwendet wird, gibt es eine Mehrdeutigkeit zwischen einer einfachen Zuordnung und einer Aufrufanweisung in Situationen wie z x = y.ToString(). B. . In allen solchen Fällen hat die Zuordnungsanweisung Vorrang vor dem Gleichheitsoperator. Dies bedeutet, dass der Beispielausdruck nicht (x = y).ToString()als x = (y.ToString()) .

Zusammengesetzte Zuordnungsanweisungen

Eine zusammengesetzte Zuordnungsanweisung übernimmt die Form V op= E (dabei op handelt es sich um einen gültigen binären Operator).

CompoundAssignmentStatement
    : Expression CompoundBinaryOperator LineTerminator? Expression StatementTerminator
    ;

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

Der Ausdruck auf der linken Seite des Zuordnungsoperators muss als Variable oder Eigenschaftszugriff klassifiziert werden, während der Ausdruck auf der rechten Seite des Zuordnungsoperators als Wert klassifiziert werden muss. Die zusammengesetzte Zuordnungsanweisung entspricht der Anweisung V = V op E mit dem Unterschied, dass die Variable auf der linken Seite des zusammengesetzten Zuordnungsoperators nur einmal ausgewertet wird. Im folgenden Beispiel wird dieser Unterschied veranschaulicht:

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

Der Ausdruck a(GetIndex()) wird zweimal für eine einfache Zuordnung ausgewertet, aber nur einmal für die zusammengesetzte Zuordnung, sodass der Code gedruckt wird:

Simple assignment
Getting index
Getting index
Compound assignment
Getting index

Mid Assignment-Anweisung

Eine Mid Zuordnungsanweisung weist einer anderen Zeichenfolge eine Zeichenfolge zu. Die linke Seite der Zuordnung weist die gleiche Syntax wie ein Aufruf der Funktion Microsoft.VisualBasic.Strings.Midauf.

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

Das erste Argument ist das Ziel der Zuordnung und muss als Variable oder als Eigenschaftszugriff klassifiziert werden, deren Typ implizit in und von .String Der zweite Parameter ist die 1-basierte Startposition, die der Stelle entspricht, an der die Zuordnung in der Zielzeichenfolge beginnen soll, und muss als Wert klassifiziert werden, dessen Typ implizit konvertierbar Integersein muss. Der optionale dritte Parameter ist die Anzahl der Zeichen aus dem rechten Wert, der der Zielzeichenfolge zugewiesen werden soll, und muss als Wert klassifiziert werden, dessen Typ implizit zu konvertierbar Integerist. Die rechte Seite ist die Quellzeichenfolge und muss als Wert klassifiziert werden, dessen Typ implizit konvertierbar Stringist. Die rechte Seite wird an den Längenparameter abgeschnitten, sofern angegeben, und ersetzt die Zeichen in der linken Zeichenfolge, beginnend an der Startposition. Wenn die rechte Zeichenfolge weniger Zeichen als der dritte Parameter enthält, werden nur die Zeichen aus der rechten Seite kopiert.

Im folgenden Beispiel wird folgendes angezeigt 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

Hinweis: Mid ist kein reserviertes Wort.

Aufrufanweisungen

Eine Aufruf-Anweisung ruft eine Methode auf, die dem optionalen Schlüsselwort Callvorausgeht. Die Aufruf-Anweisung wird auf die gleiche Weise wie der Funktionsaufrufausdruck verarbeitet, wobei einige Unterschiede unten angegeben sind. Der Aufrufausdruck muss als Wert oder void klassifiziert werden. Jeder Wert, der sich aus der Auswertung des Aufrufausdrucks ergibt, wird verworfen.

Wenn das Call Schlüsselwort nicht angegeben wird, muss der Aufrufausdruck mit einem Bezeichner oder Schlüsselwort oder mit . einem With Block beginnen. So ist beispielsweise "Call 1.ToString()" eine gültige Anweisung, aber "1.ToString()" nicht. (Beachten Sie, dass in einem Ausdruckskontext auch Aufrufausdrücke nicht mit einem Bezeichner beginnen müssen. Beispielsweise ist "Dim x = 1.ToString()" eine gültige Anweisung).

Es gibt einen weiteren Unterschied zwischen den Aufrufanweisungen und Aufrufausdrücken: Wenn eine Aufruf-Anweisung eine Argumentliste enthält, wird dies immer als Argumentliste des Aufrufs verwendet. Das folgende Beispiel veranschaulicht den Unterschied:

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
    ;

Conditional Statements (Bedingte Anweisungen)

Bedingte Anweisungen ermöglichen die bedingte Ausführung von Anweisungen basierend auf Ausdrücken, die zur Laufzeit ausgewertet werden.

ConditionalStatement
    : IfStatement
    | SelectStatement
    ;

Wenn... Dann... Else-Anweisungen

Eine If...Then...Else Anweisung ist die grundlegende bedingte Anweisung.

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

Jeder Ausdruck in einer If...Then...Else Anweisung muss ein boolescher Ausdruck gemäß Section Boolean Expressions sein. (Hinweis: Dies erfordert keinen booleschen Typ). Wenn der Ausdruck in der If Anweisung true ist, werden die vom If Block eingeschlossenen Anweisungen ausgeführt. Wenn der Ausdruck falsch ist, werden alle ElseIf Ausdrücke ausgewertet. Wenn einer der ElseIf Ausdrücke als wahr ausgewertet wird, wird der entsprechende Block ausgeführt. Wenn kein Ausdruck auf "true" ausgewertet wird und ein Else Block vorhanden ist, wird der Else Block ausgeführt. Sobald der Ausführung eines Blocks abgeschlossen ist, wird die Ausführung an das Ende der If...Then...Else Anweisung übergeben.

Die Zeilenversion der If Anweisung weist einen einzelnen Satz von Anweisungen auf, die ausgeführt werden sollen, wenn der If Ausdruck vorhanden ist True , und einen optionalen Satz von Anweisungen, die ausgeführt werden sollen, wenn der Ausdruck ist False. Beispiel:

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

Die Zeilenversion der If-Anweisung bindet weniger eng als ":", und die Else Bindung wird an die lexikalisch nächste vorangehende, If die von der Syntax zulässig ist. Die folgenden beiden Versionen sind z. B. gleichwertig:

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

Alle Anweisungen außer Bezeichnungsdeklarationsanweisungen sind innerhalb einer Zeilenanweisungen If zulässig, einschließlich Blockanweisungen. Sie können jedoch lineTerminators nicht als "StatementTerminators" verwenden, außer innerhalb von mehrzeiligen Lambda-Ausdrücken. Beispiel:

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

Select Case-Anweisungen

Eine Select Case Anweisung führt Anweisungen basierend auf dem Wert eines Ausdrucks aus.

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

Der Ausdruck muss als Wert klassifiziert werden. Wenn eine Select Case Anweisung ausgeführt wird, wird der Select Ausdruck zuerst ausgewertet, und die Case Anweisungen werden dann in der Reihenfolge der Textdeklaration ausgewertet. Die erste Case Anweisung, die ausgewertet wird, hat True ihren Block ausgeführt. Wenn keine Case Anweisung ausgewertet True wird und eine Case Else Anweisung vorhanden ist, wird dieser Block ausgeführt. Sobald ein Block die Ausführung abgeschlossen hat, wird die Ausführung an das Ende der Select Anweisung übergeben.

Die Ausführung eines Case Blocks darf nicht in den nächsten Schalterabschnitt fallen. Dadurch wird eine allgemeine Klasse von Fehlern verhindert, die in anderen Sprachen auftreten, wenn eine Case Beendigungsanweisung versehentlich weggelassen wird. Dieses Verhalten wird im folgenden Beispiel veranschaulicht:

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

Der Code druckt:

x = 10

Obwohl Case 10 und Case 20 - 10 wählen Sie für denselben Wert aus, wird ausgeführt, Case 10 da sie textmäßig vorangestellt ist Case 20 - 10 . Wenn die nächste Case Erreicht ist, wird die Ausführung nach der Select Anweisung fortgesetzt.

Eine Case Klausel kann zwei Formen annehmen. Ein Formular ist ein optionales Is Schlüsselwort, ein Vergleichsoperator und ein Ausdruck. Der Ausdruck wird in den Typ des Select Ausdrucks konvertiert. Wenn der Ausdruck nicht implizit in den Typ des Select Ausdrucks konvertiert wird, tritt ein Kompilierungszeitfehler auf. Wenn der Ausdruck E ist, lautet der Select Vergleichsoperator Op, und der Case Ausdruck ist E1, wird der Fall als E OP E1 ausgewertet. Der Operator muss für die Typen der beiden Ausdrücke gültig sein; andernfalls tritt ein Kompilierungszeitfehler auf.

Das andere Formular ist optional ein Ausdruck, gefolgt vom Schlüsselwort To und einem zweiten Ausdruck. Beide Ausdrücke werden in den Typ des Select Ausdrucks konvertiert. Wenn ein Ausdruck nicht implizit in den Typ des Select Ausdrucks konvertiert wird, tritt ein Kompilierungszeitfehler auf. Wenn der Select Ausdruck lautet, lautet E1Eder erste Case Ausdruck , und der zweite Case Ausdruck ist E2, wird dies Case entweder als E = E1 (wenn nicht E2 angegeben) oder (E >= E1) And (E <= E2)ausgewertet. Die Operatoren müssen für die Typen der beiden Ausdrücke gültig sein; andernfalls tritt ein Kompilierungszeitfehler auf.

Loop-Anweisungen

Schleifenanweisungen ermöglichen die wiederholte Ausführung der Anweisungen im Textkörper.

LoopStatement
    : WhileStatement
    | DoLoopStatement
    | ForStatement
    | ForEachStatement
    ;

Jedes Mal, wenn ein Schleifentext eingegeben wird, wird eine neue Kopie aus allen lokalen Variablen erstellt, die in diesem Textkörper deklariert sind, initialisiert für die vorherigen Werte der Variablen. Jeder Verweis auf eine Variable innerhalb des Schleifentexts verwendet die zuletzt vorgenommene Kopie. Dieser Code zeigt ein Beispiel:

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

Der Code erzeugt die Ausgabe:

31    32    33

Wenn der Schleifentext ausgeführt wird, wird verwendet, welche Kopie der Variablen aktuell ist. Die Anweisung Dim y = x bezieht sich beispielsweise auf die neueste Kopie y und die originale Kopie von x. Und wenn eine Lambda-Funktion erstellt wird, merkt sie sich, welche Kopie einer Variablen zum Zeitpunkt der Erstellung aktuell war. Daher verwendet jede Lambda-Funktion dieselbe freigegebene Kopie von x, aber eine andere Kopie von y. Am Ende des Programms befindet sich diese freigegebene Kopie x , auf die sie sich beziehen, jetzt am Endwert 3, wenn sie die Lambdas ausführen.

Beachten Sie, dass, wenn es keine Lambda- oder LINQ-Ausdrücke gibt, unmöglich zu wissen, dass eine neue Kopie bei der Schleifeneingabe erstellt wird. In der Tat werden Compileroptimierungen in diesem Fall keine Kopien erstellen. Beachten Sie auch, dass es unzulässig ist, GoTo eine Schleife mit Lambda- oder LINQ-Ausdrücken zu verwenden.

Während... End While and Do... Loop-Anweisungen

Eine While Oder Do Schleifen-Anweisung basiert auf einem booleschen Ausdruck.

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

Eine While Loop-Anweisung wird so lange durchlaufen, wie der boolesche Ausdruck auf "true" ausgewertet wird. Eine Do Loop-Anweisung kann eine komplexere Bedingung enthalten. Ein Ausdruck kann nach dem Do Schlüsselwort oder nach dem Loop Schlüsselwort platziert werden, aber nicht nach beiden. Der boolesche Ausdruck wird gemäß Section Boolean Expressions ausgewertet. (Hinweis: Dies erfordert keinen booleschen Typ). Sie ist auch gültig, um überhaupt keinen Ausdruck anzugeben; in diesem Fall wird die Schleife nie beendet. Wenn der Ausdruck hinter Dodem Ausdruck platziert wird, wird er ausgewertet, bevor der Schleifenblock für jede Iteration ausgeführt wird. Wenn der Ausdruck hinter Loopdem Ausdruck platziert wird, wird er ausgewertet, nachdem der Schleifenblock für jede Iteration ausgeführt wurde. Wenn Sie den Ausdruck nach dem Ausdruck platzieren Loop , wird daher eine weitere Schleife als nach der Platzierung Dogeneriert. In folgendem Beispiel wird dieses Verhalten veranschaulicht:

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

Der Code erzeugt die Ausgabe:

Second Loop

Bei der ersten Schleife wird die Bedingung ausgewertet, bevor die Schleife ausgeführt wird. Bei der zweiten Schleife wird die Bedingung nach ausführung der Schleife ausgeführt. Dem bedingten Ausdruck muss entweder ein While Schlüsselwort oder ein Until Schlüsselwort vorangestellt werden. Der erste Bricht die Schleife auf, wenn die Bedingung als falsch ausgewertet wird, letztere, wenn die Bedingung als wahr ausgewertet wird.

Hinweis: Until ist kein reserviertes Wort.

Für... Next-Anweisungen

Eine For...Next Anweisungsschleife basiert auf einer Gruppe von Grenzen. Eine For Anweisung gibt eine Schleifensteuerelementvariable, einen unteren gebundenen Ausdruck, einen ausdruck der oberen Grenze und einen optionalen Schrittwertausdruck an. Die Variable des Schleifensteuerelements wird entweder über einen Bezeichner angegeben, gefolgt von einer optionalen As Klausel oder einem Ausdruck.

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

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

NextExpressionList
    : Expression ( Comma Expression )*
    ;

Gemäß den folgenden Regeln bezieht sich die Variable des Schleifensteuerelements entweder auf eine neue lokale Variable, die für diese For...Next Anweisung spezifisch ist, oder auf eine bereits vorhandene Variable oder auf einen Ausdruck.

  • Wenn es sich bei der Schleifensteuerelementvariable um einen Bezeichner mit einer As Klausel handelt, definiert der Bezeichner eine neue lokale Variable des typs, der in der As Klausel angegeben ist, die auf die gesamte For Schleife festgelegt ist.

  • Wenn es sich bei der Variablen des Schleifensteuerelements um einen Bezeichner ohne Klausel As handelt, wird der Bezeichner zuerst mithilfe der Regeln für die Auflösung einfacher Namen aufgelöst (siehe Abschnittsausdrücke mit einfachem Namen), mit der Ausnahme, dass sich dieses Vorkommen des Bezeichners nicht in und von sich selbst befindet, dazu führen würde, dass eine implizite lokale Variable erstellt wird (Abschnitt implizite lokale Deklarationen).

    • Wenn diese Auflösung erfolgreich ist und das Ergebnis als Variable klassifiziert wird, ist die Variable des Schleifensteuerelements die bereits vorhandene Variable.

    • Wenn die Lösung fehlschlägt oder die Auflösung erfolgreich ist und das Ergebnis als Typ klassifiziert wird, dann:

      • Wenn lokale Variablentypausschlüsse verwendet werden, definiert der Bezeichner eine neue lokale Variable, deren Typ von den gebundenen und Schrittausdrücken abgeleitet wird, die auf die gesamte For Schleife begrenzt ist;
      • wenn die lokale Variablentyp-Ableitung nicht verwendet wird, aber implizite lokale Deklaration ist, wird eine implizite lokale Variable erstellt, deren Bereich die gesamte Methode (Abschnitt implizite lokale Deklarationen) ist, und die Variable des Schleifensteuerelements bezieht sich auf diese bereits vorhandene Variable;
      • Wenn weder lokale Variablentyp-Ableitungen noch implizite lokale Deklarationen verwendet werden, handelt es sich um einen Fehler.
    • Wenn die Auflösung erfolgreich ist, die weder als Typ noch als Variable klassifiziert ist, handelt es sich um einen Fehler.

  • Wenn es sich bei der Variablen des Schleifensteuerelements um einen Ausdruck handelt, muss der Ausdruck als Variable klassifiziert werden.

Eine Loop-Steuerelementvariable kann nicht von einer anderen eingeschlossenen For...Next Anweisung verwendet werden. Der Typ der Schleifensteuerungsvariable einer For Anweisung bestimmt den Typ der Iteration und muss eine der folgenden Sein:

  • Byte, SByte, UShortIntegerUIntegerShort, ULong, , Long, , , SingleDecimalDouble
  • Ein Aufzählungstyp
  • Object
  • Ein Typ T mit den folgenden Operatoren, wobei B es sich um einen Typ handelt, der in einem booleschen Ausdruck verwendet werden kann:
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

Die gebundenen und Schrittausdrücke müssen implizit in den Typ der Schleifensteuerelementvariablen konvertierbar sein und als Werte klassifiziert werden. Zur Kompilierungszeit wird der Typ der Schleifensteuerelementvariable abgeleitet, indem der breiteste Typ zwischen den Unteren, oberen Begrenzungs- und Schrittausdruckstypen ausgewählt wird. Wenn keine Erweiterung zwischen zwei typen vorhanden ist, tritt ein Kompilierungszeitfehler auf.

Wenn es sich bei der Laufzeit um den Typ der Schleifensteuerungsvariable handelt Object, wird der Typ der Iteration mit zwei Ausnahmen wie zur Kompilierungszeit abgeleitet. Wenn die gebundenen und Schrittausdrücke alle integralen Typen sind, aber keinen breiten Typ aufweisen, wird der breiteste Typ, der alle drei Typen umfasst, abgeleitet. Und zweitens, wenn der Typ der Schleifensteuerungsvariable abgeleitet Stringwird, Double wird stattdessen abgeleitet. Wenn zur Laufzeit kein Schleifensteuerungstyp bestimmt werden kann oder wenn eines der Ausdrücke nicht in den Schleifensteuerelementtyp konvertiert werden kann, tritt ein System.InvalidCastException Fehler auf. Sobald am Anfang der Schleife ein Schleifensteuerelementtyp ausgewählt wurde, wird derselbe Typ während der gesamten Iteration verwendet, unabhängig von Änderungen, die an dem Wert in der Variablen des Schleifensteuerelements vorgenommen wurden.

Eine For Anweisung muss durch eine übereinstimmende Next Anweisung geschlossen werden. Eine Next Anweisung ohne Variable entspricht der innersten geöffneten For Anweisung, während eine Next Anweisung mit einer oder mehreren Schleifensteuerungsvariablen von links nach rechts mit den Schleifen übereinstimmt, die For mit den einzelnen Variablen übereinstimmen. Wenn eine Variable mit einer For Schleife übereinstimmt, die an diesem Punkt nicht die geschachtelte Schleife ist, führt ein Kompilierungszeitfehler zu einem Fehler.

Am Anfang der Schleife werden die drei Ausdrücke in textbezogener Reihenfolge ausgewertet, und der untere gebundene Ausdruck wird der Variablen für Schleifensteuerelemente zugewiesen. Wenn der Schrittwert nicht angegeben wird, wird er implizit in das Literal 1konvertiert, das in den Typ der Loop-Steuerelementvariable konvertiert wird. Die drei Ausdrücke werden nur am Anfang der Schleife ausgewertet.

Am Anfang jeder Schleife wird die Steuerelementvariable verglichen, um festzustellen, ob sie größer als der Endpunkt ist, wenn der Schrittausdruck positiv oder kleiner als der Endpunkt ist, wenn der Schrittausdruck negativ ist. Andernfalls wird die For Schleife beendet. Andernfalls wird der Schleifenblock ausgeführt. Wenn die Variable des Schleifensteuerelements kein Grundtyp ist, wird der Vergleichsoperator bestimmt, ob der Ausdruck step >= step - step wahr oder falsch ist. Bei der Next Anweisung wird der Schrittwert der Steuerelementvariablen hinzugefügt, und die Ausführung wird an den Anfang der Schleife zurückgegeben.

Beachten Sie, dass für jede Iteration des Schleifenblocks keine neue Kopie der Schleifensteuerelementvariable erstellt wird. In dieser Hinsicht unterscheidet sich die For Anweisung von For Each (Abschnitt für Jede... Nächste Anweisungen).

Es ist ungültig, eine Schleife außerhalb der Schleife zu For verzweigen.

Für jeden... Next-Anweisungen

Eine For Each...Next Anweisung wird basierend auf den Elementen in einem Ausdruck in einer Schleife ausgeführt. Eine For Each Anweisung gibt eine Schleifensteuerelementvariable und einen Enumerationsausdruck an. Die Variable des Schleifensteuerelements wird entweder über einen Bezeichner angegeben, gefolgt von einer optionalen As Klausel oder einem Ausdruck.

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

Folgen der gleichen Regeln wie For...Next Anweisungen (Abschnitt für... Next Statements) bezieht sich die Variable des Schleifensteuerelements entweder auf eine neue lokale Variable, die für diese For Each spezifisch ist... Nächste Anweisung oder einer bereits vorhandenen Variable oder einem Ausdruck.

Der Enumerationsausdruck muss als Wert klassifiziert werden, und sein Typ muss ein Auflistungstyp oder Objectein Auflistungstyp sein. Wenn der Typ des Enumerationsausdrucks lautet Object, wird die gesamte Verarbeitung bis zur Laufzeit zurückgestellt. Andernfalls muss eine Konvertierung vom Elementtyp der Auflistung in den Typ der Schleifensteuerelementvariable vorhanden sein.

Die Variable des Schleifensteuerelements kann nicht von einer anderen eingeschlossenen For Each Anweisung verwendet werden. Eine For Each Anweisung muss durch eine übereinstimmende Next Anweisung geschlossen werden. Eine Next Anweisung ohne Schleifensteuerungsvariable entspricht der innersten geöffneten For Each. Eine Next Anweisung mit einer oder mehreren Schleifensteuerelementvariablen stimmt von links nach rechts mit den Schleifen überein, die For Each dieselbe Schleifensteuerelementvariable aufweisen. Wenn eine Variable mit einer For Each Schleife übereinstimmt, die nicht die verschachtelte Schleife an diesem Punkt ist, tritt ein Kompilierungsfehler auf.

Ein Typ C wird als Sammlungstyp bezeichnet, wenn einer von:

  • Alle folgenden Punkte sind wahr:

    • C enthält eine barrierefreie Instanz, eine freigegebene oder Erweiterungsmethode mit der Signatur GetEnumerator() , die einen Typ Ezurückgibt.
    • E enthält eine barrierefreie Instanz, eine freigegebene oder Erweiterungsmethode mit der Signatur MoveNext() und dem Rückgabetyp Boolean.
    • E enthält eine barrierefreie Instanz oder freigegebene Eigenschaft mit dem Namen Current eines Getter. Der Typ dieser Eigenschaft ist der Elementtyp des Auflistungstyps.
  • Sie implementiert die Schnittstelle System.Collections.Generic.IEnumerable(Of T), in diesem Fall wird der Elementtyp der Auflistung als betrachtet T.

  • Sie implementiert die Schnittstelle System.Collections.IEnumerable, in diesem Fall wird der Elementtyp der Auflistung als betrachtet Object.

Im Folgenden finden Sie ein Beispiel für eine Klasse, die aufgezählt werden kann:

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

Bevor die Schleife beginnt, wird der Enumerationsausdruck ausgewertet. Wenn der Typ des Ausdrucks das Entwurfsmuster nicht erfüllt, wird der Ausdruck in System.Collections.IEnumerable oder System.Collections.Generic.IEnumerable(Of T). Wenn der Ausdruckstyp die generische Schnittstelle implementiert, wird die generische Schnittstelle zur Kompilierungszeit bevorzugt, aber die nicht generische Schnittstelle wird zur Laufzeit bevorzugt. Wenn der Ausdruckstyp die generische Schnittstelle mehrmals implementiert, wird die Anweisung als mehrdeutig betrachtet, und ein Kompilierungszeitfehler tritt auf.

Hinweis: Die nicht generische Schnittstelle wird im spät gebundenen Fall bevorzugt, da die Auswahl der generischen Schnittstelle bedeuten würde, dass alle Aufrufe der Schnittstellenmethoden Typparameter umfassen würden. Da es nicht möglich ist, die übereinstimmenden Typargumente zur Laufzeit zu kennen, müssten alle derartigen Aufrufe mit verspätet gebundenen Aufrufen erfolgen. Dies wäre langsamer als das Aufrufen der nicht generischen Schnittstelle, da die nicht generische Schnittstelle mithilfe von Kompilierungszeitaufrufen aufgerufen werden könnte.

GetEnumerator wird für den resultierenden Wert aufgerufen, und der Rückgabewert der Funktion wird in einer temporären Datei gespeichert. Anschließend wird am Anfang jeder Iteration MoveNext die temporären Aufrufe ausgeführt. Wenn sie zurückgegeben wird False, wird die Schleife beendet. Andernfalls wird jede Iteration der Schleife wie folgt ausgeführt:

  1. Wenn die Variable des Schleifensteuerelements eine neue lokale Variable (anstelle einer bereits vorhandenen Variable) identifiziert hat, wird eine neue Kopie dieser lokalen Variablen erstellt. Bei der aktuellen Iteration beziehen sich alle Verweise innerhalb des Schleifenblocks auf diese Kopie.
  2. Die Current Eigenschaft wird abgerufen, an den Typ der Schleifensteuerelementvariable (unabhängig davon, ob die Konvertierung implizit oder explizit ist) umgewandelt und der Variablen für Schleifensteuerelemente zugewiesen.
  3. Der Schleifenblock wird ausgeführt.

Hinweis: Es gibt eine geringfügige Änderung des Verhaltens zwischen Version 10.0 und 11.0 der Sprache. Vor 11.0 wurde für jede Iteration der Schleife keine neue Iterationsvariable erstellt. Dieser Unterschied ist nur dann feststellbar, wenn die Iterationsvariable von einer Lambda- oder LINQ-Expression erfasst wird, die dann nach der Schleife aufgerufen wird:

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

Bis zu Visual Basic 10.0 hat dies zur Kompilierungszeit eine Warnung erzeugt und dreimal "3" gedruckt. Das lag daran, dass nur eine einzelne Variable "x" von allen Iterationen der Schleife gemeinsam genutzt wurde, und alle drei Lambdas haben dasselbe "x" erfasst, und zum Zeitpunkt der Ausführung der Lambdas hielt sie dann die Zahl 3. Ab Visual Basic 11.0 wird "1, 2, 3" gedruckt. Der Grund dafür ist, dass jede Lambda-Funktion eine andere Variable "x" erfasst.

Hinweis: Das aktuelle Element der Iteration wird in den Typ der Schleifensteuerungsvariable konvertiert, auch wenn die Konvertierung explizit ist, da es keinen praktischen Ort gibt, um einen Konvertierungsoperator in der Anweisung einzuführen. Dies wurde beim Arbeiten mit dem nun veralteten Typ System.Collections.ArrayListbesonders problematisch, da der Elementtyp .Object Dies hätte Guss in einer großen Menge Schleifen benötigt, etwas, das wir fühlten, war nicht ideal. Ironischerweise ermöglichten Generika die Erstellung einer stark typierten Sammlung, System.Collections.Generic.List(Of T)die uns möglicherweise diesen Entwurfspunkt überdenken ließ, aber aus Gründen der Kompatibilität kann dies jetzt nicht geändert werden.

Wenn die Next Anweisung erreicht ist, wird die Ausführung an den Anfang der Schleife zurückgegeben. Wenn eine Variable nach dem Next Schlüsselwort angegeben wird, muss sie mit der ersten Variablen nach dem For EachSchlüsselwort identisch sein. Betrachten Sie z. B. den folgenden Code:

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

Sie entspricht dem folgenden Code:

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

Wenn der Enumeratortyp E implementiert System.IDisposablewird, wird der Enumerator beim Beenden der Schleife durch Aufrufen der Dispose Methode verworfen. Dadurch wird sichergestellt, dass ressourcen, die vom Enumerator gehalten werden, freigegeben werden. Wenn die Methode, die die For Each Anweisung enthält, keine unstrukturierte Fehlerbehandlung verwendet, wird die For Each Anweisung in eine Try Anweisung eingeschlossen, in der die Dispose Methode aufgerufen Finally wird, um die Bereinigung sicherzustellen.

Hinweis: Der System.Array Typ ist ein Sammlungstyp, und da alle Arraytypen von System.Arraydiesem abgeleitet sind, ist jeder Arraytypausdruck in einer For Each Anweisung zulässig. Bei eindimensionalen Arrays listet die For Each Anweisung die Arrayelemente in zunehmender Indexreihenfolge auf, beginnend mit Index 0 und enden mit Indexlänge - 1. Bei mehrdimensionalen Arrays werden die Indizes der äußerst rechten Dimension zuerst erhöht.

Der folgende Code wird z. B. gedruckt 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

Es ist ungültig, in einen For Each Anweisungsblock außerhalb des Blocks zu verzweigen.

Exception-Handling-Anweisungen

Visual Basic unterstützt strukturierte Ausnahmebehandlung und unstrukturierte Ausnahmebehandlung. In einer Methode kann nur ein Ausnahmebehandlungsstil verwendet werden, die Anweisung kann jedoch Error in der strukturierten Ausnahmebehandlung verwendet werden. Wenn eine Methode beide Arten der Ausnahmebehandlung verwendet, führt ein Kompilierungszeitfehler zu ergebnissen.

ErrorHandlingStatement
    : StructuredErrorStatement
    | UnstructuredErrorStatement
    ;

Strukturierte Exception-Handling-Anweisungen

Die strukturierte Ausnahmebehandlung ist eine Methode zum Behandeln von Fehlern, indem explizite Blöcke deklariert werden, in denen bestimmte Ausnahmen behandelt werden. Die strukturierte Ausnahmebehandlung erfolgt über eine Try Anweisung.

StructuredErrorStatement
    : ThrowStatement
    | TryStatement
    ;

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

Beispiel:

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

Eine Try Anweisung besteht aus drei Arten von Blöcken: Versuchen Sie Blöcke, Fangen von Blöcken und schließlich Blöcken. Ein Try-Block ist ein Anweisungsblock, der die auszuführenden Anweisungen enthält. Ein Catch-Block ist ein Anweisungsblock, der eine Ausnahme behandelt. Ein abschließender Block ist ein Anweisungsblock, der Anweisungen enthält, die ausgeführt werden sollen, wenn die Try Anweisung beendet wird, unabhängig davon, ob eine Ausnahme aufgetreten ist und behandelt wurde. Eine Try Anweisung, die nur einen Try-Block und einen endgültigen Block enthalten kann, muss mindestens einen Catch-Block oder schließlich block enthalten. Es ist ungültig, die Ausführung explizit in einen Try-Block zu übertragen, außer innerhalb eines Catch-Blocks in derselben Anweisung.

Finally-Blöcke

Ein Finally Block wird immer ausgeführt, wenn die Ausführung einen Teil der Try Anweisung verlässt. Es ist keine explizite Aktion erforderlich, um den Finally Block auszuführen. Wenn die Ausführung die Try Anweisung verlässt, führt das System den Finally Block automatisch aus und überträgt dann die Ausführung an das beabsichtigte Ziel. Der Finally Block wird unabhängig davon ausgeführt, wie die Ausführung die Try Anweisung verlässt: bis zum Ende des Try Blocks, bis zum Ende eines Catch Blocks, durch eine Anweisung, durch eine Exit TryGoTo Anweisung oder durch keine Behandlung einer ausgelösten Ausnahme.

Beachten Sie, dass der Await Ausdruck in einer asynchronen Methode und die Yield Anweisung in einer Iteratormethode dazu führen kann, dass der Steuerfluss in der asynchronen oder iterator-Methodeninstanz angehalten und in einer anderen Methodeninstanz fortgesetzt wird. Dies ist jedoch lediglich eine Aussetzung der Ausführung und beinhaltet nicht das Beenden der jeweiligen asynchronen Methode oder iterator-Methodeninstanz und führt daher nicht Finally dazu, dass Blöcke ausgeführt werden.

Es ist ungültig, die Ausführung explizit in einen Finally Block zu übertragen. Es ist auch ungültig, die Ausführung aus einem Finally Block außer über eine Ausnahme zu übertragen.

FinallyStatement
    : 'Finally' StatementTerminator
      Block?
    ;

catch-Blöcke

Wenn beim Verarbeiten des Try Blocks eine Ausnahme auftritt, wird jede Catch Anweisung in textualer Reihenfolge untersucht, um festzustellen, ob die Ausnahme behandelt wird.

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

Der in einer Catch Klausel angegebene Bezeichner stellt die Ausnahme dar, die ausgelöst wurde. Wenn der Bezeichner eine As Klausel enthält, wird der Bezeichner als im lokalen Deklarationsbereich des Catch Blocks deklariert. Andernfalls muss der Bezeichner eine lokale Variable (keine statische Variable) sein, die in einem enthaltenden Block definiert wurde.

Eine Catch Klausel ohne Bezeichner erfasst alle ausnahmen, die von System.Exception. Eine Catch Klausel mit einem Bezeichner erfasst nur Ausnahmen, deren Typen mit dem Typ des Bezeichners identisch oder abgeleitet sind. Der Typ muss System.Exceptionoder ein von System.Exception. Wenn eine Ausnahme abgefangen wird, von System.Exceptionder abgeleitet wird, wird ein Verweis auf das Ausnahmeobjekt im von der Funktion Microsoft.VisualBasic.Information.Errzurückgegebenen Objekt gespeichert.

A Catch clause with a When clause will only catch exceptions when the expression evaluates to True; the type of the expression must be a Boolean expression as per Section Boolean Expressions. Eine When Klausel wird erst angewendet, nachdem der Typ der Ausnahme überprüft wurde, und der Ausdruck kann auf den Bezeichner verweisen, der die Ausnahme darstellt, wie in diesem Beispiel veranschaulicht wird:

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

In diesem Beispiel wird gedruckt:

Third handler

Wenn eine Catch Klausel die Ausnahme behandelt, wird die Ausführung an den Catch Block übertragen. Am Ende des Catch Blocks überträgt die Ausführung an die erste Anweisung, die auf die Try Anweisung folgt. Die Try Anweisung behandelt keine Ausnahmen, die in einem Catch Block ausgelöst werden. Wenn keine Catch Klausel die Ausnahme behandelt, wird die Ausführung an einen vom System festgelegten Speicherort übertragen.

Es ist ungültig, die Ausführung explizit in einen Catch Block zu übertragen.

Die Filter in When-Klauseln werden normalerweise vor dem Auslösen der Ausnahme ausgewertet. Der folgende Code druckt z. B. "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

Async- und Iterator-Methoden führen jedoch dazu, dass alle Blöcke innerhalb dieser Blöcke vor allen Filtern außerhalb ausgeführt werden. Wenn beispielsweise der obige Code vorhanden war Async Sub Foo(), lautet die Ausgabe "Schließlich, Filter, Catch".

Throw-Anweisung

Die Throw Anweisung löst eine Ausnahme aus, die durch eine Instanz eines vom Typ abgeleiteten System.ExceptionTyps dargestellt wird.

ThrowStatement
    : 'Throw' Expression? StatementTerminator
    ;

Wenn der Ausdruck nicht als Wert klassifiziert wird oder kein typ System.Exceptionabgeleiteter Typ ist, tritt ein Kompilierungszeitfehler auf. Wenn der Ausdruck zur Laufzeit als Nullwert ausgewertet wird, wird stattdessen eine System.NullReferenceException Ausnahme ausgelöst.

Eine Throw Anweisung kann den Ausdruck innerhalb eines Catch-Blocks einer Try Anweisung weglassen, solange kein endgültiger Block vorhanden ist. In diesem Fall beschreibt die Anweisung die Ausnahme, die derzeit im Catch-Block behandelt wird. Beispiel:

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

Unstrukturierte Exception-Handling-Anweisungen

Die unstrukturierte Ausnahmebehandlung ist eine Methode zum Behandeln von Fehlern, indem Anweisungen zum Verzweigen angibt werden, wenn eine Ausnahme auftritt. Die unstrukturierte Ausnahmebehandlung wird mithilfe von drei Anweisungen implementiert: der Anweisung, der ErrorOn Error Anweisung, der Anweisung und der Resume Anweisung.

UnstructuredErrorStatement
    : ErrorStatement
    | OnErrorStatement
    | ResumeStatement
    ;

Beispiel:

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

Wenn eine Methode unstrukturierte Ausnahmebehandlung verwendet, wird ein einzelner strukturierter Ausnahmehandler für die gesamte Methode erstellt, die alle Ausnahmen abfangen soll. (Beachten Sie, dass dieser Handler in Konstruktoren nicht über den Aufruf des Aufrufs New am Anfang des Konstruktors erweitert wird.) Die Methode verfolgt dann den neuesten Ausnahmehandlerspeicherort und die neueste Ausnahme, die ausgelöst wurde. Bei der Eingabe in die Methode werden die Position des Ausnahmehandlers und die Ausnahme auf beide festgelegt Nothing. Wenn eine Ausnahme in einer Methode ausgelöst wird, die unstrukturierte Ausnahmebehandlung verwendet, wird ein Verweis auf das Ausnahmeobjekt im von der Funktion Microsoft.VisualBasic.Information.Errzurückgegebenen Objekt gespeichert.

Unstrukturierte Fehlerbehandlungsanweisungen sind in iterator- oder asynchronen Methoden nicht zulässig.

Error-Anweisung

Eine Error Anweisung löst eine System.Exception Ausnahme mit einer Visual Basic 6-Ausnahmenummer aus. Der Ausdruck muss als Wert klassifiziert werden, und sein Typ muss implizit in Integer.

ErrorStatement
    : 'Error' Expression StatementTerminator
    ;

On Error-Anweisung

Eine On Error Anweisung ändert den neuesten Ausnahmebehandlungszustand.

OnErrorStatement
    : 'On' 'Error' ErrorClause StatementTerminator
    ;

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

Es kann auf eine von vier Arten verwendet werden:

  • On Error GoTo -1 setzt die letzte Ausnahme auf Nothing.

  • On Error GoTo 0 setzt den neuesten Ausnahmehandlerspeicherort auf Nothing.

  • On Error GoTo LabelName legt die Bezeichnung als aktuellen Ausnahmehandlerspeicherort fest. Diese Anweisung kann nicht in einer Methode verwendet werden, die einen Lambda- oder Abfrageausdruck enthält.

  • On Error Resume Next legt das Resume Next Verhalten als den neuesten Speicherort des Ausnahmehandlers fest.

Resume-Anweisung

Eine Resume Anweisung gibt die Ausführung an die Anweisung zurück, die die letzte Ausnahme verursacht hat.

ResumeStatement
    : 'Resume' ResumeClause? StatementTerminator
    ;

ResumeClause
    : 'Next'
    | LabelName
    ;

Wenn der Next Modifizierer angegeben ist, wird die Ausführung an die Anweisung zurückgegeben, die nach der Anweisung ausgeführt wurde, die die letzte Ausnahme verursacht hat. Wenn ein Bezeichnungsname angegeben ist, wird die Ausführung an die Bezeichnung zurückgegeben.

Da die SyncLock Anweisung einen impliziten strukturierten Fehlerbehandlungsblock enthält und Resume Next spezielle Verhaltensweisen für Ausnahmen aufweist, Resume die in SyncLock Anweisungen auftreten. Resume gibt die Ausführung an den Anfang der SyncLock Anweisung zurück, während Resume Next die Ausführung an die nächste Anweisung nach der SyncLock Anweisung zurückgegeben wird. Betrachten Sie z. B. den folgenden Code:

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

Es druckt das folgende Ergebnis.

Before exception
Before exception
After SyncLock

Wenn Sie die SyncLock Anweisung zum ersten Mal durchlaufen, Resume wird die Ausführung an den Anfang der SyncLock Anweisung zurückgegeben. Das zweite Mal durch die SyncLock Anweisung gibt Resume Next die Ausführung bis zum Ende der SyncLock Anweisung zurück. Resume und Resume Next sind innerhalb einer SyncLock Anweisung nicht zulässig.

In allen Fällen, wenn eine Resume Anweisung ausgeführt wird, wird die neueste Ausnahme auf Nothingfestgelegt. Wenn eine Resume Anweisung ohne letzte Ausnahme ausgeführt wird, löst die Anweisung eine System.Exception Ausnahme aus, die die Visual Basic-Fehlernummer 20 (Resume without error) enthält.

Branch-Anweisungen

Branch-Anweisungen ändern den Ausführungsfluss in einer Methode. Es gibt sechs Verzweigungsanweisungen:

  1. Eine GoTo Anweisung bewirkt, dass die Ausführung auf die angegebene Bezeichnung in der Methode übertragen wird. Es ist nicht zulässigGoTo, ForFor EachUsingWithSyncLockin einem Lambda- oder LINQ-Ausdruck eine lokale Variable dieses Blocks in einem TryLambda- oder LINQ-Ausdruck aufzunehmen.
  2. Eine Exit Anweisung überträgt die Ausführung an die nächste Anweisung nach dem Ende der sofort enthaltenden Block-Anweisung der angegebenen Art. Wenn der Block der Methodenblock ist, beendet der Ablauf die Methode, wie im Abschnittssteuerungsfluss beschrieben. Wenn die Exit Anweisung nicht in der Art des in der Anweisung angegebenen Blocks enthalten ist, tritt ein Kompilierungszeitfehler auf.
  3. Eine Continue Anweisung überträgt die Ausführung an das Ende der sofort enthaltenden Blockschleifen-Anweisung der angegebenen Art. Wenn die Continue Anweisung nicht in der Art des in der Anweisung angegebenen Blocks enthalten ist, tritt ein Kompilierungszeitfehler auf.
  4. Eine Stop Anweisung bewirkt, dass eine Debuggerausnahme auftritt.
  5. Eine End Anweisung beendet das Programm. Finalizer werden vor dem Herunterfahren ausgeführt, aber die abschließenden Blöcke aller derzeit ausgeführten Try Anweisungen werden nicht ausgeführt. Diese Anweisung kann nicht in Programmen verwendet werden, die nicht ausführbar sind (z. B. DLLs).
  6. Eine Return Anweisung ohne Ausdruck entspricht einer oder Exit Function einer Exit Sub Anweisung. Eine Return Anweisung mit einem Ausdruck ist nur in einer regulären Methode zulässig, die eine Funktion ist, oder in einer asynchronen Methode, die eine Funktion mit Rückgabetyp Task(Of T) für einige Tist. Der Ausdruck muss als Wert klassifiziert werden, der implizit in die Funktionsrücklaufvariable (bei regulären Methoden) oder in die Task-Rückgabevariable (bei asynchronen Methoden) konvertierbar ist. Das Verhalten besteht darin, den Ausdruck auszuwerten und dann in der Rückgabevariablen zu speichern und dann eine implizite Exit Function Anweisung auszuführen.
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
    ;

Array-Handling-Anweisungen

Zwei Anweisungen vereinfachen das Arbeiten mit Arrays: ReDim Anweisungen und Erase Anweisungen.

ArrayHandlingStatement
    : RedimStatement
    | EraseStatement
    ;

ReDim-Anweisung

Eine ReDim Anweisung instanziiert neue Arrays.

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

RedimClauses
    : RedimClause ( Comma RedimClause )*
    ;

RedimClause
    : Expression ArraySizeInitializationModifier
    ;

Jede Klausel in der Anweisung muss als Variable oder eigenschaftszugriff klassifiziert werden, deren Typ ein Arraytyp ist oder Object, und auf eine Liste von Arraygrenzen folgt. Die Anzahl der Grenzen muss mit dem Typ der Variablen übereinstimmen; eine beliebige Anzahl von Grenzen ist zulässig.Object Zur Laufzeit wird ein Array für jeden Ausdruck von links nach rechts mit den angegebenen Grenzen instanziiert und dann der Variablen oder Eigenschaft zugewiesen. Wenn der Variabletyp lautet Object, ist die Anzahl der Dimensionen die angegebene Anzahl von Dimensionen, und der Arrayelementtyp ist Object. Wenn die angegebene Anzahl von Dimensionen nicht mit der Variablen oder Eigenschaft zur Laufzeit kompatibel ist, tritt ein Kompilierungsfehler auf. Beispiel:

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

Wenn das Preserve Schlüsselwort angegeben ist, müssen die Ausdrücke auch als Wert klassifiziert werden, und die neue Größe für jede Dimension mit Ausnahme der am rechten Rand stehenden muss mit der Größe des vorhandenen Arrays identisch sein. Die Werte im vorhandenen Array werden in das neue Array kopiert: Wenn das neue Array kleiner ist, werden die vorhandenen Werte verworfen; Wenn das neue Array größer ist, werden die zusätzlichen Elemente auf den Standardwert des Elementtyps des Arrays initialisiert. Betrachten Sie z. B. den folgenden Code:

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

Es druckt das folgende Ergebnis:

3, 0

Wenn es sich bei dem vorhandenen Arrayverweis um einen Nullwert zur Laufzeit handelt, wird kein Fehler angegeben. Abgesehen von der äußerst rechten Dimension, wenn sich die Größe einer Dimension ändert, wird eine System.ArrayTypeMismatchException ausgelöst.

Hinweis: Preserve ist kein reserviertes Wort.

Erase-Anweisung

Eine Erase Anweisung legt jede der Arrayvariablen oder Eigenschaften fest, die in der Anweisung angegeben sind, auf Nothing. Jeder Ausdruck in der Anweisung muss als Variable oder Eigenschaftszugriff klassifiziert werden, deren Typ ein Arraytyp oder Objectein Arraytyp ist. Beispiel:

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

        ' The following two statements are equivalent.
        Erase x
        x = Nothing
    End Sub
End Module
EraseStatement
    : 'Erase' EraseExpressions StatementTerminator
    ;

EraseExpressions
    : Expression ( Comma Expression )*
    ;

Using statement (Using-Anweisung)

Instanzen von Typen werden automatisch vom Garbage Collector freigegeben, wenn eine Auflistung ausgeführt wird und keine Liveverweise auf die Instanz gefunden werden. Wenn ein Typ eine besonders wertvolle und knappe Ressource (z. B. Datenbankverbindungen oder Dateihandles) enthält, ist es möglicherweise nicht wünschenswert, bis die nächste Garbage Collection wartet, um eine bestimmte Instanz des Typs zu bereinigen, der nicht mehr verwendet wird. Um eine einfache Möglichkeit zum Freigeben von Ressourcen vor einer Sammlung bereitzustellen, kann ein Typ die System.IDisposable Schnittstelle implementieren. Ein Typ, der dies tut, macht eine Dispose Methode verfügbar, die aufgerufen werden kann, um zu erzwingen, dass wertvolle Ressourcen sofort freigegeben werden, z. B.:

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

        ' Do some work
        ...

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

Die Using Anweisung automatisiert den Vorgang des Abrufens einer Ressource, das Ausführen einer Reihe von Anweisungen und das anschließende Löschen der Ressource. Die Anweisung kann zwei Formen annehmen: In einer ist die Ressource eine lokale Variable, die als Teil der Anweisung deklariert und als reguläre lokale Variablendeklarationsanweisung behandelt wird; in the other, the resource is the result of an expression.

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

UsingResources
    : VariableDeclarators
    | Expression
    ;

Wenn es sich bei der Ressource um eine lokale Variablendeklarationsanweisung handelt, muss der Typ der lokalen Variablendeklaration ein Typ sein, der implizit in konvertiert System.IDisposablewerden kann. Die deklarierten lokalen Variablen sind schreibgeschützt, auf den Using Anweisungsblock festgelegt und müssen einen Initialisierer enthalten. Wenn die Ressource das Ergebnis eines Ausdrucks ist, muss der Ausdruck als Wert klassifiziert werden und muss einen Typ aufweisen, der implizit in konvertiert System.IDisposablewerden kann. Der Ausdruck wird nur einmal ausgewertet, am Anfang der Anweisung.

Der Using Block ist implizit in einer Try Anweisung enthalten, deren block schließlich die Methode IDisposable.Dispose für die Ressource aufruft. Dadurch wird sichergestellt, dass die Ressource auch dann verworfen wird, wenn eine Ausnahme ausgelöst wird. Daher ist es ungültig, einen Block von außerhalb des Blocks zu Using verzweigen, und ein Using Block wird als einzelne Anweisung für die Zwecke Resume und .Resume Next Wenn die Ressource lautet Nothing, wird kein Aufruf Dispose ausgeführt. Das Beispiel:

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

entspricht:

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

Eine Using Anweisung mit einer lokalen Variablendeklarationsanweisung kann mehrere Ressourcen gleichzeitig abrufen, was geschachtelten Using Anweisungen entspricht. Beispiel: eine Using Anweisung des Formulars:

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

entspricht:

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

Await-Anweisung

Eine Await-Anweisung hat dieselbe Syntax wie ein Await-Operator-Ausdruck (Section Await Operator), ist nur in Methoden zulässig, die auch Await-Ausdrücke zulassen und dasselbe Verhalten wie ein Await-Operatorausdruck aufweisen.

Sie kann jedoch entweder als Wert oder nichtig klassifiziert werden. Alle Werte, die sich aus der Auswertung des Await-Operatorausdrucks ergeben, werden verworfen.

AwaitStatement
    : AwaitOperatorExpression StatementTerminator
    ;

Yield-Anweisung

Yield-Anweisungen beziehen sich auf Iteratormethoden, die in Section Iterator-Methoden beschrieben werden.

YieldStatement
    : 'Yield' Expression StatementTerminator
    ;

Yield ist ein reserviertes Wort, wenn die unmittelbar eingeschlossene Methode oder der Lambda-Ausdruck, in dem sie angezeigt wird, über einen Iterator Modifizierer verfügt und wenn dies nach diesem YieldIterator Modifizierer angezeigt wird; sie wird an anderer Stelle nicht reserviert. Sie ist auch in Präprozessordirektiven nicht reserviert. Die Rendite-Anweisung ist nur im Textkörper einer Methode oder eines Lambda-Ausdrucks zulässig, in dem es sich um ein reserviertes Wort handelt. Innerhalb der unmittelbar eingeschlossenen Methode oder Lambda kann die Ertragsanweisung nicht innerhalb des Textkörpers eines Catch Blocks oder Finally Blocks oder innerhalb des Textkörpers einer SyncLock Anweisung auftreten.

Die Ertragsanweisung akzeptiert einen einzelnen Ausdruck, der als Wert klassifiziert werden muss und dessen Typ implizit in den Typ der aktuellen Iteratorvariable (Section Iterator Methods) seiner eingeschlossenen Iteratormethode umsetzbar ist.

Der Steuerungsfluss erreicht nur eine Yield Anweisung, wenn die MoveNext Methode für ein Iteratorobjekt aufgerufen wird. (Dies liegt daran, dass eine Iteratormethodeninstanz ihre Anweisungen nur aufgrund der MoveNext oder Dispose der Methoden ausführt, die für ein Iteratorobjekt aufgerufen werden; und die Dispose Methode führt nur Code in Finally Blöcken aus, sofern Yield nicht zulässig).

Wenn eine Yield Anweisung ausgeführt wird, wird der Ausdruck ausgewertet und in der aktuellen Iteratorvariablen der iterator-Methodeninstanz gespeichert, die diesem Iteratorobjekt zugeordnet ist. Der Wert True wird an den Aufrufer von MoveNext, und der Kontrollpunkt dieser Instanz wird angehalten, bis der nächste Aufruf des MoveNext Iteratorobjekts fortschreitet.