Conditions and Loops
Before you start
You need: VB.NET declarations and operators (Article 01).
Time: about 40 minutes, plus the practice.
Learning objective
Follow the control flow of an existing VB.NET procedure and predict which branch runs and how many times a loop executes.
Topics
If,ElseIf,Else, and single-lineIfSelect Case, including ranges and comma listsFor,For Each,Do, andWhileExitandContinueAndversusAndAlso,OrversusOrElse- The
If()operator andIIf() - Off-by-one traps in legacy loops
If and ElseIf
If percentage >= 90 Then
grade = "A+"
ElseIf percentage >= 80 Then
grade = "A"
ElseIf percentage >= 70 Then
grade = "B"
Else
grade = "C"
End If
Then is required. End If closes the block. There is no C#-style brace, so indentation is the only visual guide — misindented legacy code is genuinely hard to read, and reformatting it is a reasonable first step before you change anything.
A single-line form exists and needs no End If:
If marks < 0 Then Throw New ArgumentException("Marks cannot be negative.")
This is fine for a guard clause. It becomes a problem when someone appends a second statement:
' Both statements run only when the condition is true — but this reads badly
If isAbsent Then marks = 0 : remarks = "Absent"
The colon is a statement separator. Rewrite it as a block.
Short-circuit evaluation
This is the distinction that causes real null-reference crashes.
' And evaluates BOTH sides — throws when student is Nothing
If student IsNot Nothing And student.Status = StudentStatus.Active Then
' AndAlso stops at the first False — safe
If student IsNot Nothing AndAlso student.Status = StudentStatus.Active Then
And and Or are bitwise/logical operators that always evaluate both operands. AndAlso and OrElse short-circuit, matching C#'s && and ||.
Use AndAlso and OrElse by default. When you find And or Or in a condition that dereferences an object, you have very likely found a latent bug — verify before changing it, because some code relies on the right-hand side running for its side effects.
Select Case
More capable than C#'s switch. It accepts ranges, comparisons, and comma-separated lists.
Select Case examType
Case ExamType.UnitTest, ExamType.Assignment
weight = 0.2D
Case ExamType.MidTerm
weight = 0.3D
Case ExamType.Final, ExamType.Practical
weight = 0.5D
Case Else
Throw New ArgumentOutOfRangeException(NameOf(examType))
End Select
Ranges and comparisons:
Select Case percentage
Case Is >= 90
grade = "A+"
Case 80 To 89
grade = "A"
Case 70 To 79
grade = "B"
Case Else
grade = "C"
End Select
There is no fall-through, so no break is needed. Cases are tested in order and the first match wins — with overlapping ranges, order decides the result.
Always include Case Else. A Select Case on an enum with no Case Else silently does nothing when a new enum member is added later.
For
For index As Integer = 0 To students.Count - 1
Console.WriteLine(students(index).Name)
Next
To is inclusive on both ends. 0 To 5 runs six times. This is the most common conversion error between C# and VB.NET:
// C# — runs 5 times, index 0..4
for (int index = 0; index < 5; index++)
' VB.NET equivalent — note the 4, not 5
For index As Integer = 0 To 4
Counting down or by steps:
For index As Integer = students.Count - 1 To 0 Step -1
' safe when removing items while iterating
Next
For marks As Integer = 0 To 100 Step 10
Next
Array bounds use UBound in older code, which returns the highest index, not the count:
For index As Integer = 0 To UBound(rollNumbers)
Next
For Each
Preferred when the index is not needed.
For Each student As Student In activeStudents
total += student.MarksObtained
Next
Declare the type in the loop header. Legacy code often writes For Each student In activeStudents and relies on Option Infer or, worse, late binding under Option Strict Off.
You cannot modify the collection while iterating it. Removing an item inside a For Each throws InvalidOperationException; use a downward For loop or build a second list.
Do and While
' Test before — may run zero times
Do While reader.Read()
LoadRow(reader)
Loop
' Test after — always runs at least once
Do
attempts += 1
success = TryConnect()
Loop Until success OrElse attempts >= 3
' Equivalent to Do While, older style
While reader.Read()
LoadRow(reader)
End While
Until inverts the condition, which reads well but trips people converting to C#: Loop Until success is while (!success).
Exit and Continue
For Each student As Student In students
If student.Status <> StudentStatus.Active Then
Continue For
End If
If student.RollNumber = target Then
found = student
Exit For
End If
Next
Exit For, Exit Do, Exit While, Exit Sub, Exit Function, and Exit Try each name what they leave. Continue For and Continue Do skip to the next iteration.
If() and IIf()
Two similar-looking things that behave differently.
' If() operator — short-circuits, evaluates only the needed branch
Dim safeDisplay As String = If(student Is Nothing, "Unknown", student.Name)
' IIf() function — a FUNCTION, so it evaluates BOTH arguments
Dim unsafeDisplay As String = IIf(student Is Nothing, "Unknown", student.Name)
IIf is a legacy function inherited from VB6. Because arguments to a function are evaluated before the call, IIf dereferences student.Name even when student is Nothing — and throws. It also returns Object, which under Option Strict Off hides the problem further.
If() with two arguments is the null-coalescing form, equivalent to C#'s ??:
Dim name As String = If(student.Name, "Unknown")
When you find IIf in legacy code, treat it as a defect to review, not a style preference.
Errors you will hit
| Message | Cause | Fix |
|---|---|---|
BC30081: 'If' must end with a matching 'End If' | Mixed single-line and block If | Pick one form |
BC30084: 'For' must end with a matching 'Next' | Missing Next | Add it |
System.NullReferenceException in a condition that checks for Nothing | Used And instead of AndAlso | And evaluates both sides — use AndAlso |
| A loop runs one time too many | For i = 0 To count includes count | Use To count - 1 |
BC30311: Value of type 'X' cannot be converted to 'Y' inside IIf | IIf evaluates both arguments | Use If(...) — the three-argument form |
And versus AndAlso is the VB.NET trap that costs the most time. And always evaluates both sides, so a null check followed by And still throws.
Common mistakes
- Using
And/OrwhereAndAlso/OrElsewas meant, then crashing onNothing - Converting
for (i = 0; i < n; i++)toFor i = 0 To n, running one iteration too many - Treating
UBoundas a count - Omitting
Case Else, so a new enum member silently does nothing - Overlapping
Select Caseranges in the wrong order - Using
IIfand being surprised by a null-reference exception - Modifying a collection inside a
For Each - Chaining statements with
:on a single-lineIf
Practice
Take a procedure from an existing VB.NET application that contains at least one loop and one multi-branch condition. Write down, without running it, how many times the loop body executes for a given input and which branch is taken. Then step through it in the debugger and compare. Separately, search the project for IIf( and And and note every occurrence that could dereference Nothing.
You can now
- Trace a VB.NET procedure's control flow accurately
- Say why
AndAlsois safe whereAndis not - Avoid the inclusive-range off-by-one in
For ... To - Recognise
IIfand say whyIf(...)replaced it - Read
Select Caseincluding ranges and comma lists
Review questions
- How many times does
For i As Integer = 0 To 5run? - Why can
Andcause a null-reference exception whereAndAlsocannot? - What does
IIfdo that theIf()operator does not? - What happens to a
Select Caseon an enum when a new member is added and there is noCase Else?
Next: Procedures and arrays