Unstructured Error Handling: On Error
|
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 (except where the page says
otherwise) and was run in the .NET 10 SDK container (Linux); the output shown is real output. The root namespace of the scratch
projects is ShelfDocs. Compiler diagnostics are shown with the file name Program.vb. The error examples are separate failing
programs.
On Error is the error-handling model of Visual Basic 6 and VBA, kept in the language so that old code compiles. It is the only
page of this section on which On Error appears in examples. New Visual Basic code uses
Structured Exception Handling (Try, Catch,
Finally), and Microsoft’s documentation says so too. The two models are not separate runtimes. Both run on .NET exceptions: an
On Error handler is entered when an exception is thrown, and Err is a view of that exception. What differs is the control flow, a
procedure-wide handler with GoTo and Resume instead of blocks, and the rules that follow from it. Read this page to understand legacy code
(see Migrating Legacy Visual Basic and
The VB Family: VB6, VBA and VBScript) and to migrate it.
| Statement | Effect |
|---|---|
|
enables a handler: when an error occurs in this procedure, control jumps to |
|
enables "ignore and continue": after an error, execution continues at the next statement; the error is only recorded in |
|
disables the enabled handler in this procedure and clears |
|
clears the error and disables the handler, used inside a handler to leave the "active" state |
|
in a handler: retry the statement that failed |
|
in a handler: continue with the statement after the one that failed |
|
in a handler: continue at a label |
|
the |
|
the statement that raises the run-time error with that VB6 number (legacy form of |
|
the line number of the last numbered line executed before the error |
How the model works
A procedure has at most one enabled handler at a time; On Error is an executable statement that replaces it. An error that
occurs while the handler is enabled transfers control to the label (the handler becomes active) or, for Resume Next, to the
next statement. A handler is not a Sub: it is a section of the same procedure, reached by a jump and left by Resume, Exit Sub,
Return, End Sub or another On Error. An error raised while a handler is active is not handled by the same procedure; it goes
to the caller.
On Error GoTo label
The usual shape: enable the handler at the top, write the normal path, leave with Return (or Exit Sub/Exit Function) before
the handler label, and write the handler after it. The handler reads the Err object before doing anything that could replace it.
Option Strict On
Module Program
Private Function SafeDivide(numerator As Integer, denominator As Integer) As Integer
On Error GoTo Handler
Dim result As Integer = numerator \ denominator
Return result
Handler:
Console.WriteLine(" Err.Number = " & Err.Number & ", Err.Description = " & Err.Description)
Console.WriteLine(" Err.Source = " & Err.Source)
Return -1
End Function
Private Sub NoExitBeforeHandler()
On Error GoTo Oops
Console.WriteLine(" body ran fine")
Oops:
Console.WriteLine(" handler code also runs, Err.Number = " & Err.Number)
End Sub
Sub Main()
Console.WriteLine("10 \ 2:")
Console.WriteLine(" -> " & SafeDivide(10, 2))
Console.WriteLine("10 \ 0:")
Console.WriteLine(" -> " & SafeDivide(10, 0))
Console.WriteLine("Falling into the label without an Exit:")
NoExitBeforeHandler()
End Sub
End Module
10 \ 2:
-> 5
10 \ 0:
Err.Number = 11, Err.Description = Attempted to divide by zero.
Err.Source = System.Private.CoreLib
-> -1
Falling into the label without an Exit:
body ran fine
handler code also runs, Err.Number = 0
Source: On Error Statement
-
10 \ 0raised aDivideByZeroException;Err.Numberis the VB6 number for it (11),Err.Descriptionis the exception’s message, andErr.Sourceis the assembly that threw it (System.Private.CoreLibfor a runtime exception). -
The label is a line label in the same procedure, followed by a colon. A label must exist (BC30132 otherwise).
-
If the code does not
ReturnorExitbefore the label, the handler’s statements run on the normal path too (the last case:Err.Numberis 0 because nothing failed). Forgetting theExitis the classic bug of this model.
On Error Resume Next, Resume and Resume Next
On Error Resume Next makes the procedure keep going. The failing statement is abandoned (assignments are not made), the error is
recorded in Err, and execution continues at the next statement. It is "inline" error handling: the code tests Err.Number
after the statements that might fail and calls Err.Clear(). Inside a handler entered with On Error GoTo, the three Resume forms
decide where to continue.
Option Strict On
Module Program
Private Sub ResumeNextDemo()
Dim values() As String = {"4", "x", "7"}
Dim total As Integer = 0
Dim i As Integer
On Error Resume Next
For i = 0 To values.Length - 1
total += Integer.Parse(values(i))
If Err.Number <> 0 Then
Console.WriteLine(" skipped '" & values(i) & "': Err.Number " & Err.Number)
Err.Clear()
End If
Next
Console.WriteLine(" total = " & total)
End Sub
Private Sub ResumeNextInHandler()
On Error GoTo Handler
Console.WriteLine(" before")
Dim zero As Integer = 0
Console.WriteLine(" value = " & (5 \ zero))
Console.WriteLine(" after the failing statement")
Return
Handler:
Console.WriteLine(" handler: " & Err.Description)
Resume Next
End Sub
Private Sub ResumeRetry()
Dim attempts As Integer = 0
Dim divisor As Integer = 0
On Error GoTo Fix
Console.WriteLine(" 7 \ divisor = " & (7 \ divisor))
Return
Fix:
attempts += 1
Console.WriteLine(" attempt " & attempts & " failed (" & Err.Description & "), changing divisor to 2")
divisor = 2
Resume
End Sub
Private Sub ResumeLabel()
On Error GoTo Handler
Dim zero As Integer = 0
Console.WriteLine(" value = " & (5 \ zero))
Console.WriteLine(" skipped by Resume Done")
Done:
Console.WriteLine(" at Done")
Return
Handler:
Console.WriteLine(" handler: " & Err.Number)
Resume Done
End Sub
Sub Main()
Console.WriteLine("On Error Resume Next:")
ResumeNextDemo()
Console.WriteLine("Resume Next in a handler:")
ResumeNextInHandler()
Console.WriteLine("Resume (retry the statement):")
ResumeRetry()
Console.WriteLine("Resume label:")
ResumeLabel()
End Sub
End Module
On Error Resume Next:
skipped 'x': Err.Number 13
total = 11
Resume Next in a handler:
before
handler: Attempted to divide by zero.
after the failing statement
Resume (retry the statement):
attempt 1 failed (Attempted to divide by zero.), changing divisor to 2
7 \ divisor = 3
Resume label:
handler: 11
at Done
Source: Resume Statement
-
Resumeretries the statement that failed; it loops forever if the handler does not fix the cause. The example repairsdivisorfirst. -
Resume Nextcontinues after the failing statement. If the failure was in a called procedure with no handler of its own, "the failing statement" is the call, as the procedure-call example below shows. -
Resume labeljumps to a label, and may leave the handler for a shared clean-up path. -
Resumebelongs in a handler. It compiles anywhere in a procedure that hasOn Error, but fails at run time when no error is being handled (see "Resume without error" below). -
A
Resume Nextwithout a following check is the opposite of error handling: it hides every failure in the procedure. PreferTryParse-style APIs and explicit checks.
On Error GoTo 0 and On Error GoTo -1
On Error GoTo 0 switches handling off: the next error is no longer trapped by this procedure and travels up to the caller (and
Err is cleared). On Error GoTo -1 clears the error and disables the handler; it is the way to leave the active state inside a
handler so that a later On Error GoTo can trap a second error. An error that occurs while a handler is active also escapes to
the caller, as the last case shows.
Option Strict On
Module Program
Private Sub Fail(label As String)
Throw New InvalidOperationException(label)
End Sub
Private Sub HandlerOff()
On Error GoTo Handler
Console.WriteLine(" error 1 is handled")
Fail("one")
Handler:
Console.WriteLine(" handler: " & Err.Description)
End Sub
Private Sub GoToZero()
On Error Resume Next
Fail("first, ignored")
Console.WriteLine(" after first failure, Err.Number = " & Err.Number)
On Error GoTo 0
Console.WriteLine(" handling is off now; Err.Number is still " & Err.Number)
Fail("second, not handled here")
Console.WriteLine(" never printed")
End Sub
Private Sub GoToMinusOne()
Dim step1 As Integer = 0
On Error GoTo First
Fail("a")
Return
First:
step1 += 1
Console.WriteLine(" First handler, Err.Number = " & Err.Number)
On Error GoTo -1 ' clears the error and the active handler
Console.WriteLine(" after GoTo -1, Err.Number = " & Err.Number)
On Error GoTo Second
Fail("b")
Return
Second:
Console.WriteLine(" Second handler: " & Err.Description & " (step1 = " & step1 & ")")
End Sub
Private Sub ErrorInsideHandler()
On Error GoTo Handler
Fail("outer")
Return
Handler:
Console.WriteLine(" in handler, about to fail again")
Fail("raised inside the handler")
End Sub
Private Sub Run(name As String, action As Action)
Console.WriteLine(name & ":")
Try
action()
Catch ex As Exception
Console.WriteLine(" escaped to the caller: " & ex.GetType().Name & " - " & ex.Message)
End Try
End Sub
Sub Main()
Run("handler reached by falling through", AddressOf HandlerOff)
Run("On Error GoTo 0", AddressOf GoToZero)
Run("On Error GoTo -1", AddressOf GoToMinusOne)
Run("error inside a handler", AddressOf ErrorInsideHandler)
End Sub
End Module
handler reached by falling through:
error 1 is handled
handler: one
On Error GoTo 0:
after first failure, Err.Number = 5
handling is off now; Err.Number is still 0
escaped to the caller: InvalidOperationException - second, not handled here
On Error GoTo -1:
First handler, Err.Number = 5
after GoTo -1, Err.Number = 0
Second handler: b (step1 = 1)
error inside a handler:
in handler, about to fail again
escaped to the caller: InvalidOperationException - raised inside the handler
Source: On Error Statement
Handlers and procedure calls
Handlers are per procedure. An error raised in a procedure with no enabled handler unwinds to the caller, and each caller on the
way is asked in turn. When a caller with an enabled, inactive handler is found, that handler runs; Resume Next there continues after
the call that failed, in the caller. On Error Resume Next is not inherited by callees: each procedure that wants inline handling
needs its own statement, and if the callee has none the error returns to the caller as shown. With no handler anywhere, the
exception is unhandled and ends the process (see the global handlers on the structured page).
Option Strict On
Module Program
Private Sub Level3()
Console.WriteLine(" Level3: about to fail")
Dim zero As Integer = 0
Console.WriteLine(1 \ zero)
Console.WriteLine(" Level3: never printed")
End Sub
Private Sub Level2()
Console.WriteLine(" Level2: no handler here")
Level3()
Console.WriteLine(" Level2: never printed")
End Sub
Private Sub Level1()
On Error GoTo Handler
Console.WriteLine(" Level1: calls Level2")
Level2()
Console.WriteLine(" Level1: runs after Resume Next (the line after the failed call)")
Return
Handler:
Console.WriteLine(" Level1 handler: " & Err.Description)
Resume Next
End Sub
Private Sub Level1Ignoring()
On Error Resume Next
Level2()
Console.WriteLine(" Level1Ignoring: continues after the call that failed, Err.Number = " & Err.Number)
End Sub
Sub Main()
Level1()
Console.WriteLine("---")
Level1Ignoring()
End Sub
End Module
Level1: calls Level2
Level2: no handler here
Level3: about to fail
Level1 handler: Attempted to divide by zero.
Level1: runs after Resume Next (the line after the failed call)
---
Level2: no handler here
Level3: about to fail
Level1Ignoring: continues after the call that failed, Err.Number = 11
Source: On Error Statement
The Err object
Err is a global function (in Microsoft.VisualBasic.Information) that returns the ErrObject holding the most recent error of
the current thread. ErrObject is a thin view of a .NET exception: setting up an Err.Raise creates an exception, and an
exception caught by a handler (or by Try) populates Err.
| Member | Meaning |
|---|---|
|
the VB6 error number; 0 means no error |
|
the message (the exception’s |
|
the assembly (or application) that raised the error |
|
VB6 help integration; empty and 0 for .NET exceptions |
|
the last error code from a `Declare`d Windows call; 0 here (see Native Interop) |
|
resets all properties; use it after handling an error with |
|
raises a run-time error |
|
the .NET |
The next program raises the .NET exceptions a legacy program meets most often and prints the number that Err.Number takes, with
the HResult that the exception itself reports. On Error Resume Next is used so that the loop continues; Err.GetException()
gives the exception behind the number.
Option Strict On
Module Program
Private Sub Raise(kind As Integer)
Select Case kind
Case 1 : Throw New DivideByZeroException()
Case 2 : Throw New OverflowException()
Case 3 : Throw New InvalidCastException()
Case 4 : Throw New IndexOutOfRangeException()
Case 5 : Throw New ArgumentException("bad argument")
Case 6 : Throw New NullReferenceException()
Case 7 : Throw New OutOfMemoryException()
Case 8 : Throw New IO.FileNotFoundException()
Case 9 : Throw New IO.DirectoryNotFoundException()
Case 10 : Throw New FormatException()
Case 11 : Throw New InvalidOperationException()
Case 12 : Throw New NotSupportedException()
Case 13 : Throw New TimeoutException()
Case 14 : Throw New UnauthorizedAccessException()
Case Else : Throw New ApplicationException("custom")
End Select
End Sub
Sub Main()
Console.WriteLine("Exception type Err.Number HResult")
For kind As Integer = 1 To 15
On Error Resume Next
Err.Clear()
Raise(kind)
Dim ex As Exception = Err.GetException()
Console.WriteLine(ex.GetType().Name.PadRight(32) & Err.Number.ToString().PadRight(12) & "0x" & ex.HResult.ToString("X8"))
Next
End Sub
End Module
Exception type Err.Number HResult
DivideByZeroException 11 0x80020012
OverflowException 6 0x80131516
InvalidCastException 13 0x80004002
IndexOutOfRangeException 9 0x80131508
ArgumentException 5 0x80070057
NullReferenceException 91 0x80004003
OutOfMemoryException 7 0x8007000E
FileNotFoundException 53 0x80070002
DirectoryNotFoundException 76 0x80070003
FormatException 13 0x80131537
InvalidOperationException 5 0x80131509
NotSupportedException 13 0x80131515
TimeoutException 5 0x80131505
UnauthorizedAccessException 5 0x80070005
ApplicationException 5 0x80131600
Source: ErrObject class
Reading the table: runtime exceptions with a classic VB6 meaning map to that number (DivideByZeroException is 11, OverflowException
6, InvalidCastException 13, IndexOutOfRangeException 9, NullReferenceException 91, OutOfMemoryException 7,
FileNotFoundException 53, DirectoryNotFoundException 76). Some .NET exceptions map to 13 (FormatException, NotSupportedException)
and anything without a specific mapping, including every custom exception and InvalidOperationException, comes out as 5, "Invalid
procedure call or argument". So Err.Number cannot tell two exceptions apart reliably; use Err.GetException() and test its type, or
move to Try/Catch, which tests the type directly.
Err.Raise and the Error statement
Err.Raise(number, source, description) raises an error. VB6 numbers that mean something are turned into the matching .NET exception
(5 becomes an ArgumentException, 11 a DivideByZeroException, 53 a FileNotFoundException); other numbers become a plain
Exception whose message is the description. Custom errors conventionally use vbObjectError + n. The old Error 11 statement does the same as Err.Raise(11), with the standard VB6 text. A Try ... Catch
block also fills Err, so legacy helper code that reads Err.Number works inside a Catch.
Option Strict On
Module Program
Private Sub RaiseDemo(number As Integer)
On Error GoTo Handler
Err.Raise(number, "ShelfDocs", "raised with number " & number)
Return
Handler:
Dim ex As Exception = Err.GetException()
Console.WriteLine(" Err.Raise(" & number & ") -> " & ex.GetType().Name & " | Number=" & Err.Number & " | Source=" & Err.Source & " | " & Err.Description)
End Sub
Private Sub ErrorStatementDemo()
On Error GoTo Handler
Error 11
Return
Handler:
Console.WriteLine(" Error 11 -> " & Err.GetException().GetType().Name & ": " & Err.Description)
End Sub
Private Sub TryCatchSetsErr()
Try
Throw New IO.FileNotFoundException("catalog.json")
Catch ex As Exception
Console.WriteLine(" inside Catch: Err.Number = " & Err.Number & ", Err.Description = " & Err.Description)
End Try
End Sub
Sub Main()
Console.WriteLine("Err.Raise:")
RaiseDemo(5)
RaiseDemo(11)
RaiseDemo(53)
RaiseDemo(1000)
RaiseDemo(vbObjectError + 513)
Console.WriteLine("Error statement:")
ErrorStatementDemo()
Console.WriteLine("Try ... Catch also fills Err:")
TryCatchSetsErr()
Console.WriteLine("vbObjectError = " & vbObjectError)
End Sub
End Module
Err.Raise:
Err.Raise(5) -> ArgumentException | Number=5 | Source=ShelfDocs | raised with number 5
Err.Raise(11) -> DivideByZeroException | Number=11 | Source=ShelfDocs | raised with number 11
Err.Raise(53) -> FileNotFoundException | Number=53 | Source=ShelfDocs | raised with number 53
Err.Raise(1000) -> Exception | Number=1000 | Source=ShelfDocs | raised with number 1000
Err.Raise(-2147220991) -> Exception | Number=-2147220991 | Source=ShelfDocs | raised with number -2147220991
Error statement:
Error 11 -> DivideByZeroException: Division by zero.
Try ... Catch also fills Err:
inside Catch: Err.Number = 53, Err.Description = catalog.json
vbObjectError = -2147221504
Source: Error Statement
Erl and line numbers
Erl() returns the line number of the most recent numbered line that ran before the error. Numbered lines are the VB6 way to
locate an error; modern code gets the line from the stack trace instead (ex.StackTrace or the logger). Without numbered lines
Erl() is 0.
Option Strict On
Module Program
Private Sub Numbered()
Dim zero As Integer = 0
On Error GoTo Handler
10: Console.WriteLine(" line 10 ran")
20: Console.WriteLine(" line 20 divides: " & (1 \ zero))
30: Console.WriteLine(" line 30 never ran")
Return
Handler:
Console.WriteLine(" Erl = " & Erl() & ", Err.Number = " & Err.Number)
Console.WriteLine(" HelpFile = '" & Err.HelpFile & "', HelpContext = " & Err.HelpContext & ", LastDllError = " & Err.LastDllError)
End Sub
Private Sub Unnumbered()
Dim zero As Integer = 0
On Error GoTo Handler
Console.WriteLine(1 \ zero)
Return
Handler:
Console.WriteLine(" Erl without line numbers = " & Erl())
End Sub
Sub Main()
Console.WriteLine("Numbered lines:")
Numbered()
Console.WriteLine("Unnumbered:")
Unnumbered()
End Sub
End Module
Numbered lines:
line 10 ran
Erl = 20, Err.Number = 11
HelpFile = '', HelpContext = 0, LastDllError = 0
Unnumbered:
Erl without line numbers = 0
Source: Information.Erl method
Resume without an error
Resume and Resume Next can run only while an error is being handled. Reaching one on the normal path raises run-time error 20,
"Resume without error". In the first procedure below the label is reached only by the Exit Sub jump over it, so it is never
entered; in the second, a Resume Next statement sits on the normal path and fails:
Option Strict On
Module Program
Private Sub ResumeInsideHandler()
On Error GoTo Handler
Console.WriteLine(" no error happens")
Exit Sub
Handler:
Resume Next
End Sub
Private Sub ResumeOnTheNormalPath()
On Error GoTo Handler
Resume Next
Exit Sub
Handler:
Console.WriteLine(" Err.Number " & Err.Number & ": " & Err.Description)
End Sub
Sub Main()
Console.WriteLine("Resume only inside a handler that ran for an error:")
ResumeInsideHandler()
Console.WriteLine("Resume on the normal path:")
ResumeOnTheNormalPath()
End Sub
End Module
Resume only inside a handler that ran for an error:
no error happens
Resume on the normal path:
Err.Number 20: Resume without error.
Source: Resume Statement
Rules and compiler errors
The structural rules of the model are enforced by the compiler. These are the errors the 17.13 compiler printed for each broken rule.
Do not mix with Try. A procedure can use Try or On Error, not both (BC30544, reported on both statements):
Option Strict On
Module Program
Sub Main()
On Error GoTo Handler
Try
Console.WriteLine("a")
Catch ex As Exception
End Try
Handler:
End Sub
End Module
Program.vb(5,9): error BC30544: Method cannot contain both a 'Try' statement and an 'On Error' or 'Resume' statement.
Program.vb(6,9): error BC30544: Method cannot contain both a 'Try' statement and an 'On Error' or 'Resume' statement.
Labels and special procedures. The target of On Error GoTo must be a label that exists (a plain number is a label, so GoTo 5
fails the same way), and On Error and Resume are not allowed in Async methods or iterators (BC36956):
Option Strict On
Module Program
Sub Main()
On Error GoTo Missing
Console.WriteLine("a")
End Sub
Async Function RunAsync() As Task
On Error Resume Next
Await Task.Delay(1)
End Function
Iterator Function Numbers() As IEnumerable(Of Integer)
On Error Resume Next
Yield 1
End Function
End Module
Program.vb(5,23): error BC30132: Label 'Missing' is not defined.
Program.vb(9,9): error BC36956: 'On Error' and 'Resume' cannot appear inside async or iterator methods.
Program.vb(13,9): error BC36956: 'On Error' and 'Resume' cannot appear inside async or iterator methods.
Option Strict On
Module Program
Sub Main()
On Error GoTo 5
Resume Next
End Sub
End Module
Program.vb(5,23): error BC30132: Label '5' is not defined.
Not inside a lambda. On Error and Resume cannot be written inside the body of a lambda expression (BC36668), but a method that
contains a lambda, a query or a Using statement can still have its own On Error handler:
Option Strict On
Module Program
Sub Main()
Dim safe As Action = Sub()
On Error Resume Next
Console.WriteLine("x")
End Sub
safe()
End Sub
End Module
Program.vb(6,34): error BC36668: 'On Error' and 'Resume' cannot appear inside a lambda expression.
What changed from the older rules
Older documentation, and the Learn pages for BC36595 and BC36013, say that a procedure with On Error cannot also contain a lambda
expression, a query expression or a Using statement. The 17.13 compiler on .NET 10 accepts all three, and the handler traps errors
raised in the code around them, including errors in the Using body (after the resource has been disposed), in a lambda that the
method calls, and in the statements after a query. This program, with On Error GoTo in each method, built without a warning:
Option Strict On
Public Class Tracker
Implements IDisposable
Public Sub Dispose() Implements IDisposable.Dispose
Console.WriteLine(" Tracker disposed")
End Sub
End Class
Module Program
Private Sub WithUsing()
On Error GoTo Handler
Using t As New Tracker()
Console.WriteLine(" inside Using")
Dim zero As Integer = 0
Console.WriteLine(1 \ zero)
End Using
Return
Handler:
Console.WriteLine(" handler: " & Err.Description)
End Sub
Private Sub WithLambda()
On Error GoTo Handler
Dim twice As Func(Of Integer, Integer) = Function(x) x * 2
Console.WriteLine(" twice(4) = " & twice(4))
Dim zero As Integer = 0
Console.WriteLine(1 \ zero)
Return
Handler:
Console.WriteLine(" handler: " & Err.Description)
End Sub
Private Sub WithQuery()
On Error GoTo Handler
Dim numbers() As Integer = {1, 2, 3}
Dim big = From n In numbers Where n > 1 Select n
Console.WriteLine(" count = " & big.Count())
Dim zero As Integer = 0
Console.WriteLine(1 \ zero)
Return
Handler:
Console.WriteLine(" handler: " & Err.Description)
End Sub
Private Sub LambdaThrows()
On Error GoTo Handler
Dim bad As Action = Sub() Throw New InvalidOperationException("raised in the lambda")
bad()
Return
Handler:
Console.WriteLine(" handler: " & Err.Description)
End Sub
Sub Main()
Console.WriteLine("On Error + Using:")
WithUsing()
Console.WriteLine("On Error + lambda:")
WithLambda()
Console.WriteLine("On Error + query:")
WithQuery()
Console.WriteLine("Lambda throws, caller has the handler:")
LambdaThrows()
End Sub
End Module
On Error + Using:
inside Using
Tracker disposed
handler: Attempted to divide by zero.
On Error + lambda:
twice(4) = 8
handler: Attempted to divide by zero.
On Error + query:
count = 2
handler: Attempted to divide by zero.
Lambda throws, caller has the handler:
handler: raised in the lambda
The compiler did not emit BC36595 or BC36013 in any of these situations, so treat them as legacy diagnostics that the Learn index still lists. The
rules that remain are BC30544 (with Try), BC36668 (inside a lambda) and BC36956 (in Async and iterator methods).
| Code | Message | Cause |
|---|---|---|
BC30544 |
Method cannot contain both a 'Try' statement and an 'On Error' or 'Resume' statement. |
mixing the two models in one method |
BC30132 |
Label 'Missing' is not defined. |
|
BC36956 |
'On Error' and 'Resume' cannot appear inside async or iterator methods. |
|
BC36668 |
'On Error' and 'Resume' cannot appear inside a lambda expression. |
|
Why it exists, and why not to use it
-
Compatibility. VB6 and VBA code uses it everywhere, and the upgrade tools and hand ports keep it. See The VB Family: VB6, VBA and VBScript.
-
It trades scoping for jumps. The handler covers the rest of the procedure, not a block, so you cannot say "this call may fail". There is no
Finally: the clean-up goes behind a label and every path has to reach it. -
Resume Nexthides failures. The statement after the failure runs with whatever state the failure left. The program continues with wrong data. -
It cannot tell exceptions apart.
Err.Numbercollapses many .NET exceptions into a few numbers (see the table above). -
It does not compose with the rest of .NET. It is not allowed in
Asyncmethods and lambdas, andUsingalready gives structured clean-up. -
The C# language has nothing like it.
Migrating to Try
The program below puts each legacy pattern next to its Try-style replacement and prints the results of both, which are identical.
The patterns:
| Legacy | Replacement |
|---|---|
|
|
|
|
|
a loop with |
|
leaving the |
|
|
|
|
|
|
|
nothing: an exception ends when its |
|
the stack trace ( |
Option Strict On
Imports System.IO
Module Legacy
' Pattern 1: error handler with a cleanup label
Function ReadFirstLineOld(path As String) As String
Dim reader As StreamReader = Nothing
Dim result As String = "(none)"
On Error GoTo Handler
reader = New StreamReader(path)
result = reader.ReadLine()
CleanUp:
If reader IsNot Nothing Then reader.Dispose()
Return result
Handler:
result = "error " & Err.Number & ": " & Err.Description
Resume CleanUp
End Function
' Pattern 2: On Error Resume Next to skip bad items
Function SumCopiesOld(fields() As String) As Integer
Dim total As Integer = 0
Dim i As Integer
On Error Resume Next
For i = 0 To fields.Length - 1
Dim n As Integer = 0
n = CInt(fields(i))
If Err.Number = 0 Then total += n
Err.Clear()
Next
Return total
End Function
' Pattern 3: Resume to retry after the handler repaired the cause
Function ParseWithDefaultOld(text As String) As Integer
Dim value As Integer
On Error GoTo Handler
value = Integer.Parse(text)
Return value
Handler:
text = "0"
Resume
End Function
' Pattern 4: Err.Raise and testing Err.Number
Sub RenewOld(extraDays As Integer)
If extraDays > 14 Then Err.Raise(5, "Renew", "A loan can be renewed by at most 14 days.")
Console.WriteLine(" renewed by " & extraDays)
End Sub
Function TryRenewOld(extraDays As Integer) As String
On Error Resume Next
RenewOld(extraDays)
If Err.Number = 5 Then Return "refused: " & Err.Description
Return "ok"
End Function
End Module
Module Modern
Function ReadFirstLineNew(path As String) As String
Try
Using reader As New StreamReader(path)
Return reader.ReadLine()
End Using
Catch ex As FileNotFoundException
Return "error 53: " & ex.Message
End Try
End Function
Function SumCopiesNew(fields() As String) As Integer
Dim total As Integer = 0
For Each field As String In fields
Dim n As Integer
If Integer.TryParse(field, n) Then total += n
Next
Return total
End Function
Function ParseWithDefaultNew(text As String) As Integer
Dim value As Integer
If Not Integer.TryParse(text, value) Then value = 0
Return value
End Function
Sub RenewNew(extraDays As Integer)
If extraDays > 14 Then Throw New InvalidOperationException("A loan can be renewed by at most 14 days.")
Console.WriteLine(" renewed by " & extraDays)
End Sub
Function TryRenewNew(extraDays As Integer) As String
Try
RenewNew(extraDays)
Return "ok"
Catch ex As InvalidOperationException
Return "refused: " & ex.Message
End Try
End Function
End Module
Module Program
Sub Main()
Dim missing As String = Path.Combine(Path.GetTempPath(), "shelf-no-such-file.txt")
Console.WriteLine("1. cleanup label")
Console.WriteLine(" old: " & ReadFirstLineOld(missing).Replace(missing, "<path>"))
Console.WriteLine(" new: " & ReadFirstLineNew(missing).Replace(missing, "<path>"))
Dim fields() As String = {"3", "two", "4", "", "5"}
Console.WriteLine("2. skip bad items")
Console.WriteLine(" old: " & SumCopiesOld(fields))
Console.WriteLine(" new: " & SumCopiesNew(fields))
Console.WriteLine("3. repair and retry")
Console.WriteLine(" old: " & ParseWithDefaultOld("abc"))
Console.WriteLine(" new: " & ParseWithDefaultNew("abc"))
Console.WriteLine("4. raising errors")
Console.WriteLine(" old: " & TryRenewOld(7))
Console.WriteLine(" old: " & TryRenewOld(30))
Console.WriteLine(" new: " & TryRenewNew(7))
Console.WriteLine(" new: " & TryRenewNew(30))
End Sub
End Module
1. cleanup label
old: error 53: Could not find file '<path>'.
new: error 53: Could not find file '<path>'.
2. skip bad items
old: 12
new: 12
3. repair and retry
old: 0
new: 0
4. raising errors
renewed by 7
old: ok
old: refused: A loan can be renewed by at most 14 days.
renewed by 7
new: ok
new: refused: A loan can be renewed by at most 14 days.
Source: Try…Catch…Finally Statement
The migration is mechanical for small procedures. For large ones, convert a procedure at a time, keep the tests (see
Testing) green, and decide per Resume Next whether the ignored failure was
intended. Many are not, and the right replacement for them is an explicit check or letting the exception through.
Where introductory material is out of date
-
Books and courses that teach
On Error GoToas the way to handle errors in Visual Basic describe VB6; for VB.NET the structured model is the recommended one, and the language documentation says so. -
The
Errobject does not give you the type of the error; the numbers are the VB6 ones, and many .NET exceptions share a number. -
The rules that name lambdas, queries and
Usingas incompatible withOn Errorare out of date for the 17.13 compiler. The rules aboutTry, lambda bodies,Asyncand iterators are current. -
On Erroris not available as a general error handler in C#; code shared with C# callers sees ordinary exceptions. -
Windows Script Host and VBScript have their own
On Error Resume Next, with noGoToform; that is a different language.
Version notes and pitfalls
| Topic | Note |
|---|---|
VB6 and VBA |
the original home of |
Visual Basic .NET |
kept for compatibility; structured handling was added next to it; |
.NET 10, VB 17.13 |
compiles and runs on .NET; not allowed in lambdas, |
Platforms |
the same on every platform; only |
Pitfalls:
-
Forgetting
Exit Sub/Returnbefore the handler label, so the handler runs on the normal path. -
Resumewith a handler that does not fix the cause: an infinite loop. -
On Error Resume Nextat the top of a long procedure: every later failure is invisible. Limit its scope to a small helper. -
Reading
Err.Numberafter another call that can fail, or after code that raised and handled a second error: the object only describes the latest error. CopyErr.NumberandErr.Descriptioninto locals first. -
Assuming
Resume Nextin the caller resumes after the line in the callee: it resumes after the call. -
Mixing the model with
Tryin one method (BC30544); a method that needs both has to be split.