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 |
|---|---|---|
|
two-way and multi-way branching on |
- |
|
multi-way branching on one value: constants, lists, ranges, comparisons |
|
|
counted loop |
|
|
loop over the elements of an array or collection |
|
|
pre-test loop: test, then body |
|
|
post-test loop: body, then test |
|
|
pre-test loop, the older spelling of |
|
|
unconditional transfers and termination |
- |
|
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
Thenpart belongs to it.If lent Then Console.Write("lent: ") : Console.WriteLine("yes")prints nothing at all whenlentisFalse, because the second statement is part of theThenbranch too. The same colon afterElsebelongs to theElsebranch. -
Elsebinds to the nearestIf, as in the last example line. -
A single-line
Ifcannot contain a block; when theThenpart 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
Source: If Then Else Statement
Points to take away:
-
An
Ifwhose branches only choose a value should use theIf(condition, a, b)operator instead, which is an expression and evaluates only the branch it returns (see Operators and Expressions). -
Because
AndAlsoshort-circuits, the usual guard isIf x IsNot Nothing AndAlso x.Length > 0 Then. WithAndthe second operand is evaluated even whenxisNothing. -
The
#Ifdirective 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 |
|
equality (the selector and the value use the normal |
a list |
|
any of them |
a range |
|
inclusive low-to-high range |
a comparison |
|
the selector compared with the value; |
|
|
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
Source: Select Case Statement
-
The
Finefunction above covers everyIntegerwith itsCaseclauses, but the compiler cannot prove it, so aReturnafterEnd Selectis still required: without it the build reports BC42353 (Function 'Fine' doesn't return a value on all code paths; checked with a two-Casefunction). ACase Elsethat 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 Truefollowed byCaseclauses with Boolean expressions is the idiom for a chain of unrelated conditions, a tidier alternative to a longElseIfladder. The firstTrueclause wins. -
String selectors follow
Option Compare: underOption Compare Textthe clauseCase "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 aCase 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 useTypeOf x Is Tin anIf, orSelect Case TruewithTypeOfclauses, as below. -
Exit Selectleaves the statement early, as in the last example, but there is rarely a reason for it: restructure withIf.
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#
forevaluates 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.25makesxaDouble), or the type that you give withAs. WithOption Strict Onan explicitAsis better for anything butInteger. -
When the start is already past the limit (in the direction of the step) the body never runs.
-
Stepcan be negative; aStepof 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
Forstatement is scoped to the loop, and one that already exists keeps its last value afterwards (iis4afterFor 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
nelements isFor i As Integer = 0 To array.Length - 1. PreferFor Eachwhen you do not need the index. -
A single
Next j, icloses two nested loops. It compiles (checked with a nested pair); the names afterNextare 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
Source: For Next Statement
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)())
Source: For Each Next Statement
-
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 aDictionarywhile aFor Eachenumerates it throwsInvalidOperationException. To remove items, loop backwards with aFor, iterate over a copy (titles.ToList()), or callRemoveAll. -
A
Nothingcollection throwsNullReferenceException; guard withIf(collection, New List(Of String)())or a test. -
For Eachover a type that is not enumerable is a compile error underOption Strict On; over anObjectvariable it is late bound and only works withOption Strict Off. -
Each iteration of a
For Eachgets its own variable, which is what closures capture (see Variables, Constants and Scope); aForloop does not. -
For EachacceptsExit ForandContinue For, and over aDictionarythe pairs come in no promised order. -
Iterating a sequence that is produced lazily (a LINQ query, an
Iteratorfunction) 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 |
|---|---|---|---|
|
before the body (pre-test) |
no |
the condition is |
|
before the body (pre-test) |
no |
the condition is |
|
after the body (post-test) |
yes |
the condition is |
|
after the body (post-test) |
yes |
the condition is |
|
before the body (pre-test) |
no |
the condition is |
|
never; leave with |
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.
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:
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
Source: Do Loop Statement
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
Source: While End While Statement
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 |
|---|---|
|
leaves the innermost enclosing |
|
leaves the enclosing |
|
returns from the procedure (a function returns its current return value; prefer |
|
leaves a |
|
starts the next pass of the innermost enclosing loop of that kind |
|
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'.
Source: Continue Statement
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
Source: With End With Statement
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
Source: StringReader Class
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 |
|
braces |
Condition type |
|
|
|
|
|
|
evaluated once |
the condition is re-evaluated on each pass |
Loop variable captured by a lambda |
one variable for a |
one per pass for |
Post-test loop |
|
|
Leave a loop |
|
|
Next iteration |
|
|
|
none |
|
Leave the process |
|
|
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 ... Loopis sometimes described as a loop that tests after the body. It tests before; the post-test form isDo ... Loop While. -
Wendcloses aWhileloop in VB6; useEnd While. VB6GoSub,On ... GoToandOn Error GoTostructures 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 Selectconstruct: the loop isDo WhileorDo Until, and the branch inside it is aSelect Case. -
When a sample’s comment and its output disagree (for example a
Continue Forthat skips even numbers prints the odd ones), run it; the output is the specification. -
If ... Thenwith a numeric value as the condition (If n Then, true for any non-zero value) was allowed in VB6 and in VB.NET withOption Strict Off; underOption Strict OnwriteIf n <> 0 Then. -
IIfis a function that evaluates both branches; theIf(c, a, b)operator has been in the language since Visual Basic 9.
Errors the compiler reports
| Code | Cause |
|---|---|
BC30512 |
a non- |
BC30096, BC30097 |
|
BC30784 |
|
BC30757 |
|
BC30809 |
the VB6 keyword |
BC30068 |
assignment to a member inside |
References
-
Control Flow (programming guide) and Statements (language reference)
-
If Then Else, Select Case, For Next, For Each Next, Do Loop and While End While
-
With End With, Using and SyncLock