Arrays

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. Examples that are meant to fail say so and show the messages the compiler printed. The project root namespace of the scratch project was Shelf; the SDK adds the project-level imports (System, System.Collections.Generic, System.Linq and others), which is why Console, List(Of T) and Enumerable methods such as Skip need no Imports line.

An array is a fixed-size, ordered block of elements of one type, stored contiguously and reached by an integer index. In .NET every array is an object whose type derives from System.Array, so an array variable holds a reference, its elements are allocated on the heap, and the usual tools of the framework (Length, Clone, GetEnumerator, the static Array.Sort and friends, LINQ) apply to it. Visual Basic adds a small set of its own syntax on top: the declaration by upper bound, ReDim, Erase, ParamArray, and the runtime functions UBound and LBound. The single most important sentence of this page is the first rule of the table below.

Rule Consequence

Dim a(4) As Integer gives the upper bound

The array has 5 elements, indexed 0 to 4. C# new int[4] has 4. Length is always upper bound plus one.

The lower bound is always 0

Dim a(0 To 4) is accepted; Dim a(1 To 5) is BC32059. Option Base does not exist in Visual Basic .NET.

Arrays are reference types

Assignment and parameter passing copy the reference; Clone, Array.Copy and ToArray copy elements.

An array has a fixed size

ReDim and Array.Resize allocate a new array and copy; other variables keep pointing at the old one. Use List(Of T) for a collection that grows.

The rank (number of dimensions) is part of the type

Integer() is one dimension, Integer(,) two, Integer()() is an array of arrays. ReDim cannot change the rank (BC30415).

Covariance for reference types

Book() converts to CatalogItem(); storing the wrong subtype fails at run time with ArrayTypeMismatchException.

Elements start as the default value

0, False, Nothing (for references and String), or a zeroed structure.

For collections that grow, shrink or are searched by key see Collections; for For Each and the other loops see Control Flow; for how ParamArray fits among the other parameter kinds see Procedures: Sub and Function; for generic variance, which also governs the array interfaces, see Generics. The C# counterpart is part of Collections and Iterators (C#).

Declaring arrays

A declaration names the variable and either the upper bound in parentheses or the array type with empty parentheses. The parentheses can follow the variable (Dim a(4) As Integer, Dim names() As String) or the type (Dim names As String()); both are the same type, and the second is the style that reads best because the type stays in one piece. The bound may be any Integer expression that is known at run time, not only a constant. Elements are initialised to the default value of the element type.

An initializer lists the elements in braces. Three spellings are equivalent: Dim a As Integer() = {10, 20, 30}, the same with New Integer() {10, 20, 30}, and the inferred form Dim a = {10, 20, 30}, whose element type is the dominant type of the elements (the one type to which all elements convert). The inferred form is the one used most in new code; it needs no repetition of the type, and with Option Strict On it stays type-safe. Array literals and local type inference arrived together in Visual Basic 9 (Visual Studio 2008); the compiler accepts them at LangVersion 9.

Option Strict On

Module Program
    Sub Main()
        ' The number in parentheses is the UPPER BOUND, not the length
        Dim shelves(4) As Integer
        Console.WriteLine("Length = " & shelves.Length)
        Console.WriteLine("LBound = " & LBound(shelves) & ", UBound = " & UBound(shelves))
        Console.WriteLine("GetLowerBound(0) = " & shelves.GetLowerBound(0) & ", GetUpperBound(0) = " & shelves.GetUpperBound(0))

        ' Elements start with the default value of the element type
        Dim titles(2) As String
        Dim flags(1) As Boolean
        Console.WriteLine("titles(0) Is Nothing: " & (titles(0) Is Nothing))
        Console.WriteLine("flags(1) = " & flags(1))

        ' Several equivalent ways to say "these three numbers"
        Dim a As Integer() = {10, 20, 30}
        Dim b() As Integer = {10, 20, 30}
        Dim c As Integer() = New Integer() {10, 20, 30}
        Dim d As Integer() = New Integer(2) {10, 20, 30}
        Dim e = {10, 20, 30}
        Console.WriteLine(a.Length & " " & b.Length & " " & c.Length & " " & d.Length & " " & e.Length)
        Console.WriteLine("e is " & e.GetType().Name)

        ' Array literal inference picks the dominant type
        Dim mixed = {1, 2.5, 3}
        Dim wide = {1, 2L, 3}
        Console.WriteLine(mixed.GetType().Name & " " & wide.GetType().Name)

        ' An empty array is not Nothing
        Dim none(-1) As String
        Dim alsoNone As String() = {}
        Dim missing As String() = Nothing
        Console.WriteLine(none.Length & " " & alsoNone.Length & " " & (missing Is Nothing))
        Console.WriteLine("Array.Empty is shared: " & Object.ReferenceEquals(Array.Empty(Of String)(), Array.Empty(Of String)()))

        ' Arrays are reference types: assignment copies the reference, Clone copies the elements
        Dim copyRef = a
        Dim copyElements = CType(a.Clone(), Integer())
        a(0) = 99
        Console.WriteLine(copyRef(0) & " " & copyElements(0))
    End Sub
End Module
Length = 5
LBound = 0, UBound = 4
GetLowerBound(0) = 0, GetUpperBound(0) = 4
titles(0) Is Nothing: True
flags(1) = False
3 3 3 3 3
e is Int32[]
Double[] Int64[]
0 0 True
Array.Empty is shared: True
99 10

What to take from the output:

  • Length is 5 for Dim shelves(4), UBound is 4, LBound is 0. GetUpperBound(0) takes the dimension number, which is zero-based; the runtime function UBound(array, dimension) takes a one-based dimension number. Mixing the two conventions is a classic slip.

  • {1, 2.5, 3} is a Double() and {1, 2L, 3} an Int64() (Long() in Visual Basic): each element converts widening to the dominant type.

  • Dim none(-1) As String is a valid empty array (upper bound -1, length 0); so is {} with a declared type. Neither is Nothing. An array variable that is declared without a size, or set to Nothing, holds no array at all and Length on it throws NullReferenceException. Prefer Array.Empty(Of T)(), which returns one shared empty array and allocates nothing.

  • Dim copyRef = a is a second name for the same array, which is why changing a(0) shows through copyRef. Clone returns Object, so CType (or DirectCast) back to the array type is needed under Option Strict On.

The compiler checks declarations. Each message below is the exact text printed by the SDK; the programs are shown so that you can see which line produced which message.

Option Strict On

Module Program
    Sub Main()
        Dim tooMany(1) As Integer = {1, 2, 3}
        Dim tooFew(3) As Integer = {1, 2}
        Dim bad As Integer() = New Integer(2) {1, 2}
        Dim noBraces As Integer() = New Integer(3)
        Dim m As Integer(,) = {{1, 2}, {3}}
        Dim wrongRank As Integer() = New Integer(,) {{1}}
    End Sub
End Module
Program.vb(5,13): error BC30672: Explicit initialization is not permitted for arrays declared with explicit bounds.
Program.vb(6,13): error BC30672: Explicit initialization is not permitted for arrays declared with explicit bounds.
Program.vb(7,47): error BC30567: Array initializer is missing 1 elements.
Program.vb(8,49): error BC30057: Too many arguments to 'Public Sub New()'.
Program.vb(9,40): error BC30567: Array initializer is missing 1 elements.
Program.vb(10,38): error BC30414: Value of type 'Integer(*,*)' cannot be converted to 'Integer()' because the array types have different numbers of dimensions.

Source: Dim Statement

Option Strict On

Module Program
    Sub Main()
        Dim a As Integer(4)
        Dim b() As Integer(4)
    End Sub
End Module
Program.vb(5,26): error BC30638: Array bounds cannot appear in type specifiers.
Program.vb(6,28): error BC30638: Array bounds cannot appear in type specifiers.
Option Strict On

Module Program
    Sub Main()
        Dim ok(0 To 4) As Integer
        Console.WriteLine(ok.Length)
        Dim vb6(1 To 5) As Integer
    End Sub
End Module
Program.vb(7,17): error BC32059: Array lower bounds can be only '0'.
Option Strict On

Module Program
    Sub Main()
        Dim a = {1, "two", 3.0}
        Dim b = {Nothing, Nothing}
        Dim c As Integer() = {1, 2.5}
    End Sub
