Control Flow

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and was checked on 2026-10-19. Every example is a complete console program that starts with Option Strict On, was built with warnings treated as errors and was run in the .NET 10 SDK container (Linux); the output shown is real output. The examples that are meant to fail say so, and show the compiler messages that the SDK printed. The project-level imports that the SDK adds (System, System.Collections.Generic, System.Linq and others) are why Console, List(Of T) and Dictionary(Of TKey, TValue) need no Imports line; System.IO is imported where a StringReader is used.

Visual Basic’s control statements are block statements closed by an explicit End (End If, End Select) or a Next or Loop keyword, so they need no braces and the closing keyword tells you which construct ends. Case does not matter, a statement ends at the end of the line unless it continues (see Program Structure and Code Conventions), and every condition must be a Boolean: If copies Then with an Integer does not compile under Option Strict On. The operators that build conditions (AndAlso, OrElse, Is Nothing, Like) are on Operators and Expressions. Procedures, which also transfer control (Return, Exit Sub), are on Procedures: Sub and Function; exception handling with Try is on Structured Exception Handling.

Construct Purpose Leave early with

If ... Then ... ElseIf ... Else ... End If

two-way and multi-way branching on Boolean conditions

-

Select Case ... Case ... Case Else ... End Select

multi-way branching on one value: constants, lists, ranges, comparisons

Exit Select

For ... To ... Step ... Next

counted loop

Exit For, Continue For

For Each ... In ... Next

loop over the elements of an array or collection

Exit For, Continue For

Do While or Do Until, then Loop

pre-test loop: test, then body

Exit Do, Continue Do

Do, then Loop While or Loop Until

post-test loop: body, then test

Exit Do, Continue Do

While ... End While

pre-test loop, the older spelling of Do While

Exit While, Continue While

GoTo label, Return, Exit Sub, Exit Function, Exit Property, Exit Try, Stop, End

unconditional transfers and termination

-

With ... End With, Using ... End Using, SyncLock ... End SyncLock

blocks that scope an object, a resource or a lock

-

If, ElseIf and Else

The block form is If condition Then, any number of ElseIf condition Then branches (written as one word), an optional Else and End If. Only the first branch whose condition is True runs. The conditions are evaluated in order and a later condition is evaluated only when every earlier one was False; inside one condition, AndAlso and OrElse short-circuit and And and Or do not.

The single-line form has no End If: If condition Then statement [Else statement]. Its details are the source of many bugs:

  • A colon joins statements, and every statement joined to the Then part belongs to it. If lent Then Console.Write("lent: ") : Console.WriteLine("yes") prints nothing at all when lent is False, because the second statement is part of the Then branch too. The same colon after Else belongs to the Else branch.

  • Else binds to the nearest If, as in the last example line.

  • A single-line If cannot contain a block; when the Then part grows, switch to the block form, which is also the form editors format correctly.

Option Strict On

Public Enum LoanState
    Active = 1
    Returned = 2
    Overdue = 3
End Enum

Module Program
    Private Function Describe(daysLate As Integer, state As LoanState) As String
        If state = LoanState.Returned Then
            Return "closed"
        ElseIf daysLate <= 0 Then
            Return "on time"
        ElseIf daysLate <= 7 Then
            Return "late by up to a week"
        Else
            Return "seriously late"
        End If
    End Function

    Sub Main()
        Console.WriteLine(Describe(0, LoanState.Active))
        Console.WriteLine(Describe(3, LoanState.Overdue))
        Console.WriteLine(Describe(30, LoanState.Overdue))
        Console.WriteLine(Describe(30, LoanState.Returned))

        ' Single-line If: the whole line is the statement
        Dim copies As Integer = 0
        If copies = 0 Then Console.WriteLine("no copies left") Else Console.WriteLine("copies available")

        ' A colon after Then keeps every statement in the Then part
        Dim lent As Boolean = False
        If lent Then Console.Write("lent: ") : Console.WriteLine("yes")
        Console.WriteLine("after the single-line If (lent = " & lent & ")")

        ' If with a nested single-line If / Else binds Else to the nearest If
        Dim big As Boolean = True, rare As Boolean = False
        If big Then If rare Then Console.WriteLine("big and rare") Else Console.WriteLine("big, not rare")
    End Sub
End Module
on time
late by up to a week
seriously late
closed
no copies left
after the single-line If (lent = False)
big, not rare

