Skip to main content
Published / updated

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-line If
  • Select Case, including ranges and comma lists
  • For, For Each, Do, and While
  • Exit and Continue
  • And versus AndAlso, Or versus OrElse
  • The If() operator and IIf()
  • 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

MessageCauseFix
BC30081: 'If' must end with a matching 'End If'Mixed single-line and block IfPick one form
BC30084: 'For' must end with a matching 'Next'Missing NextAdd it
System.NullReferenceException in a condition that checks for NothingUsed And instead of AndAlsoAnd evaluates both sides — use AndAlso
A loop runs one time too manyFor i = 0 To count includes countUse To count - 1
BC30311: Value of type 'X' cannot be converted to 'Y' inside IIfIIf evaluates both argumentsUse 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/Or where AndAlso/OrElse was meant, then crashing on Nothing
  • Converting for (i = 0; i < n; i++) to For i = 0 To n, running one iteration too many
  • Treating UBound as a count
  • Omitting Case Else, so a new enum member silently does nothing
  • Overlapping Select Case ranges in the wrong order
  • Using IIf and being surprised by a null-reference exception
  • Modifying a collection inside a For Each
  • Chaining statements with : on a single-line If

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 AndAlso is safe where And is not
  • Avoid the inclusive-range off-by-one in For ... To
  • Recognise IIf and say why If(...) replaced it
  • Read Select Case including ranges and comma lists

Review questions

  1. How many times does For i As Integer = 0 To 5 run?
  2. Why can And cause a null-reference exception where AndAlso cannot?
  3. What does IIf do that the If() operator does not?
  4. What happens to a Select Case on an enum when a new member is added and there is no Case Else?

Next: Procedures and arrays