End Module
Program.vb(5,17): error BC36915: Cannot infer an element type because more than one type is possible. Specifying the type of the array might correct this error.
Program.vb(6,17): error BC36914: Cannot infer an element type, and Option Strict On does not allow 'Object' to be assumed. Specifying the type of the array might correct this error.
Program.vb(7,34): error BC30512: Option Strict On disallows implicit conversions from 'Double' to 'Integer'.
Option Strict On

Module Program
    Const Primes As Integer() = {2, 3, 5}
    Const Names() As String = {"a"}
    Sub Main()
    End Sub
End Module
Program.vb(4,11): error BC30424: Constants must be of an intrinsic or enumerated type, not a class, structure, type parameter, or array type.
Program.vb(5,11): error BC30424: Constants must be of an intrinsic or enumerated type, not a class, structure, type parameter, or array type.
Code Message (abridged) Cause and fix

BC30672

Explicit initialization is not permitted for arrays declared with explicit bounds

Dim a(1) As Integer = {1, 2}. Either give the bound or the initializer: Dim a As Integer() = {1, 2} or Dim a(1) As Integer.

BC30567 / BC30568

Array initializer is missing N elements / has N too many elements

New Integer(2) {1, 2}: with New T(n) {...} the initializer must have exactly n + 1 elements.

BC30057

Too many arguments to Public Sub New()

New Integer(3) without braces is a constructor call, not an array creation. Array creation always has {} after the bound, even when it is empty: New Integer(3) {}.

BC30414

Value of type Integer(*,*) cannot be converted to Integer() because the array types have different numbers of dimensions

Rank mismatch. A one-dimensional initializer for a two-dimensional variable, or the other way round.

BC30638

Array bounds cannot appear in type specifiers

Dim a As Integer(4): the bound goes on the variable (Dim a(4) As Integer), not on the type.

BC32059

Array lower bounds can be only 0

Dim a(1 To 5). Visual Basic .NET dropped the VB6 syntax for non-zero bounds. 0 To 4 is still accepted and means the same as 4.

BC36915

Cannot infer an element type because more than one type is possible

{1, "two", 3.0} has no dominant type. Name the type: Dim a As Object() = ... or, better, do not mix types.

BC36914

Cannot infer an element type, and Option Strict On does not allow Object to be assumed

{Nothing, Nothing}. Name the element type.

BC30424

Constants must be of an intrinsic or enumerated type, not a class, structure, type parameter, or array type

An array cannot be Const. Use ReadOnly (the variable cannot be reassigned, but the elements can still change) or an ImmutableArray(Of T) / IReadOnlyList(Of T) field.

Two further facts about declarations are worth knowing. First, Dim a(n) As Integer with a non-constant n compiles; the check happens when the line runs (a negative n other than -1 throws OverflowException, see Failure modes and exceptions). Second, the runtime cost of an array is its elements only: there is no per-element object, so Integer() of a million elements is 4 MB, whereas a List(Of Object) of boxed integers is several times larger.

Indexing and iterating