Points to take away:

  • An If whose branches only choose a value should use the If(condition, a, b) operator instead, which is an expression and evaluates only the branch it returns (see Operators and Expressions).

  • Because AndAlso short-circuits, the usual guard is If x IsNot Nothing AndAlso x.Length > 0 Then. With And the second operand is evaluated even when x is Nothing.

  • The #If directive is a different thing: it selects code at compile time (see Conditional Compilation and Directives).

Select Case

Select Case evaluates its selector expression once, then compares it with the Case clauses from the top. The first clause that matches runs, then control leaves the statement. There is no fall-through, no break, and no GoTo Case. A Case clause can contain a comma-separated list of any of these:

Form Example Matches

a value or expression

Case 5, Case "Fiction", Case LoanState.Active

equality (the selector and the value use the normal = rules, including Option Compare)

a list

Case 8, 9, 10

any of them

a range

Case 1 To 7, Case "a" To "f"

inclusive low-to-high range

a comparison

Case Is > 10, Case Is <= 0

the selector compared with the value; Is here is not the reference operator

Case Else

Case Else

anything not matched earlier; always last

Option Strict On

Public Enum LoanState
    Active = 1
    Returned = 2
    Overdue = 3
End Enum

Module Program
    Private evaluations As Integer

    Private Function Selector() As Integer
        evaluations += 1
        Return 12
    End Function

    Private Function Fine(daysLate As Integer) As Decimal
        Select Case daysLate
            Case Is <= 0
                Return 0D
            Case 1 To 7
                Return 0.5D * daysLate
            Case 8, 9, 10
                Return 5D
            Case Is > 10
                Return 10D
        End Select
        Return -1D        ' not reachable: every Integer is covered
    End Function

    Private Function Category(genre As String) As String
        Select Case genre
            Case "Fiction", "Poetry"
                Return "literature"
            Case "Science", "Maths"
                Return "science"
            Case "a" To "f"
                Return "early letters"
            Case Else
                Return "other"
        End Select
    End Function

    Sub Main()
        For Each days In {-2, 0, 3, 7, 9, 10, 11, 100}
            Console.WriteLine("days late " & days.ToString().PadLeft(4) & " -> fine " & Fine(days).ToString("0.00"))
        Next
        Console.WriteLine(Category("Poetry") & ", " & Category("Maths") & ", " & Category("c") & ", " & Category("Travel"))

        ' The selector is evaluated once, and the first matching Case wins
        Select Case Selector()
            Case 1 To 20
                Console.WriteLine("first Case matches")
            Case 12
                Console.WriteLine("never reached: the first match wins")
        End Select
        Console.WriteLine("selector evaluated " & evaluations & " time")

        ' Select Case True tests unrelated conditions in order
        Dim stock As Integer = 0, onLoan As Integer = 3
        Select Case True
            Case stock > 0
                Console.WriteLine("in stock")
            Case onLoan > 0
                Console.WriteLine("all copies on loan")
            Case Else
                Console.WriteLine("out of print")
        End Select

        ' No fall-through; Exit Select leaves early
        Select Case 5
            Case 5
                Console.WriteLine("five")
                If stock = 0 Then Exit Select
                Console.WriteLine("not printed")
        End Select
    End Sub
