Visual Basic for Programmers of Other Languages

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 was checked on 2026-10-19 against Visual Basic 17.13 on .NET 10 (LTS, SDK 10.0.402); .NET 11 was a release candidate that day and is treated as upcoming. It is written for someone who already programs in another language and wants to read and write Visual Basic without being surprised. Every program on it was built with Option Strict On and warnings as errors and run in the official mcr.microsoft.com/dotnet/sdk:10.0 container image, and the output shown is the real output. The page starts with the dozen rules that cause most of the surprises, each shown by a program, then gives side-by-side maps to C#, Java and VB6/VBA, a flowchart that answers "which Visual Basic feature replaces …​", and a list of the usual mistakes. The language is explained in order starting from Program Structure and Code Conventions; the platform it runs on is the same as C#'s, see Visual Basic and .NET.

Visual Basic and C# compile to the same intermediate language for the same runtime, and they share the .NET libraries, so most of what matters about the platform (collections, LINQ, async, IDisposable, generics) is identical. The differences are in the syntax, in a few semantic defaults inherited from older BASIC dialects, and in a handful of features that only Visual Basic has. The C# reference is at C# and .NET and the Java reference starts at Java Getting Started.

The surprises at a glance

Surprise Rule Page

Names ignore case

Copies, copies and COPIES are the same identifier, and so are Console.WriteLine and console.writeline. Two names that differ only in case in the same scope clash.

Program Structure and Code Conventions

Statements end at the end of the line

There are no semicolons. A statement continues on the next line after an operator, a comma or an opening bracket (implicit continuation since Visual Basic 10), or after an explicit _. A colon puts several statements on a line.

Program Structure and Code Conventions

Option Strict is off in a new project

The compiler allows implicit narrowing and late binding unless Option Strict On is set, in the file or in the project.

Option Statements

Array declarations give the upper bound

Dim shelf(3) As String creates four elements, indexed 0 to 3.

Arrays

= assigns and compares

The meaning depends on the position: a statement that starts with a target assigns; inside an expression it compares. There is no ==.

Operators and Expressions

/ and \ are different operators

/ is floating-point division, \ is integer division, Mod is the remainder.

Operators and Expressions

^ is exponentiation, and gives a Double

2 ^ 3 is the Double 8. It is not exclusive-or; that is Xor.

Operators and Expressions

& concatenates

Use & to join strings. + also works on strings, but is addition when both operands are numbers, so avoid it for text.

Strings and Text

And and Or do not short-circuit

Both operands are always evaluated. AndAlso and OrElse are the short-circuit forms, and the ones to use in conditions. And, Or, Xor and Not are also the bitwise operators on integers.

Control Flow

Nothing is a default value, not only null

For a reference type it is the null reference; for a value type it is the default (0, False, a zeroed structure).

Nothing and Nullable Value Types

True is -1 as a number

CInt(True) is -1, although Convert.ToInt32(True) is 1. Booleans print as True and False.

Type Conversions

Arithmetic overflow throws