Elements are reached with parentheses, a(i), never square brackets: a[0] is a syntax error (BC30203) because [ begins an escaped identifier in Visual Basic. Parentheses are also used for method calls and for indexers, which is why a(i) and f(i) look the same; the compiler resolves the meaning from the declaration. Three loops cover almost every need:

Option Strict On

Module Program
    Sub Main()
        Dim isbns As String() = {"9780132350884", "9780134494166", "9781491950357"}

        For i = 0 To isbns.Length - 1
            Console.WriteLine(i & ": " & isbns(i))
        Next

        For i = LBound(isbns) To UBound(isbns)
            Console.Write(isbns(i).Substring(0, 3) & " ")
        Next
        Console.WriteLine()

        For Each isbn In isbns
            Console.Write(isbn.Length & " ")
        Next
        Console.WriteLine()

        ' Backwards
        For i = isbns.Length - 1 To 0 Step -1
            Console.Write(isbns(i).Substring(10) & " ")
        Next
        Console.WriteLine()

        ' Off by one: the loop bound is the upper bound, not the length
        Try
            For i = 0 To isbns.Length
                Console.Write(i & " ")
            Next
            Console.WriteLine()
            Dim last = isbns(isbns.Length)
            Console.WriteLine(last)
        Catch ex As IndexOutOfRangeException
            Console.WriteLine(ex.GetType().Name & ": " & ex.Message)
        End Try

        ' For Each over a literal
        Dim total = 0
        For Each n In {1, 2, 3, 4}
            total += n
        Next
        Console.WriteLine("total = " & total)

        ' There is no negative index and no range syntax: use arithmetic, Array.Copy or LINQ
        Console.WriteLine("last = " & isbns(isbns.Length - 1))
        Console.WriteLine("last = " & isbns.Last())
        Console.WriteLine("middle = " & String.Join(",", isbns.Skip(1).Take(1)))
    End Sub
End Module
0: 9780132350884
1: 9780134494166
2: 9781491950357
978 978 978
13 13 13
357 166 884
0 1 2 3
IndexOutOfRangeException: Index was outside the bounds of the array.
total = 10
last = 9781491950357
last = 9781491950357
middle = 9780134494166
  • A counted For loop runs to isbns.Length - 1 or to UBound(isbns); the loop bound is the upper bound, so To isbns.Length is one step too far. The loop in the example printed 0 1 2 3 for a three-element array and only the following read, isbns(isbns.Length), threw IndexOutOfRangeException.

  • For Each needs no index and no bound, so it cannot go off by one. It reads the elements in index order, and for a multidimensional array in row-major order (last index fastest).

  • Backwards iteration uses Step -1. When elements are removed while walking an array-like structure, walk backwards.

The type of the For Each variable can be inferred (For Each isbn In isbns gives String) or declared (For Each isbn As String In isbns). Declare it when the sequence is weakly typed, for example Object().

Arrays have no negative indexes and no range or "index from end" syntax. C# a[^1] and a[1..3] have no counterpart: the compiler stops with BC30201 and BC30203.

Option Strict On

Module Program
    Sub Main()
        Dim a = {1, 2, 3}
        Dim s = a(1..2)
    End Sub
End Module
Program.vb(6,21): error BC30203: Identifier expected.
Option Strict On

Module Program
    Sub Main()
        Dim a = {1, 2, 3}
        Dim t = a(^1)
    End Sub
End Module
Program.vb(6,19): error BC30201: Expression expected.

The replacements are arithmetic (a(a.Length - 1)), the LINQ methods Last(), Skip and Take, Array.Copy and the views in Slices, views and copies.

The For Each variable is a copy

Assigning to the loop variable of For Each compiles in Visual Basic (C# rejects it with CS1656), and does what you would expect: it changes the variable, not the array. For an array of structures that is a trap, because the loop variable holds a copy of the element and a write to one of its fields is lost.

Option Strict On

Module Program
    Structure Counter
        Public Value As Integer
    End Structure

    Sub Main()
        Dim numbers = {1, 2, 3}
        For Each n In numbers
            n = n * 2                       ' compiles: changes the loop variable only
        Next
        Console.WriteLine(String.Join(",", numbers))

        Dim counters(1) As Counter
        For Each c In counters
            c.Value = 1                     ' compiles: changes a copy
        Next
        Console.WriteLine(counters(0).Value & " " & counters(1).Value)

        counters(0).Value = 5               ' an array element is a variable, not a copy
        For i = 0 To counters.Length - 1
            counters(i).Value += 1
        Next
        Console.WriteLine(counters(0).Value & " " & counters(1).Value)

        Dim list As New List(Of Counter) From {New Counter()}
        Dim one = list(0)
        one.Value = 9
        Console.WriteLine("list(0).Value = " & list(0).Value)
    End Sub
End Module
1,2,3
0 0
6 1
list(0).Value = 0

Use an index loop, counters(i).Value += 1, when you need to change array elements in place. An array element is a variable, so unlike list(0).Value = 5 on a List(Of Counter) (BC30068, "Expression is a value and therefore cannot be the target of an assignment") the assignment to a field of counters(0) compiles and works. For arrays of structures that are updated often, this is a real advantage over List(Of T); see Structures and Value Types.

ReDim, ReDim Preserve and Erase

An array does not change size. ReDim creates a new array of the requested bounds and stores the reference in the variable; without Preserve the new array is empty (all default values), with Preserve the compiler emits a copy of the old elements into it. A shorter array loses the elements above the new bound. Because it is a new array, everything that still refers to the old array keeps seeing the old contents.

Option Strict On

Module Program
    Sub Show(label As String, values As Integer())
        If values Is Nothing Then
            Console.WriteLine(label & ": Nothing")
        Else
            Console.WriteLine(label & ": [" & String.Join(", ", values) & "] (UBound " & UBound(values) & ")")
        End If
    End Sub

    Sub Main()
        Dim counts(2) As Integer
        counts(0) = 1 : counts(1) = 2 : counts(2) = 3
        Dim before = counts

        ReDim counts(4)                 ' new empty array: the old values are lost
        Show("after ReDim", counts)

        counts(0) = 7 : counts(1) = 8
        ReDim Preserve counts(5)        ' copies the old elements into a new, larger array
        Show("after ReDim Preserve (grow)", counts)

        ReDim Preserve counts(1)        ' shrinking drops the elements above the new bound
        Show("after ReDim Preserve (shrink)", counts)

        Show("the array another variable still refers to", before)
        Console.WriteLine("same object: " & Object.ReferenceEquals(before, counts))

        ' Array.Resize does the same through a ByRef parameter and also allocates a new array
        Dim viaResize = counts
        Array.Resize(counts, 4)
        Show("after Array.Resize", counts)
        Show("viaResize (old array)", viaResize)

        ' Erase sets the variable to Nothing
        Erase counts
        Show("after Erase", counts)

        ' A multidimensional array can change only its last dimension with Preserve
        Dim grid(1, 1) As Integer
        grid(1, 1) = 5
        ReDim Preserve grid(1, 3)
        Console.WriteLine("grid(1, 1) = " & grid(1, 1) & ", GetLength(1) = " & grid.GetLength(1))
        Try
            ReDim Preserve grid(3, 3)
        Catch ex As ArrayTypeMismatchException
            Console.WriteLine(ex.GetType().Name & ": " & ex.Message)
        End Try

        ' ReDim cannot change the rank
        Dim resizable() As Integer
        ReDim resizable(3)
        Console.WriteLine("resizable: " & resizable.Length)
    End Sub
End Module
after ReDim: [0, 0, 0, 0, 0] (UBound 4)
after ReDim Preserve (grow): [7, 8, 0, 0, 0, 0] (UBound 5)
after ReDim Preserve (shrink): [7, 8] (UBound 1)
the array another variable still refers to: [1, 2, 3] (UBound 2)
same object: False
after Array.Resize: [7, 8, 0, 0] (UBound 3)
viaResize (old array): [7, 8] (UBound 1)
after Erase: Nothing
grid(1, 1) = 5, GetLength(1) = 4
ArrayTypeMismatchException: Attempted to access an element as a type incompatible with the array.
resizable: 4

Source: ReDim Statement

The rules, all of which the example above exercises:

Statement Rule

ReDim a(n)

Allocates a new array with upper bound n; old elements are lost. The variable must already be declared as an array (Dim a() As Integer).

ReDim Preserve a(n)

Allocates a new array and copies Math.Min(old.Length, new.Length) elements. Cost is proportional to the length; calling it once per added element makes a loop quadratic.

ReDim Preserve m(i, j)

Only the last dimension may change. Changing another dimension throws ArrayTypeMismatchException when the statement runs (not a compile error).

ReDim with As

Not allowed: ReDim data(n) As Byte is BC30811. Declare the variable first, then ReDim it.

ReDim changing the rank

BC30415. ReDim a(1, 2) on a one-dimensional variable does not compile.

Array.Resize(a, n)

The framework equivalent with a length (not an upper bound) as argument, taking the variable ByRef. It also allocates a new array and copies. Array.Resize(a, 0) gives an empty array.

Erase a

Sets the array variable to Nothing; the elements become garbage when nothing else refers to the array. It does not zero the elements of an array that other variables still use; use Array.Clear for that. On a non-array variable it is BC30049.

Note the unit difference between the two ways to resize: ReDim Preserve counts(5) produces six elements and Array.Resize(counts, 4) produces four. Erase rarely matters in .NET: the garbage collector reclaims arrays that are no longer referenced, and setting a local variable to Nothing does not make that happen earlier in release builds.

Option Strict On

Module Program
    Sub Main()
        Dim a As Integer() = {1, 2, 3}
        ReDim a(5)
        ReDim Preserve a(1, 2)
        Dim n As Integer = 3
        ReDim n(2)
        Dim s = "text"
        Erase s
    End Sub
End Module
Program.vb(7,24): error BC30415: 'ReDim' cannot change the number of dimensions of an array.
Program.vb(9,15): error BC30049: 'Redim' statement requires an array.
Program.vb(11,15): error BC30049: 'Erase' statement requires an array.
Option Strict On

Module Program
    Sub Main()
        Dim data() As Byte
        Dim n As Integer = 4
        ReDim data(n) As Byte
        Console.WriteLine(data.Length)
    End Sub
End Module
Program.vb(7,23): error BC30811: 'ReDim' statements can no longer be used to declare array variables.

ReDim is a statement left over from VB6 and it survives only because it is concise. In new code that needs a growing sequence, use List(Of T) (amortised O(1) Add; its ToArray() gives an array when an API needs one).

Multidimensional and jagged arrays

Visual Basic has two ways to model a table. A rectangular array, Dim m(2, 3) As Integer, is a single object with a fixed number of rows and columns; its type is Integer(,). A jagged array, Dim j(2)() As Integer, is an array whose elements are themselves arrays, each of which can have a different length; its type is Integer()(). Both declarations list upper bounds: m(2, 3) is 3 rows by 4 columns.

Option Strict On

Module Program
    Sub Main()
        ' Rectangular: (2, 3) means 3 rows by 4 columns
        Dim matrix(2, 3) As Integer
        For r = 0 To matrix.GetUpperBound(0)
            For c = 0 To matrix.GetUpperBound(1)
                matrix(r, c) = r * 10 + c
            Next
        Next
        Console.WriteLine("Rank = " & matrix.Rank & ", Length = " & matrix.Length)
        Console.WriteLine("GetLength(0) = " & matrix.GetLength(0) & ", GetLength(1) = " & matrix.GetLength(1))
        Console.WriteLine("UBound(matrix, 1) = " & UBound(matrix, 1) & ", UBound(matrix, 2) = " & UBound(matrix, 2))

        ' For Each visits a rectangular array in row-major order
        For Each v In matrix
            Console.Write(v & " ")
        Next
        Console.WriteLine()

        ' Initializers nest braces, one level per dimension
        Dim table As Integer(,) = {{1, 2, 3}, {4, 5, 6}}
        Console.WriteLine("table(1, 2) = " & table(1, 2))

        ' Jagged: an array whose elements are arrays, each with its own length
        Dim jagged(2)() As Integer
        jagged(0) = New Integer() {1}
        jagged(1) = New Integer() {1, 2, 3}
        jagged(2) = Array.Empty(Of Integer)()
        For i = 0 To jagged.Length - 1
            Console.WriteLine("row " & i & " has " & jagged(i).Length & " elements: " & String.Join(",", jagged(i)))
        Next

        Dim triangle = {New Integer() {1}, New Integer() {1, 1}, New Integer() {1, 2, 1}}
        Console.WriteLine("triangle type: " & triangle.GetType().Name & ", triangle(2)(1) = " & triangle(2)(1))

        Dim literal As Integer()() = {({1, 2}), ({3})}
        Console.WriteLine("literal(0)(1) = " & literal(0)(1) & ", literal(1).Length = " & literal(1).Length)

        ' Clone is shallow: the rows are shared
        Dim shallow = CType(jagged.Clone(), Integer()())
        shallow(1)(0) = 100
        Console.WriteLine("jagged(1)(0) after changing the clone: " & jagged(1)(0))

        ' Array.Copy and Clone work on the whole multidimensional array
        Dim copy = CType(table.Clone(), Integer(,))
        copy(0, 0) = -1
        Console.WriteLine("table(0, 0) = " & table(0, 0) & ", copy(0, 0) = " & copy(0, 0))
    End Sub
End Module
Rank = 2, Length = 12
GetLength(0) = 3, GetLength(1) = 4
UBound(matrix, 1) = 2, UBound(matrix, 2) = 3
0 1 2 3 10 11 12 13 20 21 22 23
table(1, 2) = 6
row 0 has 1 elements: 1
row 1 has 3 elements: 1,2,3
row 2 has 0 elements:
triangle type: Int32[][], triangle(2)(1) = 2
literal(0)(1) = 2, literal(1).Length = 1
jagged(1)(0) after changing the clone: 100
table(0, 0) = 1, copy(0, 0) = -1
Rectangular Integer(,) Jagged Integer()()

Declaration

Dim m(2, 3) As Integer

Dim j(2)() As Integer, then j(i) = New Integer() {...}

Element access

m(r, c)

j(r)(c)

Shape

all rows the same length

each row has its own length (and can be Nothing until assigned)

Length

rows times columns (12)

number of rows (3)

GetLength(d) / UBound(m, d+1)

length / upper bound of dimension d (zero-based GetLength, one-based UBound)

j.GetLength(0); use j(i).Length for a row

For Each

visits every element, row-major

visits each row array

Clone

copies all elements

copies the row references (shallow): rows are shared with the clone

Memory

one block

one object per row, usually slower to allocate, often faster to read in a hot loop

Resizing

ReDim Preserve on the last dimension only

each row is a separate array; rows can be replaced independently

Points that cause bugs:

  • UBound(matrix, 1) is the upper bound of the first dimension (rows); the column dimension is 2. UBound returns a bound, not a length, so a loop For c = 0 To UBound(matrix, 2) is right and For c = 0 To UBound(matrix, 2) - 1 is one short. matrix.GetLength(1) and matrix.GetUpperBound(1) use dimension numbers starting at zero.

  • Dim t As Integer(,) = {{1, 2, 3}, {4, 5, 6}} nests one pair of braces per dimension. A ragged initializer such as {{1, 2}, {3}} for a rectangular array is rejected by the compiler (BC30567).

  • A jagged literal needs each row marked as an array: {({1, 2}), ({3})} with a declared target type, or {New Integer() {1, 2}, New Integer() {3}} to let the compiler infer Integer()().

  • An index count must match the rank: m(1) on a two-dimensional array is BC30105 ("Number of indices is less than the number of dimensions").

Option Strict On

Module Program
    Sub Main()
        Dim a = {1, 2, 3}
        Dim b = {1, 2, 3}
        If a = b Then Console.WriteLine("equal")
        Dim m(2, 2) As Integer
        m(1) = 5
        Dim r As Integer() = m
        ReDim m(2)
        Dim x As Integer() = a.AsSpan()
    End Sub
End Module
Program.vb(7,12): error BC31080: Operator '=' is not defined for types 'Integer()' and 'Integer()'. Use 'Is' operator to compare two reference types.
Program.vb(9,10): error BC30105: Number of indices is less than the number of dimensions of the indexed array.
Program.vb(10,30): error BC30414: Value of type 'Integer(*,*)' cannot be converted to 'Integer()' because the array types have different numbers of dimensions.
Program.vb(11,15): error BC30415: 'ReDim' cannot change the number of dimensions of an array.
Program.vb(12,30): error BC30311: Value of type 'Span(Of Integer)' cannot be converted to 'Integer()'.

The messages show the typical misuses in one program: = on arrays is BC31080 (arrays have no =; use Is for identity or SequenceEqual for contents), BC30105 for the missing index, BC30414 for the rank mismatch, BC30415 for ReDim with the wrong rank, and BC30311 for the AsSpan() result that cannot be stored in an array variable.

Passing arrays and ParamArray

An array parameter receives a copy of the reference. The callee can change elements, which the caller sees; the callee can re-point its own parameter at a different array, which the caller does not see unless the parameter is ByRef. This is the same rule as for every reference type (see Procedures: Sub and Function). Returning an array hands the caller a reference too; if the array is the internal state of an object, return a copy (Clone/ToArray) or a read-only wrapper (Array.AsReadOnly, IReadOnlyList(Of T), ImmutableArray(Of T)), otherwise callers can change your object from outside.

ParamArray lets callers pass the elements as separate arguments; inside the procedure the parameter is an ordinary array. The compiler packs the arguments into a new array on every call (allocation), and passing an existing array hands it over unchanged.

Option Strict On

Module Program
    Sub DoubleAll(values As Integer())      ' ByVal: the reference is copied, the elements are shared
        For i = 0 To values.Length - 1
            values(i) *= 2
        Next
    End Sub

    Sub Replace(ByRef values As Integer())  ' ByRef: the caller's variable can be re-pointed
        values = New Integer() {0, 0}
    End Sub

    Sub Rebind(values As Integer())         ' ByVal: re-pointing the parameter is invisible to the caller
        values = New Integer() {0, 0}
    End Sub

    Function Sum(ParamArray numbers As Integer()) As Integer
        Dim total = 0
        For Each n In numbers
            total += n
        Next
        Return total
    End Function

    Function Describe(label As String, ParamArray parts As String()) As String
        If parts Is Nothing Then Return label & ": Nothing"
        Return label & ": " & parts.Length & " part(s)"
    End Function

    Function Squares(count As Integer) As Integer()
        Dim result(count - 1) As Integer
        For i = 0 To count - 1
            result(i) = i * i
        Next
        Return result
    End Function

    Sub Main()
        Dim data = {1, 2, 3}
        DoubleAll(data)
        Console.WriteLine(String.Join(",", data))
        Rebind(data)
        Console.WriteLine(String.Join(",", data))
        Replace(data)
        Console.WriteLine(String.Join(",", data))

        Console.WriteLine(Sum() & " " & Sum(5) & " " & Sum(1, 2, 3) & " " & Sum(New Integer() {4, 5}))
        Console.WriteLine(Describe("none"))
        Console.WriteLine(Describe("two", "a", "b"))
        Console.WriteLine(Describe("array", New String() {"x", "y", "z"}))
        Console.WriteLine(Describe("nothing", Nothing))
        Console.WriteLine(String.Join(",", Squares(5)))
    End Sub
End Module
2,4,6
2,4,6
0,0
0 5 6 9
none: 0 part(s)
two: 2 part(s)
array: 3 part(s)
nothing: Nothing
0,1,4,9,16

Source: ParamArray

Code Message (abridged) Meaning

BC30192

End of parameter list expected. Cannot define parameters after a paramarray parameter

ParamArray must be the last parameter, and there can be only one.

BC30050

ParamArray parameter must be an array

ParamArray value As Integer is wrong; write As Integer().

BC30667

ParamArray parameters must be declared ByVal

ByRef ParamArray is not allowed.

BC30642

Optional and ParamArray cannot be combined

A ParamArray is already optional: it accepts zero arguments.

Option Strict On

Module Program
    Sub Add(ParamArray numbers As Integer())
    End Sub

    Sub Main()
        Add(1, 2, "three")
        Add({1, 2}, 3)
    End Sub
End Module
Program.vb(8,19): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Integer'.
Program.vb(9,13): error BC30311: Value of type 'Integer()' cannot be converted to 'Integer'.

Details of the call forms: with Sum() the callee receives an empty array, not Nothing; Describe("nothing", Nothing) passes Nothing as the array itself, so the callee has to check for it. When the element type is Object the packing rule has a twist: an argument whose type converts to Object() is passed as the array itself, anything else becomes one element.

Option Strict On

Module Program
    Function Count(ParamArray values As Object()) As Integer
        Return values.Length
    End Function

    Sub Main()
        Dim ints As Integer() = {1, 2, 3}
        Dim strs As String() = {"a", "b"}
        Console.WriteLine("Integer() -> " & Count(ints))
        Console.WriteLine("String()  -> " & Count(strs))
        Console.WriteLine("Object()  -> " & Count(CType(strs, Object())))
        Console.WriteLine("nothing   -> " & Count())
    End Sub
End Module
Integer() -> 1
String()  -> 2
Object()  -> 2
nothing   -> 0

Source: ParamArray

An Integer() is not an Object() (arrays of value types are not covariant), so it arrives as a single element, while a String() is an Object() and arrives unpacked. C# 13 added params ReadOnlySpan<T> and params collections of other types; Visual Basic 17.13 declares ParamArray with an array type only (BC30050 otherwise).

The Array class, runtime functions and LINQ

All arrays inherit the members of System.Array. The ones that matter in daily work:

Member Notes

Length, LongLength, Rank

Length is the total element count over all dimensions.

GetLength(d), GetLowerBound(d), GetUpperBound(d)

Zero-based dimension numbers. GetUpperBound of an empty array is -1.

Array.Sort(a), Sort(a, comparer), Sort(a, Comparison), Sort(keys, items)

In-place, unstable introspective sort. Uses IComparable unless a comparer is given. Nothing elements sort first. Throws InvalidOperationException (inner ArgumentException) for mixed types and RankException for multidimensional input.

Array.Reverse(a), Array.Clear(a, i, n), Array.Fill(a, v)

In place.

Array.IndexOf, LastIndexOf, BinarySearch

IndexOf returns -1 when absent (not LBound - 1); BinarySearch needs sorted input and returns the bitwise complement of the insertion point when absent.

Array.Exists, Find, FindIndex, FindAll, TrueForAll, ForEach, ConvertAll

Take Predicate, Action or Converter delegates; in Visual Basic pass a lambda or AddressOf.

Array.Copy, CopyTo, Clone, Array.Resize

Copying needs a destination that is long enough (ArgumentException, "Destination array was not long enough").

Array.Empty(Of T)(), Array.AsReadOnly(a), Array.MaxLength

An empty shared array, a read-only wrapper, and the largest array size (2147483591 on this runtime).

Array.CreateInstance(type, lengths, lowerBounds)

The only way to create an array with a non-zero lower bound; see Non-zero bounds and VB6.

Option Strict On

Module Program
    Sub Main()
        Dim titles As String() = {"Refactoring", "Clean Code", "Domain-Driven Design", "Working Effectively with Legacy Code"}
        Dim copy As String() = CType(titles.Clone(), String())

        Array.Sort(copy)
        Console.WriteLine("Sort: " & String.Join(" | ", copy))

        Array.Sort(copy, Function(left, right) left.Length.CompareTo(right.Length))
        Console.WriteLine("Sort by length: " & String.Join(" | ", copy))

        Array.Reverse(copy)
        Console.WriteLine("Reverse: " & String.Join(" | ", copy))

        Console.WriteLine("IndexOf Clean Code = " & Array.IndexOf(titles, "Clean Code"))
        Console.WriteLine("IndexOf missing    = " & Array.IndexOf(titles, "Missing"))

        Dim sorted As Integer() = {3, 8, 15, 21, 40}
        Console.WriteLine("BinarySearch 15 = " & Array.BinarySearch(sorted, 15))
        Console.WriteLine("BinarySearch 16 = " & Array.BinarySearch(sorted, 16) & " (complement of insertion point " & (Not Array.BinarySearch(sorted, 16)) & ")")

        Console.WriteLine("Exists > 30: " & Array.Exists(sorted, Function(n) n > 30))
        Console.WriteLine("Find > 10: " & Array.Find(sorted, Function(n) n > 10))
        Console.WriteLine("FindIndex > 10: " & Array.FindIndex(sorted, Function(n) n > 10))
        Console.WriteLine("FindAll even: " & String.Join(",", Array.FindAll(sorted, Function(n) n Mod 2 = 0)))
        Console.WriteLine("TrueForAll > 0: " & Array.TrueForAll(sorted, Function(n) n > 0))

        Dim lengths = Array.ConvertAll(titles, Function(t) t.Length)
        Console.WriteLine("ConvertAll: " & String.Join(",", lengths))

        Dim buffer(5) As Integer
        Array.Fill(buffer, 7)
        Array.Clear(buffer, 1, 2)
        Console.WriteLine("Fill + Clear: " & String.Join(",", buffer))

        Dim target(7) As Integer
        Array.Copy(sorted, 1, target, 3, 3)
        Console.WriteLine("Copy: " & String.Join(",", target))
        sorted.CopyTo(target, 0)
        Console.WriteLine("CopyTo: " & String.Join(",", target))

        ' Two parallel arrays sorted together
        Dim keys As Integer() = {3, 1, 2}
        Dim items As String() = {"three", "one", "two"}
        Array.Sort(keys, items)
        Console.WriteLine("Sort(keys, items): " & String.Join(",", items))

        Array.ForEach(sorted, Sub(n) Console.Write(n & ";"))
        Console.WriteLine()

        ' Comparing arrays: = is not defined, Is compares references
        Dim x = {1, 2, 3}
        Dim y = {1, 2, 3}
        Console.WriteLine("Is: " & (x Is y) & ", Equals: " & x.Equals(y) & ", SequenceEqual: " & x.SequenceEqual(y))
        Console.WriteLine("Array.MaxLength = " & Array.MaxLength)
    End Sub
End Module
Sort: Clean Code | Domain-Driven Design | Refactoring | Working Effectively with Legacy Code
Sort by length: Clean Code | Refactoring | Domain-Driven Design | Working Effectively with Legacy Code
Reverse: Working Effectively with Legacy Code | Domain-Driven Design | Refactoring | Clean Code
IndexOf Clean Code = 1
IndexOf missing    = -1
BinarySearch 15 = 2
BinarySearch 16 = -4 (complement of insertion point 3)
Exists > 30: True
Find > 10: 15
FindIndex > 10: 2
FindAll even: 8,40
TrueForAll > 0: True
ConvertAll: 11,10,20,36
Fill + Clear: 7,0,0,7,7,7
Copy: 0,0,0,8,15,21,0,0
CopyTo: 3,8,15,21,40,21,0,0
Sort(keys, items): one,two,three
3;8;15;21;40;
Is: False, Equals: False, SequenceEqual: True
Array.MaxLength = 2147483591

Source: Array Class

Option Strict On

Module Program
    Sub Main()
        Dim words As String() = {"alpha", "Beta", "gamma", "Delta"}
        Array.Sort(words)
        Console.WriteLine("default order:     " & String.Join(",", words))
        Array.Sort(words, StringComparer.Ordinal)
        Console.WriteLine("ordinal:           " & String.Join(",", words))
        Array.Sort(words, StringComparer.OrdinalIgnoreCase)
        Console.WriteLine("ignoring case:     " & String.Join(",", words))

        Dim withNothing As String() = {"b", Nothing, "a"}
        Array.Sort(withNothing)
        Console.WriteLine("Nothing sorts first: " & (withNothing(0) Is Nothing))

        Dim mixed As Object() = {1, "two"}
        Try
            Array.Sort(mixed)
        Catch ex As InvalidOperationException
            Console.WriteLine(ex.GetType().Name & ": " & ex.InnerException.GetType().Name)
        End Try

        ' Split and Join round-trip an array and a string
        Dim fields = "9780132350884;Refactoring;Fowler".Split(";"c)
        Console.WriteLine(fields.Length & " fields, last = " & fields(UBound(fields)))
    End Sub
End Module
default order:     alpha,Beta,Delta,gamma
ordinal:           Beta,Delta,alpha,gamma
ignoring case:     alpha,Beta,Delta,gamma
Nothing sorts first: True
InvalidOperationException: ArgumentException
3 fields, last = Fowler

Observations from the two programs:

  • Array.Sort(words) uses the current culture, so the default order of alpha, Beta, Delta, gamma depends on the machine’s culture (on the container the result was alpha, Beta, Delta, gamma). Pass StringComparer.Ordinal or StringComparer.OrdinalIgnoreCase when the order must be the same everywhere; this is the same rule as for string comparison in Strings and Text.

  • x Is y and x.Equals(y) are both reference comparisons for arrays, so two arrays with equal contents are not "equal"; SequenceEqual compares element by element, and StructuralComparisons.StructuralEqualityComparer does it for nested arrays too.

  • The lambda parameter names in Array.Sort(copy, Function(left, right) ...) are left and right on purpose. A Visual Basic lambda parameter may not hide a local variable of the enclosing method, even one declared later in the method: with a later Dim x = 1, the lambda parameter x is BC36641.

Option Strict On

Module Program
    Sub Main()
        Dim names As String() = {"b", "a"}
        Array.Sort(names, Function(x, y) String.CompareOrdinal(x, y))
        Dim x = 1
    End Sub
End Module
Program.vb(6,36): error BC36641: Lambda parameter 'x' hides a variable in an enclosing block, a previously defined range variable, or an implicitly declared variable in a query expression.

The Visual Basic runtime library also has array-related functions, kept for compatibility with VB6 code: UBound and LBound (namespace Microsoft.VisualBasic.Information), Strings.Join, Strings.Split and Strings.Filter. They work on arrays and are fine to keep, but the framework methods (String.Join, String.Split, Array.FindAll) are what a C# or F# reader of the same code base expects.

LINQ works on arrays because an array is an IEnumerable(Of T): Where, Select, OrderBy, ToArray, Sum, Any, Last, Skip, Take and Chunk are available without Imports System.Linq (the SDK adds it); see LINQ Query Expressions. LINQ methods return new sequences or arrays; none of them sorts or changes the source array.

Covariance and the interfaces of arrays

An array of a reference type converts implicitly to an array of any base type (array covariance, present since .NET 1.0). The conversion is unchecked at compile time: the compiler cannot know that the variable items of type CatalogItem() refers to a Book(), so a store of a Magazine compiles and fails when it runs.

Option Strict On

Public Class CatalogItem
    Public Property Title As String = ""
End Class

Public Class Book
    Inherits CatalogItem
End Class

Public Class Magazine
    Inherits CatalogItem
End Class

Module Program
    Sub Main()
        Dim books As Book() = {New Book() With {.Title = "Refactoring"}}

        ' Arrays of a reference type are covariant: Book() converts to CatalogItem()
        Dim items As CatalogItem() = books
        Console.WriteLine("same array: " & Object.ReferenceEquals(books, items))
        Console.WriteLine("runtime type: " & items.GetType().Name)

        Try
            items(0) = New Magazine() With {.Title = "Byte"}   ' compiles, fails at run time
        Catch ex As ArrayTypeMismatchException
            Console.WriteLine(ex.GetType().Name & ": " & ex.Message)
        End Try

        ' Arrays of value types are not covariant, but an array of String is an array of Object
        Dim objects As Object() = New String() {"a", "b"}
        Console.WriteLine("Object() from String(): " & objects.Length)
        Dim numbers As Integer() = {1, 2}
        Console.WriteLine("Integer() is Object(): " & (TypeOf CObj(numbers) Is Object()))

        ' An array also implements the generic collection interfaces
        Dim list As IList(Of Integer) = numbers
        Dim readOnlyView As IReadOnlyList(Of Integer) = numbers
        Console.WriteLine("IList.Count = " & list.Count & ", IsReadOnly = " & list.IsReadOnly)
        Console.WriteLine("IReadOnlyList(0) = " & readOnlyView(0))
        Try
            list.Add(3)
        Catch ex As NotSupportedException
            Console.WriteLine(ex.GetType().Name & ": " & ex.Message)
        End Try

        ' The safest read-only wrapper
        Dim wrapper = Array.AsReadOnly(numbers)
        numbers(0) = 100
        Console.WriteLine("wrapper(0) sees the change: " & wrapper(0))
    End Sub
End Module
same array: True
runtime type: Book[]
ArrayTypeMismatchException: Attempted to access an element as a type incompatible with the array.
Object() from String(): 2
Integer() is Object(): False
IList.Count = 2, IsReadOnly = True
IReadOnlyList(0) = 1
NotSupportedException: Collection was of a fixed size.
wrapper(0) sees the change: 100

Rules to remember:

  • Covariance applies only to reference types: Integer() is not an Object() (the output shows False), though String() is.

  • Every store into an array of a reference type costs a type check because of this. Declare arrays of sealed types (such as String) or use List(Of T) where the cost matters.

  • A one-dimensional array with a zero lower bound implements IList(Of T), IReadOnlyList(Of T), ICollection(Of T) and IEnumerable(Of T); the mutating members throw NotSupportedException ("Collection was of a fixed size"). Prefer IReadOnlyList(Of T) or IEnumerable(Of T) as parameter types for code that only reads: every array and list fits.

  • Array.AsReadOnly returns a view, not a copy: the wrapper sees later changes to the underlying array. For a snapshot, copy first.

Slices, views and copies

Visual Basic has no range operator and cannot declare Span(Of T) or ReadOnlySpan(Of T) in a local, field or parameter: they are ref structs, and the compiler reports the use as BC30668 ("is obsolete: Types with embedded references are not supported in this version of your compiler"). The index of a Span(Of T) cannot be assigned to either (BC30068). A span can still be created and consumed inside one expression, such as numbers.AsSpan().Slice(1, 2).ToArray() or Integer.Parse("123".AsSpan()) in the example below, but there is no way to keep one in a variable. The usable alternatives are:

Tool Semantics Use when

Array.Copy(src, i, dst, j, n)

copy of a range into an existing array

you own both arrays

ArraySegment(Of T)

view over a range of the same storage; writes go through

you pass part of a buffer to another method

Memory(Of T) (a.AsMemory(start, length))

view; a normal structure, so it can be stored in a field or a local

asynchronous code and long-lived windows

ArrayPool(Of T).Shared.Rent/Return

a reusable buffer, possibly longer than requested

hot paths that need scratch arrays

Skip(n).Take(m).ToArray(), Chunk(size)

copies, lazily evaluated until ToArray

readability matters more than speed

ImmutableArray(Of T)

immutable value wrapper over an array; Add returns a new array

shared, thread-safe, read-only data

Option Strict On

Module Program
    Sub Main()
        Dim numbers As Integer() = {1, 2, 3}
        Dim view As Span(Of Integer) = numbers.AsSpan()
        Console.WriteLine(view.Length)
    End Sub
End Module
Program.vb(6,21): error BC30668: 'Span(Of Integer)' is obsolete: 'Types with embedded references are not supported in this version of your compiler.'.
Option Strict On

Imports System.Buffers
Imports System.Collections.Immutable

Module Program
    Sub Main()
        Dim numbers As Integer() = {10, 20, 30, 40, 50}

        ' A copy of part of an array
        Dim part(2) As Integer
        Array.Copy(numbers, 1, part, 0, 3)
        part(0) = -1
        Console.WriteLine("copy:    " & String.Join(",", part) & " original: " & String.Join(",", numbers))

        ' A view over part of an array: ArraySegment shares the storage
        Dim segment As New ArraySegment(Of Integer)(numbers, 1, 3)
        segment(0) = 21                      ' writes through to numbers(1)
        Console.WriteLine("segment: " & String.Join(",", segment) & " original: " & String.Join(",", numbers))

        ' Memory(Of T) is a view that VB can store in a variable
        Dim memory As Memory(Of Integer) = numbers.AsMemory(2, 2)
        numbers(2) = 31
        Console.WriteLine("memory:  " & String.Join(",", memory.ToArray()) & " sees the change to numbers(2)")

        ' A span can be used inside one expression, never stored in a variable
        Console.WriteLine("span slice: " & String.Join(",", numbers.AsSpan().Slice(1, 2).ToArray()) & ", parsed from a span: " & Integer.Parse("123".AsSpan()))

        ' LINQ makes copies
        Dim page = numbers.Skip(1).Take(2).ToArray()
        Console.WriteLine("LINQ:    " & String.Join(",", page))
        For Each chunk In numbers.Chunk(2)
            Console.Write("[" & String.Join(",", chunk) & "] ")
        Next
        Console.WriteLine()

        ' Rent a scratch buffer instead of allocating one per call
        Dim rented = ArrayPool(Of Integer).Shared.Rent(16)
        Console.WriteLine("rented length >= 16: " & (rented.Length >= 16))
        ArrayPool(Of Integer).Shared.Return(rented)

        ' ImmutableArray is a value-type wrapper whose Add returns a new array
        Dim frozen = ImmutableArray.Create(1, 2, 3)
        Dim bigger = frozen.Add(4)
        Console.WriteLine("immutable: " & frozen.Length & " and " & bigger.Length)
        Dim unset As ImmutableArray(Of Integer) = Nothing
        Console.WriteLine("default ImmutableArray: IsDefault = " & unset.IsDefault)
        Try
            Console.WriteLine(unset.Length)
        Catch ex As NullReferenceException
            Console.WriteLine(ex.GetType().Name)
        End Try
    End Sub
End Module
copy:    -1,30,40 original: 10,20,30,40,50
segment: 21,30,40 original: 10,21,30,40,50
memory:  31,40 sees the change to numbers(2)
span slice: 21,31, parsed from a span: 123
LINQ:    21,31
[10,21] [31,40] [50]
rented length >= 16: True
immutable: 3 and 4
default ImmutableArray: IsDefault = True
NullReferenceException

Notes on the example: ImmutableArray(Of Integer) is a structure, so a variable that was never assigned (default) is not Nothing but an uninitialised wrapper: IsDefault is True and reading Length throws NullReferenceException. Check IsDefaultOrEmpty before using a field of that type that may not have been set. ArrayPool returns arrays that can be longer than the length you asked for; keep the length you need in a separate variable and call Return when you are done (with clearArray:=True for sensitive data).

Non-zero bounds and VB6

Visual Basic 6 and VBA allowed Dim a(1 To 5) and Option Base 1, so that the first element had any index. Visual Basic .NET removed both (array lower bounds were fixed at zero from version 7 so that arrays are the same in all .NET languages). The compiler keeps accepting 0 To n as a compatibility spelling and rejects other lower bounds with BC32059. Option Base is a syntax error (BC30206, "Option must be followed by Compare, Explicit, Infer, or Strict"):

Option Strict On
Option Base 1

Module Program
    Sub Main()
    End Sub
End Module
Program.vb(2,8): error BC30206: 'Option' must be followed by 'Compare', 'Explicit', 'Infer', or 'Strict'.

The framework can still create an array with a non-zero lower bound through Array.CreateInstance, because other languages and COM interop need them. Such an array is not an Integer() or String() in the Visual Basic sense: its type name is String[*], it cannot be cast to String() (InvalidCastException), and it has to be accessed with GetValue/SetValue:

Option Strict On

Module Program
    Sub Main()
        ' Dim a(0 To 4) is allowed: the lower bound is always zero
        Dim fine(0 To 4) As Integer
        Console.WriteLine("fine: " & fine.Length)

        ' System.Array can create arrays whose lower bound is not zero
        Dim lengths As Integer() = {3}
        Dim lowers As Integer() = {1}
        Dim oneBased As Array = Array.CreateInstance(GetType(String), lengths, lowers)
        oneBased.SetValue("first", 1)
        oneBased.SetValue("third", 3)
        Console.WriteLine("Type = " & oneBased.GetType().Name & ", Rank = " & oneBased.Rank)
        Console.WriteLine("LowerBound = " & oneBased.GetLowerBound(0) & ", UpperBound = " & oneBased.GetUpperBound(0))
        Console.WriteLine("Value at 3 = " & CStr(oneBased.GetValue(3)))
        Try
            Dim asStrings = CType(oneBased, String())
            Console.WriteLine(asStrings.Length)
        Catch ex As InvalidCastException
            Console.WriteLine(ex.GetType().Name)
        End Try
        Try
            oneBased.SetValue("zero", 0)
        Catch ex As IndexOutOfRangeException
            Console.WriteLine(ex.GetType().Name & ": " & ex.Message)
        End Try
    End Sub
End Module
fine: 5
Type = String[*], Rank = 1
LowerBound = 1, UpperBound = 3
Value at 3 = third
InvalidCastException
IndexOutOfRangeException: Index was outside the bounds of the array.

If migrated VB6 code relied on Option Base 1 or Dim a(1 To n), the safe migration is to allocate n + 1 elements and ignore element 0, or (better) to rewrite loops to start at 0; see Migrating Legacy Visual Basic and The VB Family: VB6, VBA and VBScript. VB6 Variant arrays, the Array() function and LBound/UBound on arrays of unknown bounds all become typed arrays or List(Of T) in .NET.

Failure modes and exceptions

Option Strict On

Module Program
    Sub Report(label As String, action As Action)
        Try
            action()
            Console.WriteLine(label & ": ok")
        Catch ex As Exception
            Console.WriteLine(label & ": " & ex.GetType().Name & " - " & ex.Message)
        End Try
    End Sub

    Sub Main()
        Dim missing As Integer() = Nothing
        Dim titles(1) As String
        Dim grid(1, 1) As Integer
        Dim size = -2

        Report("Length of Nothing", Sub() Console.WriteLine(missing.Length))
        Report("UBound of Nothing", Sub() Console.WriteLine(UBound(missing)))
        Report("element Is Nothing, then .Length", Sub() Console.WriteLine(titles(0).Length))
        Report("negative size", Sub()
                                    Dim a(size) As Integer
                                    Console.WriteLine(a.Length)
                                End Sub)
        Report("Integer.MaxValue as upper bound", Sub()
                                                      Dim a(Integer.MaxValue) As Byte
                                                      Console.WriteLine(a.Length)
                                                  End Sub)
        Report("Sort a 2-D array", Sub() Array.Sort(grid))
        Report("GetLength(2) on a 2-D array", Sub() Console.WriteLine(grid.GetLength(2)))
        Report("UBound dimension 3", Sub() Console.WriteLine(UBound(grid, 3)))
        Report("copy into a too-small array", Sub() Array.Copy({1, 2, 3}, New Integer(1) {}, 3))

        Dim empty(-1) As Integer
        Console.WriteLine("empty: Length = " & empty.Length & ", UBound = " & UBound(empty) & ", LBound = " & LBound(empty))
        Console.WriteLine("an array is an Array: " & (TypeOf titles Is Array) & ", base type " & titles.GetType().BaseType.Name)

        ' Value equality of arrays needs a structural comparer
        Dim a1 = {1, 2, 3}
        Dim a2 = {1, 2, 3}
        Dim comparer = System.Collections.StructuralComparisons.StructuralEqualityComparer
        Console.WriteLine("structural equality: " & comparer.Equals(a1, a2) & ", SequenceEqual: " & a1.SequenceEqual(a2))
    End Sub
End Module
Length of Nothing: NullReferenceException - Object reference not set to an instance of an object.
UBound of Nothing: ArgumentNullException - Value cannot be null. (Parameter 'Array')
element Is Nothing, then .Length: NullReferenceException - Object reference not set to an instance of an object.
negative size: OverflowException - Arithmetic operation resulted in an overflow.
Integer.MaxValue as upper bound: OverflowException - Arithmetic operation resulted in an overflow.
Sort a 2-D array: RankException - Only single dimension arrays are supported here.
GetLength(2) on a 2-D array: IndexOutOfRangeException - Array does not have that many dimensions.
UBound dimension 3: RankException - Argument 'Rank' is not valid for the array.
copy into a too-small array: ArgumentException - Destination array was not long enough. Check the destination index, length, and the array's lower bounds. (Parameter 'destinationArray')
empty: Length = 0, UBound = -1, LBound = 0
an array is an Array: True, base type Array
structural equality: True, SequenceEqual: True
Exception Typical cause Prevention

IndexOutOfRangeException

a(a.Length), a loop that runs To a.Length, GetLength(2) on a 2-D array

loop to Length - 1 or use For Each; check Length first

NullReferenceException

array variable is Nothing (never created, erased with Erase, returned as Nothing); an element of a String() or class array is Nothing

create arrays eagerly, return Array.Empty(Of T)() instead of Nothing

ArgumentNullException

UBound(Nothing), Array.Copy(Nothing, ...)

guard parameters

OverflowException

Dim a(-2), or an upper bound of Integer.MaxValue (length would overflow)

validate sizes that come from users

OutOfMemoryException

a size that fits Integer but not the available memory

bound sizes, process in chunks

ArrayTypeMismatchException

store of the wrong subtype through a covariant reference; ReDim Preserve on a non-last dimension

keep the declared and the runtime type the same

RankException

Array.Sort of a multidimensional array, UBound(m, 3) on a 2-D array

sort a jagged array or a flattened copy

ArgumentException

Array.Copy into a destination that is too short; mixed types in Array.Sort

compute lengths; use typed arrays

InvalidCastException

casting an Array.CreateInstance array with a non-zero lower bound to a typed array

use GetValue/SetValue

Option Strict On cannot stop any of these: they are run-time conditions. The only compile-time protection against indexing mistakes is to avoid raw indexes (For Each, LINQ) and to use unit tests with boundary cases (empty array, one element, last element).

A shelf example

The reference scenario of this section is a library catalogue; the example below keeps books in an array, spreads them over a rectangular array of shelf levels and slots, groups them into a jagged array by size and sorts a copy so that the caller’s order is untouched. Note the root-namespace trap that the first draft of this program hit: the project root namespace was Shelf, and because Visual Basic is case-insensitive a local variable named shelf (or Shelf) is BC30112, "is a namespace and cannot be used as an expression"; the loop variables are called level and slot.

Option Strict On

Public Class Book
    Public ReadOnly Property Title As String
    Public ReadOnly Property Pages As Integer

    Public Sub New(title As String, pages As Integer)
        Me.Title = title
        Me.Pages = pages
    End Sub

    Public Overrides Function ToString() As String
        Return Title & " (" & Pages & " pages)"
    End Function
End Class

Module Program
    Const LevelCount As Integer = 3
    Const SlotsPerLevel As Integer = 4

    ' Returns a new array; the caller owns it
    Function ByLength(books As Book()) As Book()
        Dim sorted = CType(books.Clone(), Book())
        Array.Sort(sorted, Function(left, right) left.Pages.CompareTo(right.Pages))
        Return sorted
    End Function

    Sub Main()
        Dim books As Book() = {
            New Book("Refactoring", 448),
            New Book("Clean Code", 464),
            New Book("The Pragmatic Programmer", 352),
            New Book("Domain-Driven Design", 560),
            New Book("Working Effectively with Legacy Code", 456)
        }

        ' Number of elements is Length; the last valid index is Length - 1
        Console.WriteLine(books.Length & " books, last index " & (books.Length - 1))

        ' A rectangular array: levels by slots (the declaration gives UPPER bounds)
        Dim slots(LevelCount - 1, SlotsPerLevel - 1) As Book
        Dim nextBook = 0
        For level = 0 To slots.GetUpperBound(0)
            For slot = 0 To slots.GetUpperBound(1)
                If nextBook < books.Length Then
                    slots(level, slot) = books(nextBook)
                    nextBook += 1
                End If
            Next
        Next

        For level = 0 To slots.GetLength(0) - 1
            Dim used = 0
            For slot = 0 To slots.GetLength(1) - 1
                If slots(level, slot) IsNot Nothing Then used += 1
            Next
            Console.WriteLine("level " & level & ": " & used & " of " & slots.GetLength(1) & " slots used")
        Next

        ' A jagged array: books grouped by size class, each group with its own length
        Dim groups(2)() As Book
        groups(0) = Array.FindAll(books, Function(b) b.Pages < 400)
        groups(1) = Array.FindAll(books, Function(b) b.Pages >= 400 AndAlso b.Pages < 500)
        groups(2) = Array.FindAll(books, Function(b) b.Pages >= 500)
        For i = 0 To groups.Length - 1
            Console.WriteLine("group " & i & ": " & groups(i).Length & " book(s)")
        Next

        For Each book In ByLength(books)
            Console.WriteLine(book)
        Next
        Console.WriteLine("original first: " & books(0).Title)

        ' The total uses a variable declared before the loop; a Sum call would do the same
        Dim totalPages = 0
        For Each book In books
            totalPages += book.Pages
        Next
        Console.WriteLine("average pages = " & (totalPages / books.Length).ToString("0.0"))
    End Sub
End Module
5 books, last index 4
level 0: 4 of 4 slots used
level 1: 1 of 4 slots used
level 2: 0 of 4 slots used
group 0: 1 book(s)
group 1: 3 book(s)
group 2: 1 book(s)
The Pragmatic Programmer (352 pages)
Refactoring (448 pages)
Working Effectively with Legacy Code (456 pages)
Clean Code (464 pages)
Domain-Driven Design (560 pages)
original first: Refactoring
average pages = 456.0

Version history

Visual Basic / .NET Array feature

Visual Basic .NET 1.0 (2002)

Zero-based arrays with the upper bound declaration, ReDim, Preserve, Erase, ParamArray, array covariance; Option Base and Dim a(1 To 5) removed.

Visual Basic 9 (Visual Studio 2008)

Array literals and inferred array types (Dim a = {1, 2, 3}); the compiler accepts them with LangVersion 9.

Visual Basic 10 (Visual Studio 2010)

Collection initializers (From), not available at LangVersion 9 (BC36716); arrays unchanged. Implicit line continuation lets initializers span lines without _.

.NET 6

Array.MaxLength, Enumerable.Chunk, PriorityQueue(Of ...); usable from Visual Basic because they are library members.

Visual Basic 17.13 / .NET 10

No array syntax has changed since Visual Basic 10. Span-based and range features of C# 8 to C# 14 (^, .., collection expressions, params Span) are not available.

Comparison with C#

Topic Visual Basic C#

Declare five elements

Dim a(4) As Integer (upper bound)

int[] a = new int[5]; (length)

Type of a local array

Integer() (or As Integer())

int[]

Literal

{1, 2, 3} or New Integer() {1, 2, 3}

new[] { 1, 2, 3 }, [1, 2, 3] (collection expression, C# 12)

Index

a(i)

a[i]

Index from end, range

not available

a[^1], a[1..3]

Resize in place

ReDim Preserve a(n) (new array)

no operator; Array.Resize(ref a, n)

Multidimensional / jagged

Integer(,) / Integer()()

int[,] / int[][]

Variable-argument parameter

ParamArray values As Integer()

params int[] values (also params ReadOnlySpan<int> from C# 13)

Assigning the For Each variable

allowed; changes the variable only

error CS1656

Span<T>, stackalloc

cannot declare (BC30668)

supported

Equality of arrays

= is an error (BC31080); Is is identity

== is identity

Non-zero lower bound

only via Array.CreateInstance

only via Array.CreateInstance

Where older material is wrong

Teaching material written for VB6, or quickly for VB.NET, repeats a handful of mistakes that this page’s examples are meant to correct. Each is checked here against the compiler.

  • "`Dim a(10) As Integer` has 10 elements." It has 11; the number is the upper bound. Likewise Dim m(5, 10) is 6 by 11. For a fixed number n of elements write Dim a(n - 1) As Integer, or Dim a(Count - 1) with a named constant.

  • "`UBound(m, 1)` is the number of columns." It is the upper bound of the first dimension (rows); the column dimension is 2, and a bound is never a length. Use GetLength when you want a count.

  • “ReDim data(n) As Byte” does not compile in Visual Basic .NET (BC30811). Declare Dim data() As Byte or Dim data(n) As Byte once and ReDim data(n) later, or write data = New Byte(n) {}.

  • "Arrays can start at 1 (Option Base 1, 1 To 5)." True for VB6/VBA only (see Non-zero bounds and VB6).

  • "Use ReDim Preserve to build a list while reading input." It works, but it copies the array on every call; List(Of T) is the right tool and has ToArray() for the end.

  • "`+` joins array elements into text." Use String.Join(", ", a) and & (see Strings and Text).

Pitfalls checklist

  • Upper bound versus length: Dim a(n) has n + 1 elements; a loop For i = 0 To a.Length is one too many.

  • A declared-but-unsized array (Dim a() As Integer) is Nothing, not empty. Initialise it or use Array.Empty(Of T)().

  • ReDim and ReDim Preserve create a new array: other references keep the old one; parameters passed ByVal do not see the new array.

  • ReDim Preserve on a multidimensional array fails at run time unless only the last dimension changes.

  • For Each gives a copy of structure elements; to modify them in place, loop over indexes.

  • Rank is part of the type: Integer(), Integer(,) and Integer()() are three unrelated types. A jagged row can be Nothing.

  • = does not compare arrays; Is compares identity; SequenceEqual compares contents.

  • Covariance makes Book() convertible to CatalogItem(); storing a Magazine then throws at run time.

  • Array.Sort is unstable and culture-sensitive for strings; pass a comparer when the order must be reproducible.

  • Array.IndexOf returns -1 for "not found"; BinarySearch returns a negative complement and requires sorted input.

  • Lambda parameter names must not collide with any local of the enclosing method, even one declared below the lambda (BC36641).

  • A local named like the root namespace or a type (shelf, text, name) can give BC30112 or silently refer to something else.

  • Const cannot be an array (BC30424); ReadOnly freezes the variable but not the elements.

  • Public properties that return an internal array let callers change your object: return a copy or IReadOnlyList(Of T).

  • Spans and ranges are unavailable: use ArraySegment, Memory(Of T), Array.Copy or LINQ.