End Module
days late   -2 -> fine 0.00
days late    0 -> fine 0.00
days late    3 -> fine 1.50
days late    7 -> fine 3.50
days late    9 -> fine 5.00
days late   10 -> fine 5.00
days late   11 -> fine 10.00
days late  100 -> fine 10.00
literature, science, early letters, other
first Case matches
selector evaluated 1 time
all copies on loan
five
  • The Fine function above covers every Integer with its Case clauses, but the compiler cannot prove it, so a Return after End Select is still required: without it the build reports BC42353 (Function 'Fine' doesn't return a value on all code paths; checked with a two-Case function). A Case Else that throws or returns an error value is the standard way to make the unexpected case visible.

  • The selector is evaluated once (evaluated 1 time), which matters for calls with side effects.

  • Select Case True followed by Case clauses with Boolean expressions is the idiom for a chain of unrelated conditions, a tidier alternative to a long ElseIf ladder. The first True clause wins.

  • String selectors follow Option Compare: under Option Compare Text the clause Case "science" also matches "SCIENCE" (see Option Statements). A string range such as "a" To "f" compares with the same rule, which is rarely what the reader assumes.

  • The selector type can be any type for which the comparison works: numbers, String, Char, Date, Boolean, enumerations. Over an enumeration, cover every member and add a Case Else, because any integer can be converted to the enumeration (see Enumerations).

  • There is no pattern matching on types or shapes, as in C# switch. For a type test use TypeOf x Is T in an If, or Select Case True with TypeOf clauses, as below.

  • Exit Select leaves the statement early, as in the last example, but there is rarely a reason for it: restructure with If.

Option Strict On

Module Program
    Private Function Kind(value As Object) As String
        Select Case True
            Case TypeOf value Is String
                Return "text"
            Case TypeOf value Is Integer
                Return "number"
            Case value Is Nothing
                Return "nothing"
            Case Else
                Return "other"
        End Select
    End Function

    Sub Main()
        Console.WriteLine(Kind("Dune") & " " & Kind(42) & " " & Kind(Nothing) & " " & Kind(4.5))
    End Sub
End Module
text number nothing other

For and Next

For counter [As type] = start To limit [Step increment] runs its body from start to limit inclusive. These rules differ from the C# for:

  • The start, the limit and the step are evaluated once, before the first pass. Changing the limit variable inside the loop does not change the number of passes; the C# for evaluates its condition on every pass.

  • The control variable takes the type of the start, limit and step expressions when it is declared in the For (For x = 0 To 1 Step 0.25 makes x a Double), or the type that you give with As. With Option Strict On an explicit As is better for anything but Integer.

  • When the start is already past the limit (in the direction of the step) the body never runs.

  • Step can be negative; a Step of zero is a loop that never ends, so guard a step that comes from data (the last example does).

  • A control variable that is declared in the For statement is scoped to the loop, and one that already exists keeps its last value afterwards (i is 4 after For i = 1 To 3).

  • You may assign to the control variable inside the body, and the loop continues from the new value, as the example shows. Avoid it: it makes the number of passes hard to see.

  • The upper bound is inclusive, so iterating an array of n elements is For i As Integer = 0 To array.Length - 1. Prefer For Each when you do not need the index.

  • A single Next j, i closes two nested loops. It compiles (checked with a nested pair); the names after Next are optional.

Option Strict On

Module Program
    Sub Main()
        ' The bounds and the Step are evaluated once, before the first pass
        Dim limit As Integer = 3
        For i = 1 To limit
            limit = 10                                   ' has no effect on this loop
            Console.Write(i & " ")
        Next
        Console.WriteLine("(limit is now " & limit & ")")

        ' Step, negative Step, and a loop that does not run at all
        For i = 10 To 0 Step -5
            Console.Write(i & " ")
        Next
        Console.WriteLine()
        For i = 5 To 1
            Console.Write("never ")
        Next
        Console.WriteLine("(5 To 1 runs zero times)")

        ' The control variable takes the type of its start, limit and step
        For x = 0 To 1 Step 0.25
            Console.Write(TypeName(x) & ":" & x & " ")
        Next
        Console.WriteLine()

        ' Floating-point steps accumulate error; prefer an integer counter
        Dim count As Integer = 0
        For d As Double = 0 To 0.3 Step 0.1
            count += 1
            Console.Write(d.ToString("R") & " ")
        Next
        Console.WriteLine("-> 0 To 0.3 Step 0.1 ran " & count & " times, not 4")

        ' A loop that ends at the maximum of its type: the final increment overflows
        Try
            For b As Byte = 250 To 255
                Console.Write(b & " ")
            Next
            Console.WriteLine("(Byte loop ended)")
        Catch ex As OverflowException
            Console.WriteLine("-> OverflowException after the last pass")
        End Try

        ' The control variable may be changed inside the loop, and keeps its last value
        For i As Integer = 1 To 10
            If i = 3 Then i = 8
            Console.Write(i & " ")
        Next
        Console.WriteLine()

        ' The upper bound is inclusive: iterate an array with Length - 1
        Dim titles As String() = {"Dune", "Emma", "Ubik"}
        For i As Integer = 0 To titles.Length - 1
            Console.Write(titles(i) & " ")
        Next
        Console.WriteLine()
    End Sub
End Module
1 2 3 (limit is now 10)
10 5 0
(5 To 1 runs zero times)
Double:0 Double:0.25 Double:0.5 Double:0.75 Double:1
0 0.1 0.2 -> 0 To 0.3 Step 0.1 ran 3 times, not 4
250 251 252 253 254 255 -> OverflowException after the last pass
1 2 8 9 10
Dune Emma Ubik

Two results deserve attention. The floating-point loop ran three times, not four: 0.1 + 0.1 + 0.1 is slightly larger than 0.3, and a Double counter accumulates the error. Use an integer counter and compute the value (i * 0.1). And the Byte loop that ends at 255 threw OverflowException after printing 255: the final increment overflows because integer overflow checking is on by default (see Operators and Expressions). The C# for loop over the same range would never end, so neither language is safe; write the loop with an Integer counter and convert inside the body.

For Each

For Each element In collection visits the elements of an array or of any object that implements IEnumerable (or has the enumerator pattern). The element variable takes the collection’s element type; declare it with As for Char in a String and for a Dictionary pair when you want the type visible. The visited object is evaluated once.

Option Strict On

Module Program
    Sub Main()
        Dim titles As New List(Of String) From {"Dune", "Emma", "Ubik"}

        For Each title In titles
            Console.Write(title.ToUpperInvariant() & " ")
        Next
        Console.WriteLine()

        ' Characters of a string, and key/value pairs
        For Each letter As Char In "Dune"
            Console.Write(letter & "-")
        Next
        Console.WriteLine()
        Dim stock As New Dictionary(Of String, Integer) From {{"Dune", 3}, {"Emma", 0}}
        For Each pair In stock
            Console.Write(pair.Key & "=" & pair.Value & " ")
        Next
        Console.WriteLine()

        ' Assigning to the iteration variable does not change the array
        Dim numbers As Integer() = {1, 2, 3}
        For Each n In numbers
            Dim doubled As Integer = n * 2
            Console.Write(doubled & " ")
        Next
        Console.WriteLine("-> array unchanged: " & String.Join(",", numbers))

        ' Changing the collection while it is enumerated
        Try
            For Each title In titles
                If title = "Emma" Then titles.Remove(title)
            Next
        Catch ex As InvalidOperationException
            Console.WriteLine("InvalidOperationException: " & ex.Message)
        End Try

        ' A Nothing collection
        Dim missing As List(Of String) = Nothing
        Try
            For Each title In missing
            Next
        Catch ex As NullReferenceException
            Console.WriteLine("NullReferenceException for Nothing")
        End Try
        For Each title In If(missing, New List(Of String)())
        Next
        Console.WriteLine("guarded with If(missing, New List(Of String)())")
    End Sub
End Module
DUNE EMMA UBIK
D-u-n-e-
Dune=3 Emma=0
2 4 6 -> array unchanged: 1,2,3
InvalidOperationException: Collection was modified; enumeration operation may not execute.
NullReferenceException for Nothing
guarded with If(missing, New List(Of String)())
  • The iteration variable is a copy of the element for value types, so assigning to it never changes the array.

  • Adding or removing elements of a List(Of T) or a Dictionary while a For Each enumerates it throws InvalidOperationException. To remove items, loop backwards with a For, iterate over a copy (titles.ToList()), or call RemoveAll.

  • A Nothing collection throws NullReferenceException; guard with If(collection, New List(Of String)()) or a test.

  • For Each over a type that is not enumerable is a compile error under Option Strict On; over an Object variable it is late bound and only works with Option Strict Off.

  • Each iteration of a For Each gets its own variable, which is what closures capture (see Variables, Constants and Scope); a For loop does not.

  • For Each accepts Exit For and Continue For, and over a Dictionary the pairs come in no promised order.

  • Iterating a sequence that is produced lazily (a LINQ query, an Iterator function) runs the producer during the loop, see Iterators and Yield.

Do and While loops: pre-test and post-test

Four forms of the Do loop and the older While loop differ in two things: where the condition is tested and whether the loop repeats while the condition is True (While) or until it becomes True (Until).

Form Test Runs the body at least once? Repeats while

Do While condition ... Loop

before the body (pre-test)

no

the condition is True

Do Until condition ... Loop

before the body (pre-test)

no

the condition is False

Do ... Loop While condition

after the body (post-test)

yes

the condition is True

Do ... Loop Until condition

after the body (post-test)

yes

the condition is False

While condition ... End While

before the body (pre-test)

no

the condition is True

Do ... Loop (no condition)

never; leave with Exit Do

yes

forever

The classic mistake is to treat Do While ... Loop as a post-test loop. It is pre-test: the condition is evaluated first, and the body can run zero times. The post-test form puts the While or Until after Loop. The diagrams show the two control flows.

flowchart TD A(["Enter the loop"]) --> B{"Condition true?"} B -->|"True"| C["Run the body"] C --> B B -->|"False"| D(["Continue after Loop"])

The diagram above is Do While condition ... Loop (and While condition ... End While): for Do Until read the labels as "False" and "True" the other way round. The next diagram is Do ... Loop While condition:

flowchart TD A(["Enter the loop"]) --> C["Run the body"] C --> B{"Condition true?"} B -->|"True"| C B -->|"False"| D(["Continue after Loop"])

For Do ... Loop Until condition the arrow back to the body is taken when the condition is False.

Option Strict On

Module Program
    Sub Main()
        Dim n As Integer

        n = 5
        Dim passes As Integer = 0
        Do While n < 3                   ' pre-test: false at once
            passes += 1
        Loop
        Console.WriteLine("Do While ... Loop       : body ran " & passes & " time(s)")

        passes = 0
        Do                               ' post-test: the body runs once before the test
            passes += 1
        Loop While n < 3
        Console.WriteLine("Do ... Loop While       : body ran " & passes & " time(s)")

        passes = 0
        Do Until n >= 3
            passes += 1
        Loop
        Console.WriteLine("Do Until ... Loop       : body ran " & passes & " time(s)")

        passes = 0
        Do
            passes += 1
        Loop Until n >= 3
        Console.WriteLine("Do ... Loop Until       : body ran " & passes & " time(s)")

        passes = 0
        While n < 3
            passes += 1
        End While
        Console.WriteLine("While ... End While     : body ran " & passes & " time(s)")

        ' Counting down with both forms
        n = 3
        Do While n > 0
            Console.Write(n & " ")
            n -= 1
        Loop
        Console.WriteLine("liftoff")
        n = 0
        Do
            Console.Write(n & " ")
            n += 1
        Loop While n < 3
        Console.WriteLine("done")
    End Sub
End Module
Do While ... Loop       : body ran 0 time(s)
Do ... Loop While       : body ran 1 time(s)
Do Until ... Loop       : body ran 0 time(s)
Do ... Loop Until       : body ran 1 time(s)
While ... End While     : body ran 0 time(s)
3 2 1 liftoff
0 1 2 done

With the condition false from the start, the pre-test forms ran their body zero times and the post-test forms exactly once. Use the post-test form when the body must run before the condition can be known (read a line, then check it); use the pre-test form otherwise. Do While and While ... End While are equivalent; While has its own Exit While and Continue While, and the VB6 keyword Wend is rejected by the compiler (BC30809, 'Wend' statements are no longer supported; use 'End While' statements instead).

A loop that validates input is the natural use of the post-test form, and a queue drained with a pre-test loop is the natural use of While:

Option Strict On

Module Program
    Sub Main()
        Dim values As String() = {"12", "x", "40", "", "7"}
        Dim index As Integer = 0
        Dim total As Integer = 0
        Dim number As Integer
        ' Post-test loop: read, then validate, then repeat while the input is wrong
        Do
            Dim text As String = values(index)
            index += 1
            If Integer.TryParse(text, number) Then
                total += number
                Console.WriteLine("accepted " & number)
            Else
                Console.WriteLine("rejected """ & text & """")
            End If
        Loop Until index = values.Length
        Console.WriteLine("total " & total)

        Dim queue As New Queue(Of Integer)({1, 2, 3})
        While queue.Count > 0
            Console.Write(queue.Dequeue() & " ")
        End While
        Console.WriteLine()
    End Sub
End Module
accepted 12
rejected "x"
accepted 40
rejected ""
accepted 7
total 59
1 2 3
flowchart TD S["Which loop?"] --> Q1{"Do you know the number of passes?"} Q1 -->|"yes, a count or a range"| F["For ... Next"] Q1 -->|"no"| Q2{"Do you visit every element of a collection?"} Q2 -->|"yes"| E["For Each ... Next"] Q2 -->|"no"| Q3{"Must the body run at least once?"} Q3 -->|"yes"| P["Do ... Loop While or Until"] Q3 -->|"no"| W["Do While or Until ... Loop, or While ... End While"]

Exit and Continue

Exit leaves the innermost enclosing statement of the named kind; Continue skips to the next iteration of the innermost enclosing loop of the named kind. Because the kind is named, a Do loop can be left from inside a For that it contains.

Statement Effect

Exit For, Exit Do, Exit While

leaves the innermost enclosing For or For Each, Do, or While

Exit Select

leaves the enclosing Select Case

Exit Sub, Exit Function, Exit Property

returns from the procedure (a function returns its current return value; prefer Return value)

Exit Try

leaves a Try or Catch block; a Finally block still runs

Continue For, Continue Do, Continue While

starts the next pass of the innermost enclosing loop of that kind

Return

returns from a procedure, optionally with a value

Option Strict On

Module Program
    Private Function FirstOverdue(titles As String(), overdue As Boolean()) As String
        For i As Integer = 0 To titles.Length - 1
            If overdue(i) Then Return titles(i)         ' leaves the loop and the Function
        Next
        Return "(none)"
    End Function

    Sub Main()
        ' Continue For skips the rest of the body
        For i As Integer = 1 To 6
            If i Mod 2 = 0 Then Continue For
            Console.Write(i & " ")
        Next
        Console.WriteLine("(odd numbers)")

        ' Exit For leaves only the innermost For
        For shelf As Integer = 1 To 3
            For slot As Integer = 1 To 3
                If slot = 2 Then Exit For
                Console.Write(shelf & "." & slot & " ")
            Next
        Next
        Console.WriteLine("(inner loop exited at slot 2)")

        ' Exit Do leaves an enclosing Do from inside a For
        Dim row As Integer = 0
        Do
            row += 1
            For column As Integer = 1 To 3
                If row * column = 4 Then Exit Do
                Console.Write(row * column & " ")
            Next
        Loop While row < 5
        Console.WriteLine("(Exit Do left both loops)")

        ' Continue While and Exit While
        Dim n As Integer = 0
        While True
            n += 1
            If n < 3 Then Continue While
            If n > 5 Then Exit While
            Console.Write(n & " ")
        End While
        Console.WriteLine("(3 to 5)")

        Console.WriteLine(FirstOverdue({"Dune", "Emma", "Ubik"}, {False, True, True}))
        Console.WriteLine(FirstOverdue({"Dune"}, {False}))

        ' Exit Try leaves a Try block; Finally still runs
        Try
            Console.Write("in Try ")
            Exit Try
            Console.Write("not printed ")
        Finally
            Console.WriteLine("Finally runs")
        End Try
    End Sub
End Module
1 3 5 (odd numbers)
1.1 2.1 3.1 (inner loop exited at slot 2)
1 2 3 2 (Exit Do left both loops)
3 4 5 (3 to 5)
Emma
(none)
in Try Finally runs

Source: Exit Statement

Using the wrong kind, or using the statement outside any block of that kind, is a compile-time error:

Option Strict On

Module Program
    Sub Main()
        Dim copies As Integer = 2
        If copies Then
            Console.WriteLine("copies")
        End If
        Exit For
        For i As Integer = 1 To 3
            Continue While
        Next
        Do
            Exit While
        Loop While copies > 5
        Select Case copies
            Case "two"
        End Select
    End Sub
End Module
Program.vb(6,12): error BC30512: Option Strict On disallows implicit conversions from 'Integer' to 'Boolean'.
Program.vb(9,9): error BC30096: 'Exit For' can only appear inside a 'For' statement.
Program.vb(11,13): error BC30784: 'Continue While' can only appear inside a 'While' statement.
Program.vb(14,13): error BC30097: 'Exit While' can only appear inside a 'While' statement.
Program.vb(17,18): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Integer'.

Three of the five errors are the conditions of this page: Exit For and Continue While and Exit While name a construct that does not enclose them (BC30096, BC30784, BC30097), the conditional of If copies Then needs a Boolean, and Case "two" cannot be compared with an Integer selector.

GoTo and labels

GoTo label jumps to a label in the same procedure. A label is a name or a number at the start of a line followed by a colon. The Learn page notes that GoTo can make code hard to read, and structured statements cover nearly every use. Two rules limit it: it cannot enter a block of a For, For Each (and similar statements) from outside, and it cannot leave a procedure.

Option Strict On

Module Program
    Private attempts As Integer

    Private Function TryLend() As Boolean
        attempts += 1
        Return attempts = 3
    End Function

    Sub Main()
        Dim limit As Integer = 5

Retry:
        If Not TryLend() AndAlso attempts < limit Then
            Console.WriteLine("attempt " & attempts & " failed, retrying")
            GoTo Retry
        End If
        Console.WriteLine("finished after " & attempts & " attempts")

        ' The same without GoTo
        attempts = 0
        Do While Not TryLend() AndAlso attempts < limit
            Console.WriteLine("attempt " & attempts & " failed, retrying")
        Loop
        Console.WriteLine("finished after " & attempts & " attempts")
    End Sub
End Module
attempt 1 failed, retrying
attempt 2 failed, retrying
finished after 3 attempts
attempt 1 failed, retrying
attempt 2 failed, retrying
finished after 3 attempts

Source: GoTo Statement

The jump into a block is rejected at compile time:

Option Strict On

Module Program
    Sub Main()
        GoTo Inside
        For i As Integer = 1 To 3
Inside:
            Console.WriteLine(i)
        Next
    End Sub
End Module
Program.vb(5,14): error BC30757: 'GoTo Inside' is not valid because 'Inside' is inside a 'For' or 'For Each' statement that does not contain this statement.

The retry pattern is the main use that remains (labels and jumps are also the machinery behind On Error handlers; see Unstructured Error Handling: On Error), and the second half of the example shows that a loop expresses the same thing without a label. Prefer the loop.

Return, End and Stop

Return ends a Sub or Function (and runs enclosing Finally blocks, as the example shows). End is different: it terminates the whole process at once. As the Learn page says, it does not call Dispose or Finalize and does not run Finally blocks. The program below ends inside a Try and its Finally never prints:

Option Strict On

Module Program
    Sub Main()
        Try
            Console.WriteLine("in Try")
            End
        Finally
            Console.WriteLine("Finally: never printed after End")
        End Try
    End Sub
End Module
in Try

Source: End Statement

Option Strict On

Module Program
    Private Sub Cleanup(message As String)
        Console.WriteLine("Finally: " & message)
    End Sub

    Sub Main()
        Try
            Console.WriteLine("in Try")
            Return
        Finally
            Cleanup("Return runs Finally")
        End Try
    End Sub
End Module
in Try
Finally: Return runs Finally

Never use End to leave a program: return from Main (or call Environment.Exit when you really must, which is the same abrupt stop with an exit code). The Stop statement is a built-in breakpoint:

Option Strict On

Module Program
    Sub Main()
        Console.WriteLine("before Stop")
        Stop
        Console.WriteLine("after Stop (no debugger attached)")
    End Sub
End Module
before Stop
after Stop (no debugger attached)

Source: Stop Statement

The Learn page says that Stop outside the IDE invokes the debugger. On the Linux container used here, without a debugger attached, it did nothing: execution continued. Use Debugger.Break() or a real breakpoint, and do not leave Stop statements in code.

With, Using and SyncLock

With object ... End With evaluates the object expression once and lets the block refer to its members with a leading dot. It saves repetition in object initialization, and in code that builds on a long path such as settings.Library.Catalog. A leading dot always means the innermost With. Over a structure variable it works on the variable itself; over a returned structure value the compiler rejects assignment to a member (BC30068, Expression is a value and therefore cannot be the target of an assignment).

Option Strict On

Public Class Book
    Public Property Title As String = ""
    Public Property Pages As Integer
    Public Property Copies As Integer
End Class

Public Structure Point
    Public X As Integer
    Public Y As Integer
End Structure

Module Program
    Sub Main()
        Dim book As New Book()
        With book
            .Title = "Dune"
            .Pages = 412
            .Copies = 3
            Console.WriteLine(.Title & ": " & .Pages & " pages, " & .Copies & " copies")
        End With

        ' The expression is evaluated once, even when it is a call
        With New Book With {.Title = "Emma"}
            .Copies += 1
            Console.WriteLine(.Title & ": " & .Copies)
        End With

        ' With over a structure variable changes the variable itself
        Dim p As New Point()
        With p
            .X = 10
            .Y = 20
        End With
        Console.WriteLine("variable: X = " & p.X & ", Y = " & p.Y)

        ' Nested With: a leading dot always refers to the innermost one
        With book
            Dim inner As New Book With {.Title = "Ubik"}
            With inner
                Console.WriteLine(.Title)
            End With
            Console.WriteLine(.Title)
        End With
    End Sub
End Module
Dune: 412 pages, 3 copies
Emma: 1
variable: X = 10, Y = 20
Ubik
Dune

Using disposes a resource when the block ends (see Object Lifetime and Disposal), SyncLock holds a monitor (see Threads, Parallelism and Synchronization), and Try is on the exception page. Exit Try leaves a Try block; Using and SyncLock end at their End statement or when an exception leaves them.

A sentinel loop over the shelf

The pieces combine in a command loop that reads lines until a sentinel (quit) or the end of the input. The input is a StringReader so that the program is self-contained; a console program would read Console.ReadLine() in the same loop.

Option Strict On

Imports System.IO

Public Enum LoanState
    Active = 1
    Returned = 2
    Overdue = 3
End Enum

Module Program
    Sub Main()
        Dim script As String = "lend 9780441172719" & vbLf &
                               "lend 9780441172719" & vbLf &
                               "return 9780441172719" & vbLf &
                               "status 9780441172719" & vbLf &
                               "unknown" & vbLf &
                               "" & vbLf &
                               "quit" & vbLf &
                               "lend 0000000000000"
        Dim loans As New Dictionary(Of String, LoanState)()

        Using input As New StringReader(script)
            Dim line As String = input.ReadLine()
            Do While line IsNot Nothing AndAlso line <> "quit"           ' sentinel loop
                If line.Length = 0 Then
                    line = input.ReadLine()
                    Continue Do                                             ' skip blank lines
                End If

                Dim parts As String() = line.Split(" "c)
                Select Case parts(0)
                    Case "lend"
                        If loans.ContainsKey(parts(1)) AndAlso loans(parts(1)) = LoanState.Active Then
                            Console.WriteLine("already on loan: " & parts(1))
                        Else
                            loans(parts(1)) = LoanState.Active
                            Console.WriteLine("lent " & parts(1))
                        End If
                    Case "return"
                        loans(parts(1)) = LoanState.Returned
                        Console.WriteLine("returned " & parts(1))
                    Case "status"
                        Dim state As LoanState
                        If loans.TryGetValue(parts(1), state) Then
                            Console.WriteLine(parts(1) & " is " & state.ToString())
                        Else
                            Console.WriteLine(parts(1) & " is unknown")
                        End If
                    Case Else
                        Console.WriteLine("unknown command: " & parts(0))
                End Select
                line = input.ReadLine()
            Loop
        End Using
        Console.WriteLine("stopped at the sentinel; loans: " & loans.Count)
    End Sub
End Module
lent 9780441172719
already on loan: 9780441172719
returned 9780441172719
9780441172719 is Returned
unknown command: unknown
stopped at the sentinel; loans: 1

Points of design in the loop: the condition tests for the end of input (Nothing) before the sentinel, with AndAlso; the read happens once at the top and once at the bottom so that the pre-test condition can see the next line; Continue Do skips blank lines (the read must come before it, or the loop would never end); and a Select Case dispatches the command. The last line, after quit, was never read.

Differences from C#

Topic Visual Basic C#

Block end

End If, End Select, Next, Loop, End While

braces

Condition type

Boolean only under Option Strict On

bool only

switch

Select Case: ranges, lists and Is comparisons, no fall-through, no break

switch: constant patterns, type and relational patterns, break or goto case

for bounds

evaluated once

the condition is re-evaluated on each pass

Loop variable captured by a lambda

one variable for a For, one per pass for a For Each

one per pass for foreach, one for for

Post-test loop

Do ... Loop While condition

do ... while (condition);

Leave a loop

Exit For, Exit Do, Exit While, named by the kind of loop

break

Next iteration

Continue For, Continue Do, Continue While

continue

goto case

none

goto case

Leave the process

End (avoid)

Environment.Exit

Locals declared in a loop body

kept between passes, unless initialized

new on each pass

Older material you may meet

Tutorials written for VB6, VBA and VBScript, and books derived from them, describe a few things that are different or wrong in Visual Basic .NET:

  • Do While ... Loop is sometimes described as a loop that tests after the body. It tests before; the post-test form is Do ... Loop While.

  • Wend closes a While loop in VB6; use End While. VB6 GoSub, On ... GoTo and On Error GoTo structures are not part of structured code (the last one is covered on the unstructured error handling page).

  • Loop and conditional code that declares totals inside the loop body, without an initializer, accumulates between passes (see Variables, Constants and Scope).

  • There is no Do Select construct: the loop is Do While or Do Until, and the branch inside it is a Select Case.

  • When a sample’s comment and its output disagree (for example a Continue For that skips even numbers prints the odd ones), run it; the output is the specification.

  • If ... Then with a numeric value as the condition (If n Then, true for any non-zero value) was allowed in VB6 and in VB.NET with Option Strict Off; under Option Strict On write If n <> 0 Then.

  • IIf is a function that evaluates both branches; the If(c, a, b) operator has been in the language since Visual Basic 9.

Errors the compiler reports

Code Cause

BC30512

a non-Boolean condition, or a Case value that cannot be converted to the selector type, under Option Strict On

BC30096, BC30097

Exit For or Exit While outside a loop of that kind

BC30784

Continue While outside a While loop (and BC30783 and similar for the other kinds)

BC30757

GoTo into a For or For Each block

BC30809

the VB6 keyword Wend

BC30068

assignment to a member inside With over a returned value