Turning VB.NET Runtime Surprises into Compile-Time Errors with Option Strict On
Option Strict On moves late-binding and narrowing-conversion failures from runtime to compile time in VB.NET. See a worked interface example, interop trade-offs, and local verification steps.
06 Oct 2025, 23:11 UTC

Every VB.NET codebase eventually ships a call that compiles cleanly and throws at runtime. The build succeeds, the tests pass, and then a user triggers a MissingMemberException because a late-bound method resolved to nothing. The useful takeaway is that Option Strict On—paired with Option Infer On—moves a whole class of these failures from runtime to compile time. It is one of the cheapest engineering decisions you can make.
What Option Strict On Actually Changes
VB.NET has historically been permissive about types. With Option Strict Off, the compiler accepts two dangerous patterns:
- Late binding: when a variable is typed as
Object, the compiler defers method resolution until runtime. If the underlying object does not expose the method, the failure happens in production, not in the build. - Implicit narrowing conversions: the compiler silently allows a conversion that can lose data, such as assigning a
Longto anIntegerwithout an explicit cast.
Option Strict On rejects both. It also tightens implicit typing around Object, forcing you to state intent with an explicit cast such as CType or TryCast, or better, a shared interface.
Option Infer On reduces the ceremony that comes with strict typing. Dim total = 0 infers Integer, and For Each n In numbers infers the element type. That keeps strict-mode code readable, but inference should not become a way to hide types from maintainers. Use explicit As clauses for public APIs, fields, and anything meant to be stable.
A Worked Example: From Object to Interface
Imagine a factory that returns a helper. Under Option Strict Off, this compiles:
Option Strict Off
Dim helper As Object = Factory.Create()
Dim answer = helper.Calculate(2, 3)
If the object returned by Factory.Create() does not expose Calculate, the compiler does not complain. The failure appears at runtime.
With Option Strict On, the compiler rejects the late-bound call immediately. The practical fix is to declare a contract and depend on it:
Option Strict On
Option Infer On
Public Interface IHelper
Function Calculate(x As Integer, y As Integer) As Integer
End Interface
' In the calling code
Dim helper As IHelper = TryCast(Factory.Create(), IHelper)
If helper Is Nothing Then
Throw New InvalidOperationException("Factory did not return an IHelper.")
End If
Dim answer = helper.Calculate(2, 3)
TryCast returns Nothing when the object does not implement the interface, so the Nothing check is mandatory. This pattern makes the dependency explicit and catches missing implementations at build time rather than in production.
The Interop and Legacy-Code Trade-off
Strict mode is not free. COM interop, reflection-heavy code, and generated code often rely on Object or Variant-style returns that Option Strict On will reject. In those cases you have three choices:
- Add explicit casts at the boundary where interop data enters your typed code.
- Wrap the interop call in a small adapter that exposes a typed interface.
- As a last resort, add
Option Strict Offto a single legacy file, keeping the exception as narrow as possible.
In a large legacy codebase, flipping the switch globally can produce a wall of errors. A safer migration is file-by-file or project-by-project: enable strict mode in a leaf project first, fix the compile errors, then move inward. That keeps the build green and the change reviewable.
Limitations and a Practical Check
Option Strict On is not a correctness proof. It does not eliminate NullReferenceException or InvalidCastException; it makes conversions and dependencies more explicit. You still need null checks, input validation, and tests around boundaries such as user input, files, and network data.
To verify the behavior locally, create a scratch VB.NET console project. This assumes VB.NET in a modern .NET project (for example .NET 6+ or .NET Framework 4.x), not classic Visual Basic 6.0; defaults vary by template and Visual Studio version.
- Set
Option Strict Offat the top of a file or in the project properties, add the late-boundObjectcall shown above, and build. It should compile. - Change the setting to
Option Strict Onand rebuild. The late-bound call should now fail to compile. - Replace the
ObjectwithIHelperandTryCast, add theNothingcheck, and rebuild. The project should compile again. - Toggle
Option Inferon and off to observe how local variable types change in a small loop. - Inspect the
.vbprojfile for<OptionStrict>and<OptionInfer>settings, because project templates do not always agree.
Run these builds from the repository root using dotnet build <path-to-project> or from Visual Studio. You need write access to the project files to change the settings. The main risk is a large error count on the first rebuild; that is expected in legacy code and is the reason to migrate incrementally. To revert, remove the file-level directive or restore the previous <OptionStrict> value in the .vbproj; the change is source-controlled and only affects future builds.
Actionable Closing
Do not treat Option Strict On as a global switch to flip over a weekend. Pick one leaf project or one folder, enable Option Strict On and Option Infer On, and let the compiler enumerate the late-bound calls and narrowing conversions you have been carrying. Fix them with interfaces and explicit casts. Keep any Option Strict Off regions narrowly scoped and commented. Then rebuild and confirm that the class of runtime surprises you used to debug in production now stops the build instead.