Language Atlas (mini-courses for more languages) Module 3 – Visual Basic .NET for maintainers (and the road to C#)
VB.NET for C# developers: the differences
Map C# to Visual Basic and catch the traps that compile in both, from array bounds and CInt rounding to And versus AndAlso, events and Option Compare.
What you will learn
- Map C# syntax to Visual Basic: Dim, Sub and Function, ByVal and ByRef, Nothing, AndAlso and OrElse
- Predict Visual Basic array bounds, For loop end values, integer division, Mod and conversion rounding
- Read event wiring with WithEvents, Handles and AddHandler, and modules and shared members
- Check which string comparison a Visual Basic file uses before trusting = on strings
Before you start
What you’ll learn · Before you start
What you will learn
- Map C# syntax to Visual Basic: Dim, Sub and Function, ByVal and ByRef, Nothing, AndAlso and OrElse
- Predict Visual Basic array bounds, For loop end values, integer division, Mod and conversion rounding
- Read event wiring with WithEvents, Handles and AddHandler, and modules and shared members
- Check which string comparison a Visual Basic file uses before trusting = on strings
Before you start
On this page
A C# developer reads Dim slots(3) As Integer as an array of three, and Visual Basic makes four. In the same file
7 / 2 is 3.5, CInt(3.5) is 4 where C#’s (int)3.5 is 3, and If x IsNot Nothing And x.Count > 0 throws when x
is Nothing, although each line compiles and looks like something you know.
This lesson maps C# to Visual Basic, one construct at a time, and pins down the places where the same-looking code
means something else: declarations and ByRef, the logical operators, array bounds and For loops, division,
rounding and overflow, events and modules, and the Option Compare that can change = in one file. Every claim comes
with a run on the .NET SDK 10.0.401, and the numbers part runs the same lines in C# beside it.
Declarations read differently: Dim, Sub, Function and ByRef
Start with what this program prints:
' Declarations and calls that read differently from C#, each printed with its result.
Module Program
' A Sub returns nothing, like a void method. ByRef lets it change the caller's variable.
Sub AddTax(ByRef amount As Integer)
amount += amount * 18 \ 100
End Sub
' A Function returns a value: the type after As is the return type.
Function Greeting(name As String) As String
Return $"Hello, {If(name, "guest")}"
End Function
Sub Main()
Dim count As Integer = Nothing
Console.WriteLine($"Dim count As Integer = Nothing gives {count}")
Dim nickname As String = Nothing
Console.WriteLine(Greeting(nickname))
Console.WriteLine($"nickname Is Nothing: {nickname Is Nothing}, nickname = """": {nickname = ""}")
Dim price As Integer = 1000
AddTax(price)
Console.WriteLine($"AddTax(price): price = {price}")
AddTax((price))
Console.WriteLine($"AddTax((price)): price = {price}")
End Sub
End Module from session import run
# Nothing as a default value, If() with two arguments, and a ByRef parameter called with and without extra
# parentheses.
run("""
dotnet run --project declarations
""") Output
$ dotnet run --project declarations Dim count As Integer = Nothing gives 0 Hello, guest nickname Is Nothing: True, nickname = "": True AddTax(price): price = 1180 AddTax((price)): price = 1180
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 declarations_session.py
Dim count As Integer = Nothing gave 0. Nothing is the default value of whatever type receives it: a null
reference for an object or a string, 0 for an Integer, False for a Boolean. That is C#’s default, not its null,
which an int does not even accept. If(nickname, "guest") is C#’s ??. The comparison line shows a difference C#
developers trip over: for strings, nickname = "" is True while nickname holds Nothing, because Visual Basic treats
the empty string as equal to Nothing, and only Is Nothing asks whether there is a string at all.
The ByRef lines matter most for reading old code. AddTax(price) changed the caller’s price to 1180, and nothing at
the call site said it might: C# makes you write ref in the call, Visual Basic only in the declaration. Wrapping the
argument in an extra pair of parentheses, AddTax((price)), passes a copy, so the second call left price at 1180
instead of raising it to 1392. A call that looks harmless can change a variable, and an extra pair of brackets can stop
it.
The rest of the mapping, with C# first:
| C# | Visual Basic | Watch for |
|---|---|---|
int count = 5; |
Dim count As Integer = 5 |
Dim count = 5 takes the type Integer with Option Infer On |
void Save() |
Sub Save() |
A Sub returns nothing |
int Total() |
Function Total() As Integer |
The type after As is the return type |
ref int amount |
ByRef amount As Integer |
No marker at the call site |
| a parameter by value | ByVal, also the default |
Parentheses around an argument force a copy |
null, default |
Nothing |
0 for an Integer, null for an object |
a ?? b and c ? x : y |
If(a, b) and If(c, x, y) |
Not IIf, as the next section shows |
x == null, x != null |
x Is Nothing, x IsNot Nothing |
= compares values, Is compares references |
s + t for strings |
s & t |
+ on a string and a number can add, with Option Strict Off |
static class, static member |
Module, Shared member |
Module members are called without the module’s name |
this, base |
Me, MyBase |
The row for + on strings is the trap for old code: with Option Strict Off, "34" + 6 converts the string to a number
and gives 40, where C# would join them into “346”. Use & for text, always.
Checkpoint
Check: Declarations, Nothing and ByRef
And and Or evaluate both sides; AndAlso, OrElse and If() stop early
C#’s && and || stop as soon as the left side decides the answer. Visual Basic’s And and Or do not: they are
the operators for both logic and bits, and they always evaluate both sides. The
short-circuit evaluation: Evaluating a logical expression only as far as needed for its result: if the left side of an and is False, the right side is skipped. Visual Basic's AndAlso and OrElse and C#'s && and || work this way; And and Or do not. versions are spelled AndAlso and OrElse. This program
asks whether an order that is Nothing has lines, four ways:
' And and IIf evaluate both sides; AndAlso and If() stop as soon as the answer is known.
Module Program
Class Order
Public Lines As New List(Of String)
End Class
Sub Main()
Dim missing As Order = Nothing
Console.WriteLine($"AndAlso: {missing IsNot Nothing AndAlso missing.Lines.Count > 0}")
Try
Console.WriteLine($"And: {missing IsNot Nothing And missing.Lines.Count > 0}")
Catch ex As NullReferenceException
Console.WriteLine($"And: {ex.GetType().Name}")
End Try
Console.WriteLine($"If(): {If(missing Is Nothing, 0, missing.Lines.Count)}")
Try
Console.WriteLine($"IIf: {IIf(missing Is Nothing, 0, missing.Lines.Count)}")
Catch ex As NullReferenceException
Console.WriteLine($"IIf: {ex.GetType().Name}")
End Try
End Sub
End Module from session import run
# And and IIf evaluate both sides of a test whose left side is already decided; AndAlso and If() do not.
run("""
dotnet run --project logic
""") Output
$ dotnet run --project logic AndAlso: False And: NullReferenceException If(): 0 IIf: NullReferenceException
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 logic_session.py
Step through the first two tests with the order set to Nothing:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- The left side first. Both versions start with the left side. missing holds Nothing, so missing IsNot Nothing is False.
- The answer is already known. False And anything is False, so the right side cannot change the result. What happens next depends on the operator.
- AndAlso stops. AndAlso is short-circuiting: it skips the right side and gives False at once. The program printed AndAlso: False.
- And reads the right side anyway. And always evaluates both sides. missing.Lines reads a field of Nothing, which throws NullReferenceException before any result exists: the program printed And: NullReferenceException.
- IIf is a function too. IIf(missing Is Nothing, 0, missing.Lines.Count) is a function call, so all three arguments are evaluated before it runs, and the third throws. The If() operator evaluates only the branch it returns, and printed 0.
With AndAlso, the left side, missing IsNot Nothing, is False and decides the result, so the right side never runs.
With And, Visual Basic evaluates missing.Lines.Count > 0 as well and throws NullReferenceException on a variable
that holds Nothing. The same split runs through the two ways to choose a value: the If(c, x, y) operator evaluates
only the branch it returns, while IIf(c, x, y) is an ordinary function, so all three arguments are evaluated before
it is called, and the one that should have been skipped throws.
Porting && as And
If order IsNot Nothing And order.Lines.Count > 0 Then compiles and works in every test with a real order, then
throws on the first Nothing. Write AndAlso and OrElse for every condition, and keep And and Or for
combining bits, such as flags And 4.
Checkpoint
Check: And and Or against AndAlso, OrElse and If()
Arrays are declared by their last index, and For loops include their end
Visual Basic declares an array by its upper bound: The highest index of an array. Visual Basic declares an array by it, so Dim marks(4) As Integer has the indexes 0 to 4 and five elements, while C# declares new int[4] by its length, four elements., the last index, not by its length. The
For ... To loop is inclusive in the same way: its end value is the last value the counter takes.
' Arrays are declared by their last index, and For ... To includes its end value.
Module Program
Sub Main()
Dim marks(4) As Integer
Console.WriteLine($"Dim marks(4): {marks.Length} elements, last index {marks.GetUpperBound(0)}")
Dim none(-1) As Integer
Console.WriteLine($"Dim none(-1): {none.Length} elements")
ReDim Preserve marks(6)
Console.WriteLine($"ReDim Preserve marks(6): {marks.Length} elements")
Dim upper As Integer = -2
Try
Dim broken(upper) As Integer
Console.WriteLine($"Dim broken(-2): {broken.Length} elements")
Catch ex As OverflowException
Console.WriteLine($"Dim broken(-2): {ex.GetType().Name}")
End Try
Dim visited As New List(Of Integer)
For i As Integer = 1 To 10 Step 3
visited.Add(i)
Next
Console.WriteLine($"For i = 1 To 10 Step 3: {String.Join(", ", visited)}")
visited.Clear()
For i As Integer = 10 To 1 Step -4
visited.Add(i)
Next
Console.WriteLine($"For i = 10 To 1 Step -4: {String.Join(", ", visited)}")
visited.Clear()
For i As Integer = 1 To 0
visited.Add(i)
Next
Console.WriteLine($"For i = 1 To 0: {visited.Count} passes")
Dim last As Integer = 3
Dim passes As Integer = 0
For i As Integer = 1 To last
last = 10
passes += 1
Next
Console.WriteLine($"For i = 1 To last, with last set to 10 inside: {passes} passes")
End Sub
End Module from session import run
# Arrays declared by their last index, ReDim Preserve, and the values For ... To ... Step visits.
run("""
dotnet run --project loops
""") Output
$ dotnet run --project loops Dim marks(4): 5 elements, last index 4 Dim none(-1): 0 elements ReDim Preserve marks(6): 7 elements Dim broken(-2): OverflowException For i = 1 To 10 Step 3: 1, 4, 7, 10 For i = 10 To 1 Step -4: 10, 6, 2 For i = 1 To 0: 0 passes For i = 1 To last, with last set to 10 inside: 3 passes
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 loops_session.py
| Declaration or loop | Visual Basic | C# that looks the same |
|---|---|---|
Dim marks(4) As Integer |
5 elements, indexes 0 to 4 | new int[4]: 4 elements, 0 to 3 |
Dim none(-1) As Integer |
0 elements, an empty array | new int[0] |
For i = 1 To 10 Step 3 |
1, 4, 7, 10 | for (i = 1; i < 10; i += 3): 1, 4, 7 |
For i = 10 To 1 Step -4 |
10, 6, 2 | for (i = 10; i > 1; i -= 4): 10, 6, 2 |
So a port of Dim marks(n) to C# is new int[n + 1], and a port of For i = a To b is a loop with <=. Three more
facts from the run. ReDim Preserve marks(6) gave 7 elements and kept the old values, and an upper bound of -2 is not
an empty array but an OverflowException. And the end of a For loop is read once, before the first pass: setting
last to 10 inside the loop still gave 3 passes, as Microsoft’s reference for the statement describes.
Key idea
In Visual Basic the number in Dim a(n) and the end of For i = a To n are both included: n is the last index and
the last value, so the array has n + 1 elements.
Checkpoint
Check: Array bounds and For loops
Division gives a Double, CInt rounds, and overflow is checked
Numbers are where a port changes results without a single error. Here are the same lines in Visual Basic and in C#, each run on the .NET SDK 10.0.401:
' Division, rounding and overflow: the lines a C# developer reads differently.
Module Program
Sub Main()
Console.WriteLine($"7 / 2 = {7 / 2}, 7 \ 2 = {7 \ 2}, -7 \ 2 = {-7 \ 2}, -7 Mod 2 = {-7 Mod 2}")
Console.WriteLine($"CInt(2.5) = {CInt(2.5)}, CInt(3.5) = {CInt(3.5)}, CInt(-2.5) = {CInt(-2.5)}, CInt(2.7) = {CInt(2.7)}")
Console.WriteLine($"Fix(2.7) = {Fix(2.7)}, Fix(-2.7) = {Fix(-2.7)}, Int(-2.7) = {Int(-2.7)}")
Dim big As Integer = Integer.MaxValue
Try
big += 1
Console.WriteLine($"Integer.MaxValue + 1 = {big}")
Catch ex As OverflowException
Console.WriteLine($"Integer.MaxValue + 1: {ex.GetType().Name}")
End Try
Dim nearMax As Double = 2147483647.5
Try
Console.WriteLine($"CInt(2147483647.5) = {CInt(nearMax)}")
Catch ex As OverflowException
Console.WriteLine($"CInt(2147483647.5): {ex.GetType().Name}")
End Try
Dim nearMin As Double = -2147483648.5
Console.WriteLine($"CInt(-2147483648.5) = {CInt(nearMin)}")
End Sub
End Module from session import run
# Division, conversion rounding and integer overflow as Visual Basic does them, with every Option of the project On.
run("""
dotnet run --project numbers
""") Output
$ dotnet run --project numbers 7 / 2 = 3.5, 7 \ 2 = 3, -7 \ 2 = -3, -7 Mod 2 = -1 CInt(2.5) = 2, CInt(3.5) = 4, CInt(-2.5) = -2, CInt(2.7) = 3 Fix(2.7) = 2, Fix(-2.7) = -2, Int(-2.7) = -3 Integer.MaxValue + 1: OverflowException CInt(2147483647.5): OverflowException CInt(-2147483648.5) = -2147483648
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 numbers_session.py
// The lines of numbers/Program.vb in C#, written the way a C# developer would first write them.
int seven = 7;
Console.WriteLine($"7 / 2 = {seven / 2}, 7 / 2.0 = {seven / 2.0}, -7 / 2 = {-seven / 2}, -7 % 2 = {-seven % 2}");
double half = 2.5;
double threeHalves = 3.5;
double almost = 2.7;
Console.WriteLine($"(int)2.5 = {(int)half}, (int)3.5 = {(int)threeHalves}, (int)-2.5 = {(int)-half}, (int)2.7 = {(int)almost}");
Console.WriteLine($"Convert.ToInt32(2.5) = {Convert.ToInt32(half)}, Convert.ToInt32(3.5) = {Convert.ToInt32(threeHalves)}, Math.Floor(-2.7) = {Math.Floor(-almost)}");
int big = int.MaxValue;
big += 1;
Console.WriteLine($"int.MaxValue + 1 = {big}");
try
{
checked
{
big = int.MaxValue;
big += 1;
}
}
catch (OverflowException ex)
{
Console.WriteLine($"checked: {ex.GetType().Name}");
} Output
7 / 2 = 3, 7 / 2.0 = 3.5, -7 / 2 = -3, -7 % 2 = -1 (int)2.5 = 2, (int)3.5 = 3, (int)-2.5 = -2, (int)2.7 = 2 Convert.ToInt32(2.5) = 2, Convert.ToInt32(3.5) = 4, Math.Floor(-2.7) = -3 int.MaxValue + 1 = -2147483648 checked: OverflowException
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run numbers.cs
| Expression | Visual Basic | C# |
|---|---|---|
| 7 divided by 2 | 7 / 2 is 3.5; 7 \ 2 is 3 |
7 / 2 is 3 for integers |
| -7 divided by 2, remainder | -7 \ 2 is -3, -7 Mod 2 is -1 |
-7 / 2 is -3, -7 % 2 is -1 |
| 2.5 and 3.5 to an integer | CInt: 2 and 4 |
(int): 2 and 3; Convert.ToInt32: 2 and 4 |
| 2.7 to an integer | CInt(2.7) is 3, Fix(2.7) is 2 |
(int)2.7 is 2 |
| -2.7 down to an integer | Int(-2.7) is -3, Fix(-2.7) is -2 |
Math.Floor(-2.7) is -3 |
Integer.MaxValue + 1 |
OverflowException | -2147483648, unless checked |
Three rows decide most ports. / in Visual Basic always divides into a Double, so C#’s integer / becomes \, and
\ and Mod truncate towards zero just as C#’s / and % do. CInt rounds, and a half goes to the even neighbour,
banker's rounding: Rounding in which a value exactly halfway between two whole numbers goes to the even one: 2.5 becomes 2 and 3.5 becomes 4. Visual Basic's CInt and Math.Round and Python's round() use it. More in “A one-week plan for learning any language”; C#’s cast (int) drops the fraction, so CInt(3.5) is 4 where
(int)3.5 is 3. Convert.ToInt32 rounds the way CInt does, which makes it the safe translation. And Visual Basic
checks integer arithmetic for overflow unless a project removes the checks, while C# arithmetic is unchecked unless
the code or the project asks for checked arithmetic, so a counter that overflows throws in one language and wraps to
-2147483648 in the other.
Checkpoint
Check: Division, conversion rounding and overflow
Handles wires events when the code compiles, AddHandler while it runs
A C# developer subscribes to an event with += and unsubscribes with -=. Visual Basic has that too, as
AddHandler and RemoveHandler, and a second way, which the Windows Forms designer writes for every control you
double-click: a field declared WithEvents and a Handles clause on the procedure that handles its event. In this
program a till raises an event for every sale:
' A till raises an event for every sale. One handler is wired with WithEvents and Handles, one with AddHandler.
Public Class Till
Public Shared Created As Integer
Public Event SaleRecorded(amount As Integer)
Public Sub New()
Created += 1
End Sub
Public Sub Record(amount As Integer)
RaiseEvent SaleRecorded(amount)
End Sub
End Class
Module Shop
Private WithEvents counter As New Till
Private total As Integer
Private Sub AddToTotal(amount As Integer) Handles counter.SaleRecorded
total += amount
Console.WriteLine($"AddToTotal: total is {total}")
End Sub
Private Sub PrintReceipt(amount As Integer)
Console.WriteLine($"PrintReceipt: receipt for {amount}")
End Sub
Sub Main()
counter.Record(120)
AddHandler counter.SaleRecorded, AddressOf PrintReceipt
counter.Record(80)
RemoveHandler counter.SaleRecorded, AddressOf PrintReceipt
counter.Record(50)
counter = New Till()
counter.Record(10)
AddHandler counter.SaleRecorded, AddressOf AddToTotal
counter.Record(5)
Console.WriteLine($"Till.Created = {Till.Created}")
End Sub
End Module from session import run
# One event, a handler wired with WithEvents and Handles, one added and removed with AddHandler and RemoveHandler.
run("""
dotnet run --project events
""") Output
$ dotnet run --project events AddToTotal: total is 120 AddToTotal: total is 200 PrintReceipt: receipt for 80 AddToTotal: total is 250 AddToTotal: total is 260 AddToTotal: total is 265 AddToTotal: total is 270 Till.Created = 2
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 events_session.py
Step through Shop.Main and watch which handler each sale reaches:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- A sale of 120. Main calls counter.Record(120), and the till raises SaleRecorded. AddToTotal has a Handles counter.SaleRecorded clause, so it runs and prints total is 120.
- Handles at work. The Handles clause was wired when the code was compiled, for whatever object the WithEvents field counter holds. Nothing in Main had to subscribe.
- AddHandler adds a second handler. AddHandler counter.SaleRecorded, AddressOf PrintReceipt subscribes another procedure while the program runs. The sale of 80 reaches both: total is 200, then receipt for 80.
- RemoveHandler takes it away. After RemoveHandler, the sale of 50 reaches AddToTotal only, which prints total is 250. A handler added with AddHandler stays until RemoveHandler or the end of the object.
- A new till in the same field. Main puts a new Till into counter. The Handles wiring moves with the field to the new object, so its sale of 10 still reaches AddToTotal: total is 260. The constructor counted a second till in the Shared field Created.
- Wired twice, counted twice. Then Main also adds AddToTotal with AddHandler, while its Handles clause still applies. The sale of 5 calls it twice, 265 and then 270: a double count that one added line can bring into Windows Forms code, where the designer already wrote the Handles clause.
Handles counter.SaleRecorded wires AddToTotal to whatever object the WithEvents field holds, when the code is
compiled; Microsoft’s guide to events calls it the declarative way. That is why the sale of 10 reached AddToTotal
after a new till was put into counter: the wiring follows the field. AddHandler and RemoveHandler subscribe while
the program runs, as C#’s += and -= do. Mixing the two on one procedure counted the sale of 5 twice.
Two words in the program map to C#’s static. Shop is a Module, Visual Basic’s form of a static class: all its
members are Shared, nobody creates it, and code anywhere in its namespace may call its members without the module’s
name, unless two modules have members of the same name, as BinaryRules.Report and TextRules.Report in the next
section do. Till.Created is a Shared field of a class, one value for all tills, which the constructor counted to
2.
A handler wired twice
A procedure with a Handles Button1.Click clause runs on every click already. Adding
AddHandler Button1.Click, AddressOf Button1_Click as well, say in Form_Load, makes it run twice per click. Use one
wiring per handler: keep Handles for controls the designer created, and AddHandler for objects you create
while the program runs.
Checkpoint
Check: WithEvents, Handles and AddHandler; modules and Shared
Option Compare Text changes string comparisons, one file at a time
Option Compare decides how Visual Basic’s own string operators compare: =, <, >, Like, and Select Case
on strings. Binary, the default, compares character codes, so case matters, as it does in C#. Text compares as text
in the current culture and ignores case. Like the other Options, a statement at the top of a file overrides the
project for that file. This project makes the same four comparisons in two files:
compare/Binary.vb
' This file has no Option Compare statement, so the project's Option Compare Binary applies.
Module BinaryRules
Function Report() As String
Return $"Binary file: ""Pune"" = ""PUNE"" is {"Pune" = "PUNE"}, ""apple"" < ""Banana"" is {"apple" < "Banana"}, " &
$"""INV-42"" Like ""inv-*"" is {"INV-42" Like "inv-*"}, ""Pune"".Equals(""PUNE"") is {"Pune".Equals("PUNE")}"
End Function
End Module compare/Text.vb
Option Compare Text
' This file compares strings as text, ignoring case, in the current culture.
Module TextRules
Function Report() As String
Return $"Text file: ""Pune"" = ""PUNE"" is {"Pune" = "PUNE"}, ""apple"" < ""Banana"" is {"apple" < "Banana"}, " &
$"""INV-42"" Like ""inv-*"" is {"INV-42" Like "inv-*"}, ""Pune"".Equals(""PUNE"") is {"Pune".Equals("PUNE")}"
End Function
End Module compare/Program.vb
' The same four comparisons, made in two files of one project.
Module Program
Sub Main()
Console.WriteLine(BinaryRules.Report())
Console.WriteLine(TextRules.Report())
End Sub
End Module from session import run
# The same comparisons in a file that keeps the project's Option Compare Binary and in a file that says Option Compare
# Text.
run("""
dotnet run --project compare
""") Output
$ dotnet run --project compare
Binary file: "Pune" = "PUNE" is False, "apple" < "Banana" is False, "INV-42" Like "inv-*" is False, "Pune".Equals("PUNE") is False
Text file: "Pune" = "PUNE" is True, "apple" < "Banana" is True, "INV-42" Like "inv-*" is True, "Pune".Equals("PUNE") is False
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 compare_session.py
In Text.vb, "Pune" = "PUNE" is True, "apple" < "Banana" is True because case no longer puts every capital first,
and Like "inv-*" matches "INV-42". In Binary.vb all three are False. C#’s == gives the Binary answer; C# has no
< for strings, and its string.CompareOrdinal, which compares character codes, also puts “Banana” before “apple”.
The last comparison is the same in both files: String.Equals is a .NET method, not a Visual Basic operator, so
Option Compare does not reach it. Before you trust = on strings in an old file, read its first lines; and when you
port such a file to C#, write the comparison out, for example string.Equals(a, b, StringComparison.CurrentCultureIgnoreCase).
Checkpoint
Check: Option Compare and string comparisons
Practise: port loops and conversions without changing a result
The exercise turns three of this lesson’s rules into Python functions that say what the Visual Basic code did:
how many elements Dim a(upper) creates, which values a For ... To ... Step loop visits, and what CInt returns,
overflow included. Its sample tests are the recorded runs of loops/Program.vb and numbers/Program.vb, so a port
that passes them keeps the results.
Exercise · Easy · Python
Port Visual Basic array bounds, For loops and CInt to Python
Code ported from Visual Basic goes wrong most often in three places that compile without a word in the new language. Write three functions in vb_rules.py that answer what the Visual Basic code did, so that a port can be checked against them:
dim_length(upper): the number of elements thatDim a(upper) As Integercreates.upperis the last index, soDim a(4)has 5 elements andDim a(-1)none. Forupperbelow -1, Visual Basic throwsOverflowException: raiseOverflowError.for_values(start, end, step=1): the list of values thatFor i = start To end Step stepgivesi, in order. The end value is included when the loop reaches it, a negative step counts down, and a loop whose start is already past its end runs no pass. A step of 0 never moves the counter, so in Visual Basic such a loop never ends once its first pass runs: raiseValueErrorfor any step of 0 instead.cint(x): whatCInt(x)returns for a floating-point numberx: the nearest whole number, a half going to the even neighbour (CInt(2.5)is 2,CInt(3.5)is 4). When that whole number does not fit in anInteger, from -2,147,483,648 to 2,147,483,647, Visual Basic throwsOverflowException: raiseOverflowError.
The sample tests are the lesson's recorded runs of loops/Program.vb and numbers/Program.vb.
Starter code · vb_rules.py
INT_MIN = -2_147_483_648
INT_MAX = 2_147_483_647
def dim_length(upper):
"""The number of elements of Dim a(upper) As Integer: upper is the last index."""
return upper # Replace this line: upper is the last index, not the length.
def for_values(start, end, step=1):
"""The values For i = start To end Step step gives i, in order."""
return list(range(start, end, step)) # Replace this line: range() stops before its end.
def cint(x):
"""CInt(x) for a floating-point x: round half to even, then check that it fits in an Integer."""
return int(x) # Replace this line: int() drops the fraction instead of rounding. The sample tests · test_vb_rules.py
from vb_rules import cint, dim_length, for_values
def test_array_lengths_from_the_loops_run():
"""counts the elements of Dim marks(4), Dim none(-1) and ReDim Preserve marks(6)"""
assert dim_length(4) == 5
assert dim_length(-1) == 0
assert dim_length(6) == 7
def test_an_upper_bound_below_minus_one_overflows():
"""raises OverflowError for Dim broken(-2), as the loops run threw OverflowException"""
try:
dim_length(-2)
except OverflowError:
pass
else:
assert False, "Dim a(-2) must raise OverflowError"
def test_for_loops_include_their_end():
"""visits 1, 4, 7, 10 for For i = 1 To 10 Step 3, and 10, 6, 2 for Step -4"""
assert for_values(1, 10, 3) == [1, 4, 7, 10]
assert for_values(10, 1, -4) == [10, 6, 2]
assert for_values(1, 4) == [1, 2, 3, 4]
def test_a_loop_past_its_end_runs_no_pass():
"""runs no pass for For i = 1 To 0, and refuses a step of 0"""
assert for_values(1, 0) == []
try:
for_values(1, 5, 0)
except ValueError:
pass
else:
assert False, "Step 0 must raise ValueError"
def test_cint_rounds_halves_to_even():
"""gives 2, 4, -2 and 3 for CInt(2.5), CInt(3.5), CInt(-2.5) and CInt(2.7), as the numbers run printed"""
assert [cint(2.5), cint(3.5), cint(-2.5), cint(2.7)] == [2, 4, -2, 3]
def test_cint_overflows_only_past_the_integer_range():
"""raises OverflowError for CInt(2147483647.5), and keeps -2147483648.5, which rounds to the even -2147483648"""
try:
cint(2147483647.5)
except OverflowError:
pass
else:
assert False, "CInt(2147483647.5) must raise OverflowError"
assert cint(-2147483648.5) == -2147483648 A hint
For for_values, keep adding step to a counter while it has not passed end: with a positive step the loop runs while the counter is at most end, with a negative step while it is at least end. Python's own round() already takes a half to the even neighbour, so cint only needs the range check after rounding.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Six differences, six groups of questions: the self-check opens the group where your answers have been shakiest and moves on from there.
Practice that adapts to you
Practise this lesson
18 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to mid-level): What is the difference between And and AndAlso in Visual Basic, and which one is C#’s &&?
And always evaluates both of its operands, while AndAlso stops when the left operand is False, because the result is
then known to be False. C#’s && short-circuits, so it corresponds to AndAlso, and || to OrElse. The difference
matters whenever the right side depends on the left: x IsNot Nothing And x.Count > 0 throws NullReferenceException
when x is Nothing, because And still reads x.Count, while the AndAlso version gives False. And and Or are
also Visual Basic’s bitwise operators, as in flags And 4, which is why they evaluate both sides. The same split holds
for choosing a value: the If(c, x, y) operator evaluates only the branch it returns, while the IIf function
evaluates every argument before it runs.
Two harder questions, both about code that compiles in either language and behaves differently:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
After a refactor of a Visual Basic Windows Forms app, saving a form writes every record twice. The Save button's Click handler has a Handles clause, and new code in Form_Load also calls AddHandler for it. What happens, how do you fix it, and how do you find other handlers wired twice?
-
Mid to staff
In one Visual Basic project, looking up the customer code "acme-01" finds "ACME-01" in the billing module but not in the shipping module, although both compare with =. How can that be, and what do you do before porting both modules to C#?
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
How many elements does Dim marks(4) As Integer have, and which values does For i = 1 To 10 Step 3 visit?
Five, indexes 0 to 4, because the number is the last index; the loop visits 1, 4, 7 and 10, its end included. A C# port needs new int[n + 1] and a loop with <=.
-
What does Dim count As Integer = Nothing give, and what is "" = Nothing for strings?
0: Nothing is the default of whatever type receives it, like C#'s default. For strings, an empty string equals Nothing, so only Is Nothing asks whether there is a string at all.
-
A Sub declares ByRef amount. What does the caller's code show, and how can it pass a copy?
Nothing at the call site shows that the variable may change; C# would make you write ref. Wrapping the argument in an extra pair of parentheses, AddTax((price)), passes a copy.
-
Which Visual Basic operators are C#'s && and ||?
AndAlso and OrElse. And and Or evaluate both sides, so x IsNot Nothing And x.Count > 0 throws NullReferenceException when x is Nothing; IIf also evaluates every argument.
-
What are CInt(3.5) and C#'s (int)3.5, and which C# call matches CInt?
4 and 3. CInt rounds a half to the even neighbour, the cast drops the fraction, and Convert.ToInt32 rounds the way CInt does.
-
What happens at Integer.MaxValue + 1 in Visual Basic, and at int.MaxValue + 1 in C#?
Visual Basic throws OverflowException, because it checks integer arithmetic unless the project turns that off. C# wraps to -2147483648 unless the code or the project asks for checked arithmetic.
-
A procedure has Handles Button1.Click and Form_Load also adds it with AddHandler. What happens on a click?
It runs twice, once per wiring. Keep Handles for controls the designer made and AddHandler for objects created while the program runs, one wiring per handler.
-
Option Compare Text is the first line of a file. What is "Pune" = "PUNE" there?
True: in that file, Visual Basic's own string operators ignore case and follow the current culture. String.Equals is a .NET method, so it still tells the two apart.
Key takeaways
Nothingis the default of any type, 0 for anInteger;ByRefchanges the caller’s variable with no marker at the call site, and an extra pair of parentheses passes a copy.AndandOrevaluate both sides andIIfevaluates every argument, so conditions useAndAlso,OrElseand theIf()operator.Dim a(n)hasn + 1elements andFor i = a To nincludesn, so ports neednew T[n + 1]and<=./divides into a Double,CIntrounds half to even where C#’s(int)truncates, and Visual Basic throws on integer overflow where C# wraps unless code ischecked.Handleswires a handler to aWithEventsfield when the code compiles,AddHandlerwhile it runs, and using both on one procedure runs it twice; aModuleis a static class andSharedmeansstatic.Option Compare Textat the top of a file makes=,<andLikeignore case in that file only, and leaves .NET methods such asString.Equalsunchanged.
Terms in this lesson
- short-circuit evaluation
- Evaluating a logical expression only as far as needed for its result: if the left side of an and is False, the right side is skipped. Visual Basic's AndAlso and OrElse and C#'s && and || work this way; And and Or do not.
- upper bound
- The highest index of an array. Visual Basic declares an array by it, so Dim marks(4) As Integer has the indexes 0 to 4 and five elements, while C# declares new int[4] by its length, four elements.
References
- Dim statement (Visual Basic) (Microsoft)
- How to: Force an argument to be passed by value (Visual Basic) (Microsoft)
- Nothing keyword (Visual Basic) (Microsoft)
- Method parameters (C# reference): ref arguments at the call site (Microsoft)
- And operator (Visual Basic): logical and bitwise (Microsoft)
- AndAlso operator (Visual Basic) (Microsoft)
- If operator (Visual Basic): short-circuit evaluation, unlike IIf (Microsoft)
- Arrays in Visual Basic (the upper bound, zero-length arrays) (Microsoft)
- For...Next statement (Visual Basic): technical implementation (Microsoft)
- \ operator (Visual Basic) (Microsoft)
- Mod operator (Visual Basic) (Microsoft)
- Type conversion functions (Visual Basic): fractional parts (Microsoft)
- Convert.ToInt32 method (.NET API reference) (Microsoft)
- -removeintchecks (Visual Basic compiler option) (Microsoft)
- The checked and unchecked statements (C# reference) (Microsoft)
- Events in Visual Basic: WithEvents, Handles, AddHandler and RemoveHandler (Microsoft)
- Module statement (Visual Basic) (Microsoft)
- Option Compare statement (Visual Basic) (Microsoft)
- Select...Case statement (Visual Basic): Option Compare and string cases (Microsoft)
- Like operator (Visual Basic) (Microsoft)
- Comparison operators (C# reference): the types that support <, >, <= and >= (Microsoft)
- String.CompareOrdinal method (.NET API reference) (Microsoft)
- Option Strict statement (Visual Basic): "34" + 6 with Option Strict Off (Microsoft)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress