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 |
|---|---|
|
The array has 5 elements, indexed |
The lower bound is always |
|
Arrays are reference types |
Assignment and parameter passing copy the reference; |
An array has a fixed size |
|
The rank (number of dimensions) is part of the type |
|
Covariance for reference types |
|
Elements start as the default value |
|
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
Source: Arrays in Visual Basic
What to take from the output:
-
Lengthis5forDim shelves(4),UBoundis4,LBoundis0.GetUpperBound(0)takes the dimension number, which is zero-based; the runtime functionUBound(array, dimension)takes a one-based dimension number. Mixing the two conventions is a classic slip. -
{1, 2.5, 3}is aDouble()and{1, 2L, 3}anInt64()(Long()in Visual Basic): each element converts widening to the dominant type. -
Dim none(-1) As Stringis a valid empty array (upper bound-1, length0); so is{}with a declared type. Neither isNothing. An array variable that is declared without a size, or set toNothing, holds no array at all andLengthon it throwsNullReferenceException. PreferArray.Empty(Of T)(), which returns one shared empty array and allocates nothing. -
Dim copyRef = ais a second name for the same array, which is why changinga(0)shows throughcopyRef.ClonereturnsObject, soCType(orDirectCast) back to the array type is needed underOption 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 |
|
BC30567 / BC30568 |
Array initializer is missing N elements / has N too many elements |
|
BC30057 |
Too many arguments to |
|
BC30414 |
Value of type |
Rank mismatch. A one-dimensional initializer for a two-dimensional variable, or the other way round. |
BC30638 |
Array bounds cannot appear in type specifiers |
|
BC32059 |
Array lower bounds can be only |
|
BC36915 |
Cannot infer an element type because more than one type is possible |
|
BC36914 |
Cannot infer an element type, and Option Strict On does not allow |
|
BC30424 |
Constants must be of an intrinsic or enumerated type, not a class, structure, type parameter, or array type |
An array cannot be |
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
Source: For Each…Next Statement
-
A counted
Forloop runs toisbns.Length - 1or toUBound(isbns); the loop bound is the upper bound, soTo isbns.Lengthis one step too far. The loop in the example printed0 1 2 3for a three-element array and only the following read,isbns(isbns.Length), threwIndexOutOfRangeException. -
For Eachneeds 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
Source: For Each…Next Statement
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 |
|---|---|
|
Allocates a new array with upper bound |
|
Allocates a new array and copies |
|
Only the last dimension may change. Changing another dimension throws |
|
Not allowed: |
|
BC30415. |
|
The framework equivalent with a length (not an upper bound) as argument, taking the variable |
|
Sets the array variable to |
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
Source: Array Dimensions in Visual Basic
Rectangular Integer(,) |
Jagged Integer()() |
|
|---|---|---|
Declaration |
|
|
Element access |
|
|
Shape |
all rows the same length |
each row has its own length (and can be |
|
rows times columns (12) |
number of rows (3) |
|
length / upper bound of dimension |
|
|
visits every element, row-major |
visits each row array |
|
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 |
|
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 is2.UBoundreturns a bound, not a length, so a loopFor c = 0 To UBound(matrix, 2)is right andFor c = 0 To UBound(matrix, 2) - 1is one short.matrix.GetLength(1)andmatrix.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 inferInteger()(). -
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 |
|
BC30050 |
ParamArray parameter must be an array |
|
BC30667 |
ParamArray parameters must be declared |
|
BC30642 |
|
A |
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 |
|---|---|
|
|
|
Zero-based dimension numbers. |
|
In-place, unstable introspective sort. Uses |
|
In place. |
|
|
|
Take |
|
Copying needs a destination that is long enough ( |
|
An empty shared array, a read-only wrapper, and the largest array size (2147483591 on this runtime). |
|
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
Source: How to: Sort an Array
Observations from the two programs:
-
Array.Sort(words)uses the current culture, so the default order ofalpha, Beta, Delta, gammadepends on the machine’s culture (on the container the result wasalpha, Beta, Delta, gamma). PassStringComparer.OrdinalorStringComparer.OrdinalIgnoreCasewhen the order must be the same everywhere; this is the same rule as for string comparison in Strings and Text. -
x Is yandx.Equals(y)are both reference comparisons for arrays, so two arrays with equal contents are not "equal";SequenceEqualcompares element by element, andStructuralComparisons.StructuralEqualityComparerdoes it for nested arrays too. -
The lambda parameter names in
Array.Sort(copy, Function(left, right) ...)areleftandrighton 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 laterDim x = 1, the lambda parameterxis 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
Source: ArrayTypeMismatchException Class
Rules to remember:
-
Covariance applies only to reference types:
Integer()is not anObject()(the output showsFalse), thoughString()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 useList(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)andIEnumerable(Of T); the mutating members throwNotSupportedException("Collection was of a fixed size"). PreferIReadOnlyList(Of T)orIEnumerable(Of T)as parameter types for code that only reads: every array and list fits. -
Array.AsReadOnlyreturns 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 |
|---|---|---|
|
copy of a range into an existing array |
you own both arrays |
|
view over a range of the same storage; writes go through |
you pass part of a buffer to another method |
|
view; a normal structure, so it can be stored in a field or a local |
asynchronous code and long-lived windows |
|
a reusable buffer, possibly longer than requested |
hot paths that need scratch arrays |
|
copies, lazily evaluated until |
readability matters more than speed |
|
immutable value wrapper over an 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
Source: ArraySegment<T> Struct
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.
Source: Array.CreateInstance Method
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
Source: IndexOutOfRangeException Class
| Exception | Typical cause | Prevention |
|---|---|---|
|
|
loop to |
|
array variable is |
create arrays eagerly, return |
|
|
guard parameters |
|
|
validate sizes that come from users |
|
a size that fits |
bound sizes, process in chunks |
|
store of the wrong subtype through a covariant reference; |
keep the declared and the runtime type the same |
|
|
sort a jagged array or a flattened copy |
|
|
compute lengths; use typed arrays |
|
casting an |
use |
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
Source: Arrays in Visual Basic
Version history
| Visual Basic / .NET | Array feature |
|---|---|
Visual Basic .NET 1.0 (2002) |
Zero-based arrays with the upper bound declaration, |
Visual Basic 9 (Visual Studio 2008) |
Array literals and inferred array types ( |
Visual Basic 10 (Visual Studio 2010) |
Collection initializers ( |
.NET 6 |
|
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 ( |
Comparison with C#
| Topic | Visual Basic | C# |
|---|---|---|
Declare five elements |
|
|
Type of a local array |
|
|
Literal |
|
|
Index |
|
|
Index from end, range |
not available |
|
Resize in place |
|
no operator; |
Multidimensional / jagged |
|
|
Variable-argument parameter |
|
|
Assigning the |
allowed; changes the variable only |
error CS1656 |
|
cannot declare (BC30668) |
supported |
Equality of arrays |
|
|
Non-zero lower bound |
only via |
only via |
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 numbernof elements writeDim a(n - 1) As Integer, orDim 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. UseGetLengthwhen you want a count. -
“ReDim data(n) As Byte” does not compile in Visual Basic .NET (BC30811). Declare
Dim data() As ByteorDim data(n) As Byteonce andReDim data(n)later, or writedata = 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 Preserveto build a list while reading input." It works, but it copies the array on every call;List(Of T)is the right tool and hasToArray()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)hasn + 1elements; a loopFor i = 0 To a.Lengthis one too many. -
A declared-but-unsized array (
Dim a() As Integer) isNothing, not empty. Initialise it or useArray.Empty(Of T)(). -
ReDimandReDim Preservecreate a new array: other references keep the old one; parameters passedByValdo not see the new array. -
ReDim Preserveon a multidimensional array fails at run time unless only the last dimension changes. -
For Eachgives a copy of structure elements; to modify them in place, loop over indexes. -
Rank is part of the type:
Integer(),Integer(,)andInteger()()are three unrelated types. A jagged row can beNothing. -
=does not compare arrays;Iscompares identity;SequenceEqualcompares contents. -
Covariance makes
Book()convertible toCatalogItem(); storing aMagazinethen throws at run time. -
Array.Sortis unstable and culture-sensitive for strings; pass a comparer when the order must be reproducible. -
Array.IndexOfreturns-1for "not found";BinarySearchreturns 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. -
Constcannot be an array (BC30424);ReadOnlyfreezes 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.Copyor LINQ.
References
-
Arrays in Visual Basic, Array Dimensions, How to: Initialize an Array Variable, How to: Assign One Array to Another Array, How to: Sort an Array and Troubleshooting Arrays
-
Dim Statement, ReDim Statement, Erase Statement, ParamArray and For Each…Next Statement
-
Array Class, Array.Sort, Array.Resize, Array.Copy, Array.BinarySearch, Array.CreateInstance and Array.Clone
-
ArraySegment<T>, Memory<T>, ArrayPool<T> and ImmutableArray<T>
-
Arrays (C# reference) for the C# comparison
-
Collections, LINQ Query Expressions and Collections and Iterators (C#) on this site