Integer arithmetic is checked by default (the C# default is unchecked).

Numbers and Math

Module, Handles, My and XML literals

Features that only Visual Basic has: a type with only shared members and no static keyword, event handling by name with Handles, an application-wide helper namespace, and XML written directly in the code.

see the sections below

Names, lines and defaults

Names ignore case

The compiler treats identifiers case-insensitively, as BASIC always did. A variable declared as copies can be used as COPIES, and console.writeline is Console.WriteLine. In Visual Studio the editor rewrites each use to the case of the declaration, so in practice you rarely type the variants by accident, and the habit of naming a field Title and a parameter title that C# programmers rely on still works, because the two live in different scopes and Me.Title disambiguates them. What does not work is a same-scope pair that differs only in case.

Option Strict On

Module Program

    Sub Main()
        Dim line As String = "Reference"
        Dim Line As Integer = 3
        Console.WriteLine(line)
    End Sub

End Module
Program.vb(7,13): error BC30288: Local variable 'Line' is already declared in the current block.

Case-insensitivity has a consequence for type names too. A local variable named host is refused when the same method also uses the type Host (BC32000: Local variable 'host' cannot be referred to before it is declared), as met on Visual Basic and .NET. The usual cure is the same as in any language: a different name. Naming conventions for Visual Basic (PascalCase for types and members, camelCase for locals and parameters, no Hungarian prefixes) are on XML Documentation, Analyzers and Code Style.

Several surprises in one program

The next program shows case-insensitive names, upper-bound array declarations, = as assignment and comparison, Nothing as a default, and the meaning of one As clause for several names.

Option Strict On

Module Program

    Structure Point2D
        Public X As Integer
        Public Y As Integer
    End Structure

    Sub Main()
        Console.WriteLine("-- names ignore case")
        Dim copies As Integer = 3
        COPIES += 1
        Console.WriteLine(Copies)
        console.writeline("still Console.WriteLine")

        Console.WriteLine("-- array declarations give the upper bound")
        Dim shelf(3) As String
        Console.WriteLine(shelf.Length & " " & shelf.GetUpperBound(0) & " " & UBound(shelf))
        Dim grid(2, 4) As Integer
        Console.WriteLine(grid.GetLength(0) & " x " & grid.GetLength(1))
        Dim shelfSlots = New String(3) {}
        Console.WriteLine(shelfSlots.Length)

        Console.WriteLine("-- = assigns in a statement and compares in an expression")
        Dim a As Integer = 5
        Dim b As Boolean = a = 5
        Dim c As Boolean = (a = 6)
        Console.WriteLine(b & " " & c)

        Console.WriteLine("-- Nothing is the default value of any type")
        Dim count As Integer = Nothing
        Dim flag As Boolean = Nothing
        Dim title As String = Nothing
        Dim origin As Point2D = Nothing
        Console.WriteLine(count & " " & flag & " [" & title & "] " & origin.X & "," & origin.Y)
        Dim empty As String = ""
        Console.WriteLine((empty = Nothing) & " " & (empty Is Nothing) & " " & String.IsNullOrEmpty(empty))

        Console.WriteLine("-- one As clause types every name in the list")
        Dim low, high As Integer
        Console.WriteLine(low.GetType().Name & " " & high.GetType().Name)
    End Sub

End Module
-- names ignore case
4
still Console.WriteLine
-- array declarations give the upper bound
4 3 3
3 x 5
4
-- = assigns in a statement and compares in an expression
True False
-- Nothing is the default value of any type
0 False [] 0,0
True False True
-- one As clause types every name in the list
Int32 Int32

Points to take from the output:

  • Dim shelf(3) As String holds 4 elements: Length is 4 and the upper bound is 3. The number is the upper bound, the opposite of a C# or Java new string[3]. Dim grid(2, 4) is a 3 by 5 array. The initializer form New String(3) {} follows the same rule. Because the declaration is an upper bound, a loop is written For i = 0 To shelf.Length - 1 or For i = 0 To UBound(shelf), and an off-by-one is a classic mistake in code ported from other languages (see Arrays).

  • The statement COPIES += 1 is an assignment; the same = in Dim b As Boolean = a = 5 first compares a with 5 and then assigns the result to b. There is no way to assign inside an expression, so the C# idiom while line = reader.ReadLine( != null) has no direct form; Visual Basic reads the line first and tests it, or uses Do ... Loop Until.

  • Nothing assigned to an Integer, a Boolean or a structure gives the default value, not a null reference. The last line of that block is a classic trap: comparing an empty string with Nothing using = gives True, because the string comparison treats a null string and an empty string as equal, while the reference test Is Nothing says False. Use String.IsNullOrEmpty when you mean either.

  • In Dim low, high As Integer both names are Integer, unlike VB6 where only the last one was typed.

Lines, continuation and comments

A statement ends at the end of the physical line. A long expression can be broken after an operator, a comma, an opening bracket or brace, a ., and several keywords without any marker (implicit line continuation, Visual Basic 10); anywhere else a trailing underscore continues the line, and since Visual Basic 16 a comment is allowed after it. Comments start with an apostrophe and run to the end of the line (Rem is an old alternative); there is no block comment. ''' starts an XML documentation comment. A colon separates statements on one line, which the examples of this reference avoid except for a very short pair. A single-line If ... Then ... Else ... needs no End If; a block If does, and its ElseIf is one word. The line-oriented grammar also means that braces appear only in array and collection initializers ({10, 20, 30}) and in object initializers (With {.Name = "Reference"}), never around blocks.

Another handful of small differences

Option Strict On

Module Program

    Sub Main()
        Dim copies As Integer? = Nothing
        Console.WriteLine("[" & (copies = 1).ToString() & "]")
        If copies = 1 Then
            Console.WriteLine("equal")
        ElseIf copies <> 1 Then
            Console.WriteLine("different")
        Else
            Console.WriteLine("neither: a comparison with Nothing is Nothing, not False")
        End If
        Console.WriteLine(copies.HasValue & " " & copies.GetValueOrDefault())

        Dim text As String = "a" & vbTab & "b"
        Console.WriteLine(text.Length)
        Dim quote As String = "She said ""hi"""
        Console.WriteLine(quote)
        Dim letter As Char = "x"c
        Console.WriteLine(letter)
        Dim items As Integer() = {10, 20, 30}
        Console.WriteLine(items(1))
        Dim w As Long = 5L : Dim r As Double = 1.5R : Dim m As Decimal = 2.5D : Dim s As Single = 1.5F
        Console.WriteLine(w & " " & r & " " & m & " " & s)
    End Sub

End Module
[]
neither: a comparison with Nothing is Nothing, not False
False 0
3
She said "hi"
x
20
5 1.5 2.5 1.5

Five more points from it. A comparison with a nullable value that holds Nothing is itself Nothing (a null Boolean?), and an If treats it as false, so both copies = 1 and copies <> 1 were false above and the Else ran: this three-valued logic is different from C#, where == on a null nullable gives false and != gives true. String literals have no backslash escapes: a double quote is doubled (""), and the control characters are the constants vbTab, vbCrLf, vbNewLine or Environment.NewLine. A one-character Char literal carries the suffix c ("x"c), because "x" is a string. Indexing an array or calling a method both use parentheses, so items(1) is the second element, and the same brackets are used for generic type arguments with the keyword Of (List(Of String)). Literal suffixes are letters (L, UI, F, R, D) rather than C#'s L, u, f, d, m.

Numbers and operators

Option Strict On

Module Program

    Function Loud(label As String, value As Boolean) As Boolean
        Console.WriteLine("  evaluated " & label)
        Return value
    End Function

    Sub Main()
        Console.WriteLine("-- division")
        Console.WriteLine(7 / 2)        ' Double
        Console.WriteLine(7 \ 2)        ' integer division
        Console.WriteLine(7 Mod 2)
        Console.WriteLine(-7 \ 2)       ' truncates toward zero
        Console.WriteLine((7 / 2).GetType().Name & " " & (7 \ 2).GetType().Name)

        Console.WriteLine("-- power is always Double")
        Console.WriteLine((2 ^ 3).GetType().Name & " " & (2 ^ 3))
        Dim cube As Integer = CInt(2 ^ 3)
        Console.WriteLine(cube)

        Console.WriteLine("-- & concatenates, + adds when both are numbers")
        Console.WriteLine("Copies: " & 3 & 4)
        Console.WriteLine(3 + 4)

        Console.WriteLine("-- And / Or do not short-circuit")
        Dim ignored1 As Boolean = Loud("left", False) And Loud("right", True)
        Dim ignored2 As Boolean = Loud("left", False) AndAlso Loud("right", True)
        Console.WriteLine(ignored1 = ignored2)

        Console.WriteLine("-- True is -1 when converted to a number")
        Console.WriteLine(CInt(True) & " " & CInt(False) & " " & CStr(True) & " " & Convert.ToInt32(True))

        Console.WriteLine("-- integer overflow is checked by default")
        Dim big As Integer = Integer.MaxValue
        Try
            big += 1
        Catch ex As OverflowException
            Console.WriteLine(ex.GetType().Name & ": " & ex.Message)
        End Try
    End Sub

End Module
-- division
3.5
3
1
-3
Double Int32
-- power is always Double
Double 8
8
-- & concatenates, + adds when both are numbers
Copies: 34
7
-- And / Or do not short-circuit
  evaluated left
  evaluated right
  evaluated left
True
-- True is -1 when converted to a number
-1 0 True 1
-- integer overflow is checked by default
OverflowException: Arithmetic operation resulted in an overflow.

What each block teaches:

  • Division. 7 / 2 is the Double 3.5 even for two integers, so dividing integers never loses the fraction by accident; 7 \ 2 is the integer 3 and -7 \ 2 is -3 (it truncates toward zero). Dividing by a zero variable with \ or Mod throws DivideByZeroException, while 7 / zero prints Infinity and throws nothing, because it is floating-point; a literal zero (7 \ 0) is refused at compile time (BC30542, division by zero occurred while evaluating this expression). See Numbers and Math.

  • Power. 2 ^ 3 is a Double; to get an integer, convert (CInt(2 ^ 3)). ^ binds tighter than the unary minus on its left (-2 ^ 2 is -4, checked), and the operator is left-associative, so 2 ^ 3 ^ 2 is 64, not 512 (checked). The precedence ladder is on Operators and Expressions.

  • Strings. "Copies: " & 3 & 4 is Copies: 34: & always converts both sides to strings. 3 + 4 is 7. Mixing a string and a number with + is allowed only when Option Strict is off, where the compiler tries to convert the text to a number and can fail at run time; with Option Strict On "Total: " + 3 does not compile (BC30512, no implicit conversion from 'String' to 'Double').

  • Logic. The output shows And evaluating right although left was already False; AndAlso stopped after the left operand. In conditions that guard against Nothing (If x IsNot Nothing AndAlso x.Length > 0) only the short-circuit forms are correct. On integers, And, Or, Xor and Not are bitwise; the same word is the logical operator when both operands are Boolean.

  • Booleans. CInt(True) is -1 (all bits set, the VB6 convention kept for compatibility) while Convert.ToInt32(True) is 1. Do not use the numeric value of a Boolean for anything.

  • Overflow. Adding 1 to an Integer variable that holds Integer.MaxValue throws OverflowException in Visual Basic (a constant expression such as Integer.MaxValue + 1 is refused at compile time instead). C# wraps around silently unless checked is used. The project property RemoveIntegerChecks turns checking off (see Projects, MSBuild and the dotnet CLI).

Bitwise and comparison operators next to C#

Option Strict On

Module Program

    Sub Main()
        Dim flags As Integer = 5                      ' binary 101
        Console.WriteLine(flags And 3)                ' bitwise on integers (C# &)
        Console.WriteLine(flags Or 2)                 ' (C# |)
        Console.WriteLine(flags Xor 1)                ' (C# ^)
        Console.WriteLine(Not flags)                  ' bitwise complement (C# ~)
        Console.WriteLine(flags << 2)                 ' shifts exist since VB 7.1
        Console.WriteLine((flags And 1) = 1)          ' parentheses: = binds tighter than And
        Console.WriteLine("shelf" = "SHELF")          ' Option Compare Binary: case-sensitive
        Console.WriteLine("shelf" <> "Shelf")         ' <> is inequality (C# !=)
        Dim a As Object = "x"
        Dim b As Object = a
        Console.WriteLine((a Is b) & " " & (a IsNot Nothing))   ' reference equality: Is / IsNot
        Console.WriteLine(-7 Mod 3)                   ' remainder keeps the sign of the dividend
        Console.WriteLine(Math.Pow(2, 10))            ' same as 2 ^ 10
    End Sub

End Module
1
7
4
-6
20
True
False
True
True True
-1
1024

In flags And 1 = 1 the comparison binds tighter than And, so without the parentheses the compiler reads flags And (1 = 1) and refuses it under Option Strict On (BC30512: it cannot convert a Boolean to an Integer); the parentheses are the fix. String comparison with = is case-sensitive under the default Option Compare Binary; Option Compare Text makes it case-insensitive for the whole file.

Visual Basic’s own constructs

Modules, shared members and the missing static

C# has static class; Java has static members; Visual Basic has the Module. Every member of a module is implicitly shared, nobody can create an instance of it, and its members can be called without the module’s name from anywhere in the same namespace, which makes a module the natural home for helper functions and for Sub Main. Shared marks a static member of an ordinary class, and Static (a different word) declares a local variable that keeps its value between calls. The program below puts the common C# and Java constructs next to their Visual Basic form; every comment names the construct it replaces.

Option Strict On

Imports System.IO

' abstract class            -> MustInherit            virtual / override -> Overridable / Overrides
Public MustInherit Class CatalogItem
    Public Property Title As String
    Public Shared Count As Integer                              ' static field -> Shared

    Protected Sub New(title As String)
        Me.Title = title                                        ' this -> Me
        Count += 1
    End Sub

    Public MustOverride ReadOnly Property Kind As String
    Public Overridable Function Describe() As String
        Return Kind & ": " & Title
    End Function
End Class

Public Class Book
    Inherits CatalogItem                                        ' class Book : CatalogItem
    Public Sub New(title As String)
        MyBase.New(title)                                       ' base(title) -> MyBase.New
    End Sub
    Public Overrides ReadOnly Property Kind As String
        Get
            Return "Book"
        End Get
    End Property
End Class

Public NotInheritable Class Magazine                            ' sealed -> NotInheritable
    Inherits CatalogItem
    Friend ReadOnly Issue As Integer                            ' internal readonly -> Friend ReadOnly
    Public Sub New(title As String, issue As Integer)
        MyBase.New(title)
        Me.Issue = issue
    End Sub
    Public Overrides ReadOnly Property Kind As String
        Get
            Return "Magazine"
        End Get
    End Property
    Public Overrides Function Describe() As String
        Return MyBase.Describe() & " #" & Issue
    End Function
End Class

' static class -> Module: every member is shared, no instance can be created
Module Toolbox

    Private ReadOnly Gate As New Object()

    Iterator Function Countdown(start As Integer) As IEnumerable(Of Integer)   ' yield return -> Yield
        For i As Integer = start To 1 Step -1
            Yield i
        Next
    End Function

    Function Grade(copies As Integer) As String                 ' switch -> Select Case
        Select Case copies
            Case 0
                Return "none"
            Case 1 To 3
                Return "few"
            Case Is > 3
                Return "many"
            Case Else
                Return "invalid"
        End Select
    End Function

    Function Visits() As Integer
        Static calls As Integer                                  ' static local -> Static
        SyncLock Gate                                            ' lock (gate) -> SyncLock
            calls += 1
        End SyncLock
        Return calls
    End Function

    Function Sum(ParamArray values As Integer()) As Integer      ' params -> ParamArray
        Return values.Sum()
    End Function

    Sub Swap(ByRef a As Integer, ByRef b As Integer)             ' ref / out -> ByRef
        Dim t As Integer = a
        a = b
        b = t
    End Sub
End Module

Module Program

    Sub Main()
        Dim items As New List(Of CatalogItem) From {New Book("Atlas"), New Magazine("Nature", 7)}

        For Each item As CatalogItem In items                                 ' foreach -> For Each
            Dim asMagazine As Magazine = TryCast(item, Magazine)              ' as -> TryCast
            Console.WriteLine(item.Describe() & If(TypeOf item Is Book, " (book)", "") &
                              If(asMagazine IsNot Nothing, " issue " & asMagazine.Issue, ""))
        Next

        Dim nickname As String = Nothing
        Console.WriteLine(If(nickname, "anonymous"))                          ' ?? -> If(a, b)
        Console.WriteLine(If(CatalogItem.Count > 1, "many", "one"))           ' ?: -> If(c, a, b)
        Console.WriteLine(Toolbox.Grade(2) & " " & Toolbox.Grade(9) & " " & Toolbox.Grade(-1))
        Console.WriteLine(String.Join(",", Toolbox.Countdown(3)))
        Console.WriteLine(Toolbox.Visits() & Toolbox.Visits())
        Console.WriteLine(Toolbox.Sum(1, 2, 3))

        Dim x As Integer = 1, y As Integer = 2
        Toolbox.Swap(x, y)
        Console.WriteLine(x & " " & y)

        Dim titles = From item In items                                        ' LINQ query syntax
                     Where item.Title.Length > 5
                     Select item.Title.ToUpperInvariant()
        Console.WriteLine(String.Join(";", titles))
        Dim twice As Func(Of Integer, Integer) = Function(n) n * 2            ' (n) => n * 2
        Console.WriteLine(twice(21))

        Using writer As New StringWriter()                                     ' using (var w = ...) { }
            writer.Write("disposed at End Using")
            Console.WriteLine(writer.ToString())
        End Using
    End Sub

End Module
Book: Atlas (book)
Magazine: Nature #7 issue 7
anonymous
many
few many invalid
3,2,1
12
6
2 1
NATURE
42
disposed at End Using

Things worth noticing in that program. Module Toolbox has a Private ReadOnly Gate field and no Shared keyword anywhere: inside a module everything is shared. Visits() printed 12 for two calls because the Static local calls survives between calls (the two numbers are printed one after the other without a separator because & joins them). Select Case never falls through: each Case ends the statement, so there is no break; a Case can list values (Case 1, 3, 5), a range (1 To 3) or a comparison (Is > 3). TypeOf item Is Book is the type test, TryCast is the as operator (it returns Nothing when the cast fails), and DirectCast and CType are the casts that throw. A method that returns a value uses Return; the older habit of assigning to the function’s name still compiles and is covered on Procedures: Sub and Function. The module pattern, Shared members and the Static statement are on Modules and Shared Members.

Events: Handles and WithEvents

C# attaches a handler with +=. Visual Basic offers that too (AddHandler) and a declarative form: a field declared WithEvents can have methods that say which of its events they handle, with a Handles clause. Windows Forms code generated by the designer uses the second form for every control. The program shows both.

Option Strict On

Public Class Loan
    Public Event Returned As EventHandler                      ' public event EventHandler Returned;

    Public Sub [Return]()
        RaiseEvent Returned(Me, EventArgs.Empty)                ' Returned?.Invoke(this, EventArgs.Empty)
    End Sub
End Class

Public Class Desk
    Private WithEvents _loan As New Loan()                      ' a field that raises events, handled by name

    Private Sub OnReturned(sender As Object, e As EventArgs) Handles _loan.Returned
        Console.WriteLine("Handles clause: loan returned")
    End Sub

    Public Sub Run()
        Dim extra As EventHandler = Sub(s, e) Console.WriteLine("AddHandler lambda: loan returned")
        AddHandler _loan.Returned, extra                         ' _loan.Returned += extra
        _loan.Return()
        RemoveHandler _loan.Returned, extra                      ' _loan.Returned -= extra
        _loan.Return()
    End Sub
End Class

Module Program

    Sub Main()
        Call New Desk().Run()

        Dim title As String = "Atlas"
        Dim book As XElement = <book year="1999">
                                   <title><%= title %></title>
                                   <author>Ada</author>
                               </book>
        Console.WriteLine(book.@year & " " & book.<title>.Value)
        Console.WriteLine(book)
    End Sub

End Module
Handles clause: loan returned
AddHandler lambda: loan returned
Handles clause: loan returned
1999 Atlas
<book year="1999">
  <title>Atlas</title>
  <author>Ada</author>
</book>

Four details. The event is raised with RaiseEvent, which does the null check that C# writes as ?.Invoke. A method that is named Return must be written in square brackets ([Return]), because Return is a keyword; the brackets let any keyword be used as an identifier. The statement Public Event Returned(sender As Object, e As EventArgs) is also legal, but declares a new delegate type (Loan.ReturnedEventHandler) instead of using EventHandler; assigning an EventHandler to such an event is then error BC30311 (the value cannot be converted), which is why the example declares Public Event Returned As EventHandler. Finally, the XML element is a literal: it is a XElement created by the compiler, with the embedded expression <%= title %>, the attribute accessed as book.@year and the child elements as book.<title>. XML literals and the axis properties exist only in Visual Basic and are covered on XML Literals and Axis Properties; events are covered on Events and Handlers.

My and default instances

My is a Visual Basic-only namespace of shortcuts to the application, the computer, the user, resources and settings. On .NET Framework it exists in every project; on .NET it is present only in Windows desktop projects whose MyType is set, and is empty in a console project (see The My Namespace for the details and the alternatives). A related feature of Windows Forms projects, the default instance, lets code call Form1.Show() on the class name as if it were a variable; it exists to ease VB6 migrations and is covered on Windows Forms Applications.

Side-by-side maps

To C# and Java

Concept Visual Basic C# Java

Import and namespace

Imports System.IO, Namespace Shelf

using System.IO;, namespace Shelf

import java.io.*;, package shelf;

Inheritance and interfaces

Inherits Base, Implements IFoo

: Base, IFoo

extends Base implements IFoo

Abstract, sealed

MustInherit, MustOverride, NotInheritable, NotOverridable

abstract, sealed

abstract, final

Virtual and override

Overridable, Overrides, Shadows (hide)

virtual, override, new

methods are virtual; @Override

Static and module

Shared, Module, Static (local)

static, static class

static

Visibility

Public, Private, Protected, Friend, Protected Friend, Private Protected

public, private, protected, internal, protected internal, private protected

public, private, protected, package-private

Constants and read-only

Const, ReadOnly

const, readonly

final, static final

This and base

Me, MyBase, MyClass (non-virtual call)

this, base

this, super

Null and the null test

Nothing, x Is Nothing, x IsNot Nothing

null, x == null, x is not null

null, x == null

Type test and casts

TypeOf x Is T, TryCast(x, T), DirectCast(x, T), CType(x, T)

x is T, x as T, (T)x

x instanceof T, (T) x

Conditional and coalescing

If(c, a, b), If(a, b)

c ? a : b, a ?? b

c ? a : b, Objects.requireNonNullElse

Logical operators

Not, AndAlso, OrElse, And, Or, Xor

!, &&, ||, &, |, ^

!, &&, ||, &, |, ^

Equality

= and <> for values, Is and IsNot for references

== and !=, ReferenceEquals

.equals, and == for references

Loops and switch

For, For Each, While, Do ... Loop, Select Case

for, foreach, while, do, switch

for, enhanced for, while, do, switch

Exceptions

Try ... Catch ex As T When cond ... Finally, Throw

try, catch (T ex) when (cond), finally, throw

try, catch, finally, throw

Disposal and locking

Using, SyncLock

using, lock

try-with-resources, synchronized

Generics

List(Of T), (Of T As IFoo), (Of T As {Class, New})

List<T>, where T : IFoo, where T : class, new()

List<T>, T extends IFoo

Lambdas and delegates

Function(x) x * 2, Sub(x) Console.WriteLine(x)

x => x * 2

x -> x * 2

Properties

Property X As Integer (auto-implemented), Property Y As Integer with Get/Set

int X { get; set; }

getters and setters

Parameters

ByVal (default), ByRef, Optional x As Integer = 0, ParamArray

by value (default), ref, out, int x = 0, params

by value only, overloads, varargs

Strings

&, "a ""quoted"" b", $"{x}", vbCrLf

+, "a \"quoted\" b", $"{x}", \r\n

+, backslash escapes, String.format, \r\n

Comments

', ''' (XML doc)

//, ///

//, /** */

Type of an expression

GetType(T), x.GetType(), TypeName(x)

typeof(T), x.GetType()

T.class, x.getClass()

The C# page of this reference for the matching topics: Control Flow in C#, Classes and Objects in C# and LINQ in C#; for Java: Java Classes and Objects, Java Inheritance and Polymorphism and Java Exceptions. What does not have a counterpart: Visual Basic has no pattern-matching switch, no record, no required members, no primary constructors and no top-level statements, and C# has no XML literals, Handles, WithEvents, My, Select Case ... Case Is or non-short-circuit boolean operators.

To VB6 and VBA

Visual Basic .NET is not VB6 with a new runtime; it kept the surface and changed the semantics. The legacy dialects are covered on The VB Family: VB6, VBA and VBScript, and the migration path on Migrating Legacy Visual Basic. The shortest map from VB6 and VBA to the current language:

VB6 or VBA Visual Basic on .NET 10

Variant

Object, or better a typed variable; with Option Strict On there is no implicit Variant behaviour.

Integer (16 bits), Long (32 bits)

Short is 16 bits, Integer is 32 bits, Long is 64 bits. Check every declaration when porting.

Currency

Decimal.

Type ... End Type

Structure ... End Structure, or a class.

Set obj = ..., Let

Plain assignment; Set and Let are gone.

On Error GoTo, Resume Next

Try ... Catch ... Finally; On Error still compiles but is not for new code, see Unstructured Error Handling: On Error.

ByRef is the default

ByVal is the default.

Option Base, arbitrary lower bounds

Always zero-based.

Collection object

List(Of T) and Dictionary(Of TKey, TValue); a Collection class still exists in the runtime library but is rarely the best choice.

Wend, GoSub, control arrays

End While; no GoSub; no control arrays.

Debug.Print

Debug.WriteLine.

DefInt and the other Def statements

Removed. Declare every variable.

Dim a, b As Integer

Both are Integer; in VB6 only b was.

Which Visual Basic feature replaces …​?

The flowchart answers the question programmers ask most often when they move to Visual Basic: this construct in my language, what is the Visual Basic way? It covers the replacements that need a different keyword or a different pattern, not the ones that are the same word with another case.

flowchart LR Q["Which Visual Basic feature replaces ...?"] --> C["a C# or Java construct"] Q --> V["a VB6 or VBA construct"] C --> C1["switch statement or expression"] C1 --> V1["Select Case with Case 1 To 3 and Case Is greater than 3, no fall-through"] C --> C2["static class"] C2 --> V2["Module: all members shared; Shared for a member of a normal class"] C --> C3["yield return"] C3 --> V3["Iterator Function with Yield"] C --> C4["null-coalescing and conditional operators"] C4 --> V4["If(a, b) and If(c, x, y)"] C --> C5["dynamic"] C5 --> V5["late binding on Object, only with Option Strict Off: see Early and Late Binding"] C --> C6["using and lock"] C6 --> V6["Using ... End Using and SyncLock ... End SyncLock"] C --> C7["event += handler"] C7 --> V7["AddHandler, or WithEvents plus a Handles clause"] C --> C8["records, pattern matching, primary constructors"] C8 --> V8["No equivalent syntax: write a class or Structure, use TypeOf and Select Case, a normal constructor"] C --> C9["top-level statements"] C9 --> V9["Module with Sub Main"] C --> C10["internal, sealed, abstract, virtual"] C10 --> V10["Friend, NotInheritable, MustInherit, Overridable"] C --> C11["out and ref parameters"] C11 --> V11["ByRef, or return a tuple"] V --> W1["Variant"] W1 --> X1["Object, or a typed variable"] V --> W2["On Error GoTo"] W2 --> X2["Try Catch Finally"] V --> W3["Type ... End Type"] W3 --> X3["Structure"] V --> W4["Collection and Scripting.Dictionary"] W4 --> X4["List Of T and Dictionary Of TKey TValue"] V --> W5["Set obj = ..."] W5 --> X5["Plain assignment"]

Where the books are outdated for this page

The two books that this section draws on compare Visual Basic with its older dialects, and some of what they say about those comparisons is out of date or incorrect. The corrected rules, in short:

  • Visual Basic .NET is not source-compatible with VB6. The books' remark that "backward compatibility" eases the transition applies to habits, not to code, and there are many semantic differences (type sizes, default parameter passing, the object model, the library).

  • Of the keywords above, the ones the books describe as VB6 features that the language lacks, or gained, should be checked against the lists on The VB Family: VB6, VBA and VBScript: VB6 had Implements but no class inheritance, VB6 did have Public, Private and Friend, and VBScript 5.0 and later has Class ... End Class.

  • The books present Option Strict Off code and the + operator for strings as normal. Neither is recommended: set Option Strict On and use &.

  • They describe Visual Basic as a language that is still gaining features and a runtime that is part of the .NET Framework; see History, Versions and Language Strategy.

Common mistakes for newcomers

Mistake What happens, and the fix

Dim shelf(3) As String expected to have three elements

It has four. Use Dim shelf(2) As String for three, or New String() {...} with an initializer.

If flags And 1 = 1 Then

Parsed as flags And (1 = 1); with Option Strict On it does not compile. Write (flags And 1) = 1.

If x IsNot Nothing And x.Length > 0 Then

Both operands are evaluated, so a null x throws NullReferenceException. Use AndAlso.

Dim name As String = Nothing then If name = "" Then expecting false

Nothing = "" is True for strings. Test with String.IsNullOrEmpty.

"Total: " + 3 + 4

Not text concatenation; with Option Strict On it does not compile, with it off it can throw a conversion exception. Use &.

2 ^ 3 assigned to an Integer

^ returns a Double. Convert, or use integer multiplication for small integer powers.

Adding 1 to a variable holding Integer.MaxValue expected to wrap

OverflowException. Overflow is checked by default.

A nullable n = 1 and n <> 1 both treated as exhaustive

With Nothing both are false. Test n.HasValue first, or compare with n.GetValueOrDefault().

A local and a type with the same name in different case (host and Host)

Names are case-insensitive. Rename the local.

Public Event Done(sender As Object, e As EventArgs) and then using an EventHandler

The event has its own delegate type. Declare Public Event Done As EventHandler.

Calling My.Computer from a console project

My is empty on .NET outside Windows desktop projects. Use the base class library.

Using a keyword as a name (Dim date As Date)

BC30183, keyword not valid as an identifier. Use another name or put the identifier in brackets.