Option Statements
|
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. Four statements, written
at the top of a source file or set once for a whole project, decide how strict the compiler is: Option Explicit (must a variable
be declared), Option Infer (may the compiler infer a type from an initializer), Option Strict (which conversions and which
kinds of late binding are errors) and Option Compare (how strings are compared by default). They are the most consequential
settings of the language. A project that compiles with Option Strict Off is silently a different dialect: values change type
at run time, a typo in a member name becomes a run-time exception, and a conversion that loses data raises no diagnostic.
The page covers each statement, where its value comes from and which value wins, exactly what Option Strict On rejects (with the
real compiler message of each case), the Custom mode that turns the same checks into warnings, the way the .NET SDK silently
suppresses those warnings, the effect of Option Compare Text on operators and on runtime-library functions, and a recommended
configuration for a solution. Conversions are the subject of
Type Conversions; late binding of Object is covered on
Early and Late Binding; the type system on
Data Types and Literals; and the build properties on
Projects, MSBuild and the dotnet CLI.
Every example is a complete program of the shelf library-catalogue scenario or a small project that uses its vocabulary. Each
program was built and run on the .NET 10 SDK in a container with Option Strict On, Option Explicit On, Option Infer On and
warnings as errors, and the output shown is the real one. Examples that are deliberately wrong are labelled, and the real compiler
messages of the build that rejects them are shown instead of run output.
The four statements at a glance
| Statement | Values | SDK project default | vbc default |
Introduced | Controls |
|---|---|---|---|---|---|
|
|
|
|
Visual Basic before .NET |
Whether every variable must be declared with |
|
|
|
|
Visual Basic .NET 2002 (7.0) |
Implicit narrowing conversions, late binding and implicit |
|
|
|
|
Visual Basic 9.0 (2008) |
Whether a local declaration without |
|
|
|
|
Visual Basic before .NET |
Default string comparison of |
Source: Option <keyword> Statement
The SDK column was read from the real build with dotnet msbuild -getProperty, so it is a fact about SDK 10.0.402 and not only
about the documentation. A project with no Option properties at all reports:
dotnet msbuild defs.vbproj -nologo -getProperty:OptionExplicit -getProperty:OptionStrict -getProperty:OptionInfer -getProperty:OptionCompare -getProperty:NoWarn
{
"Properties": {
"OptionExplicit": "On",
"OptionStrict": "Off",
"OptionInfer": "On",
"OptionCompare": "Binary",
"NoWarn": "41999,42016,42017,42018,42019,42020,42021,42022,42032,42036"
}
}
Source: -optionstrict
Two things in that output deserve attention before anything else. OptionStrict is Off by default: a project created with
dotnet new console -lang vb compiles code that every other mainstream typed language would reject, and the section recommends
turning it on in every project. And NoWarn is pre-filled with ten warning numbers; they are the warnings that the Custom mode of
Option Strict would report, which is why that mode does nothing in an SDK project until the list is replaced (see
Custom mode and the SDK NoWarn trap).
Where a setting comes from
A setting is resolved from three places, and the most specific one wins:
-
The file. An
Optionstatement at the top of a source file applies to that file only. It overrides everything below it. -
The project. An MSBuild property (
OptionStrict,OptionExplicit,OptionInfer,OptionCompare) is passed to the compiler as/optionstrict,/optionexplicit,/optioninferand/optioncomparefor every file that has no statement of its own. In Visual Studio the same values appear on the project’s Compile page. Putting the properties in aDirectory.Build.propsfile gives the whole directory tree the same value. -
The compiler default, which on
vbcis-optionstrict-,-optionexplicit+,-optioncompare:binaryand, through the response file shipped with the compiler,-optioninfer+. Visual Studio’s Tools > Options > Projects and Solutions > VB Defaults page supplies the initial values for new projects (documented asExplicit On,Strict Off,Compare BinaryandInfer On).
The Option statements of a file are subject to three placement rules, each enforced by the compiler:
-
They must appear before any other statement of the file: before
Imports, attributes and declarations. The order among theOptionstatements themselves is free, and conventionallyOption Strict Oncomes first. -
Each of the four statements may appear at most once per file.
-
They have file scope. There is no way to switch a setting on for a part of a file, and
Optionstatements cannot be placed in aModule,Classor procedure.
The compiler reports the violations like this (a file with a doubled Option Strict, a doubled Option Compare, and, in a second
build, an Option Explicit placed inside a module):
Program.vb(2,1): error BC30225: 'Option Strict' statement can only appear once per file.
Program.vb(4,1): error BC30225: 'Option Compare' statement can only appear once per file.
Program.vb(7,5): error BC30627: 'Option' statements must precede any declarations or 'Imports' statements.
Because the file wins over the project, the settings of an individual file can be tightened or relaxed without touching the project.
The next program shows it for Option Compare, the one statement that has a legitimate reason to differ between files. The project
sets <OptionCompare>Text</OptionCompare>; Program.vb has no statement and follows the project, and Exact.vb declares
Option Compare Binary and overrides it.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<OptionStrict>On</OptionStrict>
<OptionCompare>Text</OptionCompare>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
Module Program
Sub Main()
Dim stored As String = "Dune"
Dim typed As String = "DUNE"
Console.WriteLine("Program.vb, no Option Compare statement : " & (stored = typed).ToString())
Console.WriteLine("Exact.vb, Option Compare Binary : " & Exact.SameTitle(stored, typed).ToString())
End Sub
End Module
Option Compare Binary
Module Exact
Function SameTitle(left As String, right As String) As Boolean
Return left = right
End Function
End Module
Program.vb, no Option Compare statement : True
Exact.vb, Option Compare Binary : False
Source: Option Compare Statement
Program.vb in that listing has no Option Strict On line because the project property supplies it; that is the style recommended
for a solution (see the recommended configuration), and it is the one place where a missing
Option Strict On is not a defect. A reader of a file alone cannot see the project property, which is the argument for repeating
Option Strict On at the top of every file of a library that is published as source.
Option Explicit
Option Explicit On makes the use of an undeclared name a compile-time error. It is on by default in the SDK, in vbc and in Visual
Studio, and it is what makes a misspelled variable name fail to compile instead of creating a new variable. The declaration
statements it refers to are Dim, ReDim, Static, Const, For/For Each control variables, Using, Catch and the like (the
declaration statements themselves are explained on
Variables, Constants and Scope).
With Option Explicit Off, using a name that is not declared creates a local variable of type Object on the spot. The compiler
accepts a typo, and on Option Strict Off nothing flags it either. The next file combines Option Explicit Off with
Option Strict On, which is the only combination that is not completely silent: Option Strict On rejects what is done with the
implicitly declared variables, because they are Object. The program is deliberately wrong; it is built to show the diagnostics.
Option Strict On
Option Explicit Off
Module Program
Function CountCopies(loans As Integer(), onShelf As Integer) As Integer
copies = onShelf
For Each loaned As Integer In loans
copes = copies - loaned
Next
Return copies
End Function
Sub Main()
Console.WriteLine(CountCopies(New Integer() {1, 2}, 10))
End Sub
End Module
Program.vb(8,21): error BC30038: Option Strict On prohibits operands of type Object for operator '-'.
Program.vb(10,16): error BC30512: Option Strict On disallows implicit conversions from 'Object' to 'Integer'.
Source: Option Explicit Statement
Line 6 declares copies implicitly and line 8 misspells it as copes. Under Option Strict On both uses are flagged because the
implicit variables are Object (BC30038 for the subtraction, BC30512 for the Return). With Option Strict Off as well, the same
file builds, and CountCopies returns the wrong value or throws at run time depending on what the variables hold. A file that
only assigns and prints an implicitly declared variable, such as total = 5 followed by Console.WriteLine(total), also builds
under Option Strict On on SDK 10.0.402. Option Explicit Off is therefore not made safe by Option Strict On.
Facts worth knowing about Option Explicit:
-
Learn documents the page’s own recommendation:
Option Explicit Offis "generally not a good practice". There is no scenario in new code that needs it. It exists for source compatibility with Visual Basic 6, where implicit declaration was the default. -
The
Option Explicitstatement overrides the-optionexplicitcompiler option and theOptionExplicitMSBuild property for its file, in the same way as the other three. -
The correct fix for BC30451, "'name' is not declared. It may be inaccessible due to its protection level", is not always a
Dim: the name may be misspelled, out of scope, or declaredPrivatein another type. The message lists the protection level because it is the second most common cause. -
A C# program has no counterpart: C# always requires a declaration, and its
varis the counterpart ofOption Infer On, not of implicit declaration.
Option Infer
Option Infer On allows a local variable declared without an As clause to take the type of its initializer. It was added in
Visual Basic 9.0 together with LINQ, whose anonymous types and query variables need it, and it applies to local variables of
procedures only: not to fields, properties, parameters or function return types.
The combination of Dim, a type and an initializer is the following (the results mirror Learn’s table; the Strict On column was
checked with the compiler):
| Declaration | Option Infer |
Option Strict Off |
Option Strict On |
|---|---|---|---|
|
|
|
|
|
|
|
Error BC30209 (requires an |
|
any |
|
Error BC30209 |
|
|
|
|
|
any |
|
|
Source: Option Infer Statement
The exception on the fourth row is worth remembering: Dim something = Nothing is the one declaration without As that
Option Strict On accepts, and the variable it creates is an Object. Write Dim something As Book = Nothing instead. The type of
the variable is the type of the initializer, not what the program later assigns to it, and a numeric literal is as narrow as its
form says: Dim copies = 3 is an Integer, Dim big = 3000000000 is a Long, Dim price = 19.99 is a Double and
Dim price = 19.99D is a Decimal. The literal rules are on
Data Types and Literals.
Turning Option Infer off on top of Option Strict On makes Dim without As an error (BC30209) and also rejects the
For Each item In collection form, because the control variable’s type is inferred too (the compiler reports the loop variable as
undeclared, BC30451). There is no benefit in Option Infer Off for new code. It can be useful
for one purpose: to find every declaration without a type in code that was written without caution, by turning it off and reading
the errors.
Option Strict
Option Strict On has three effects, which Learn lists as the three conditions that cause a compile-time error. They correspond to
the three warning settings of the project designer’s Compile page:
-
Implicit narrowing conversions are errors. A conversion that can lose data or fail at run time (
DoubletoInteger,LongtoInteger,Stringto a number,Objectto anything, a base type to a derived type) must be written out with a conversion function orCType/DirectCast/TryCast. -
Late binding is an error. A member access on an expression of type
Objectis resolved at run time only withOption Strict Off; withOn, it does not compile. -
Implicit
Objecttyping is an error. A declaration, a function result, a parameter or a property without anAsclause is an error, except for theDim x = Nothingcase above.
Two further groups of rules are consequences of the first. Operators do not accept Object operands (=, + and the others raise
BC32013 or BC30038), and a number is never converted to Boolean, String or Date implicitly. The widening direction is
unrestricted: Integer to Long, Double or Decimal, Char to String, a derived type to its base, anything to Object, an
enumeration to its underlying type, and a constant to any numeric type that can hold its value.
What the compiler says
The next program collects the implicit conversions and the late binding that Option Strict On rejects. Every line that is marked
in the build output is a separate error, and the conversion-function replacement for each is on
Type Conversions. The program is deliberately wrong and was built to
show the diagnostics; it has no run output.
Option Strict On
Module Program
Sub Main()
Dim price As Double = 19.99
Dim whole As Integer = price
Dim anything As Object = "text"
Dim count As Integer = anything
Console.WriteLine(anything.Length)
Dim untyped
Dim flag As Boolean = "True"
Dim published As Date = "2026-10-19"
Dim label As String = 42
Dim initial As Char = "ab"
Dim average As Integer = 10 / 4
Dim cube As Integer = 2 ^ 3
End Sub
End Module
Program.vb(10,13): error BC30209: Option Strict On requires all variable declarations to have an 'As' clause.
Program.vb(10,13): warning BC42024: Unused local variable: 'untyped'.
Program.vb(11,31): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Boolean'.
Program.vb(12,33): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Date'.
Program.vb(13,31): error BC30512: Option Strict On disallows implicit conversions from 'Integer' to 'String'.
Program.vb(14,31): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Char'.
Program.vb(15,34): error BC30512: Option Strict On disallows implicit conversions from 'Double' to 'Integer'.
Program.vb(16,31): error BC30512: Option Strict On disallows implicit conversions from 'Double' to 'Integer'.
Program.vb(6,32): error BC30512: Option Strict On disallows implicit conversions from 'Double' to 'Integer'.
Program.vb(8,32): error BC30512: Option Strict On disallows implicit conversions from 'Object' to 'Integer'.
Program.vb(9,27): error BC30574: Option Strict On disallows late binding.
Source: Option Strict Statement
The unused untyped variable also draws warning BC42024, a diagnostic that exists independently of the Option statements. The
messages are listed in the compiler’s order, which is not the order of the lines. Notice what the output says about two lines that
look harmless: Dim average As Integer = 10 / 4 and Dim cube As Integer = 2 ^ 3 are rejected because / and ^ always return a
Double, so the result of dividing two Integer values needs \ (integer division) or CInt. The book errata list in the
section outline contains exactly this mistake, so the correct form is stated here: an Integer quotient is written with the
\ operator or an explicit CInt(a / b), and the exponentiation operator is followed by a conversion.
Declarations are checked before bodies, so a missing As on a parameter or a function stops the build earlier and hides the
other errors. This one-function file builds on its own:
Option Strict On
Module Declarations
Function Echo(value)
Return value
End Function
End Module
Declarations.vb(4,14): error BC30210: Option Strict On requires all Function, Property, and Operator declarations to have an 'As' clause.
Declarations.vb(4,19): error BC30211: Option Strict On requires that all method parameters have an 'As' clause.
The second group is what the operators and the control statements reject. It was built from the following program, which is deliberately wrong as well:
Option Strict On
Module Program
Sub Take(ByRef n As Integer)
n += 1
End Sub
Sub Main()
Dim a As Object = 5
Dim b As Object = 6
Console.WriteLine(a = b)
Console.WriteLine(a + b)
If a Then Console.WriteLine("truthy")
Dim something = Nothing
Console.WriteLine(something Is Nothing)
Dim total As Long = 10
Take(total)
For Each i As Integer In New Long() {1L, 2L}
Console.WriteLine(i)
Next
For i As Integer = 1 To 3.5
Console.WriteLine(i)
Next
Dim flag As Boolean = 1
Select Case a
Case 5
Console.WriteLine("five")
End Select
Dim items As Object() = {1, 2}
Dim n As Integer = items(0)
End Sub
End Module
Program.vb(11,27): error BC32013: Option Strict On disallows operands of type Object for operator '='. Use the 'Is' operator to test for object identity.
Program.vb(11,31): error BC32013: Option Strict On disallows operands of type Object for operator '='. Use the 'Is' operator to test for object identity.
Program.vb(12,27): error BC30038: Option Strict On prohibits operands of type Object for operator '+'.
Program.vb(12,31): error BC30038: Option Strict On prohibits operands of type Object for operator '+'.
Program.vb(13,12): error BC30512: Option Strict On disallows implicit conversions from 'Object' to 'Boolean'.
Program.vb(17,14): error BC30512: Option Strict On disallows implicit conversions from 'Long' to 'Integer'.
Program.vb(21,33): error BC30512: Option Strict On disallows implicit conversions from 'Double' to 'Integer'.
Program.vb(24,31): error BC30512: Option Strict On disallows implicit conversions from 'Integer' to 'Boolean'.
Program.vb(25,21): error BC32013: Option Strict On disallows operands of type Object for operator '='. Use the 'Is' operator to test for object identity.
Program.vb(30,28): error BC30512: Option Strict On disallows implicit conversions from 'Object' to 'Integer'.
Source: Comparison Operators
Read the output against the source, because three lines compile although they look similar to rejected ones:
-
Line 14,
Dim something = Nothing, builds (the exception from the table). -
Line 18,
For Each i As Integer In New Long() {1L, 2L}, builds. Learn documents that narrowing conversions from the elements of aFor Eachcollection to the loop variable are not checked, even withOption Strict On. The conversion happens at run time and throwsOverflowExceptionif aLongelement does not fit. Prefer the loop variable of the element’s own type. -
Line 17,
Take(total), is an error althoughTaketakesByRef: aByRefargument must be of exactly the parameter’s type, or the conversion in either direction would have to be checked.
Line 12, a + b over two Object values, is BC30038 and not BC32013, which is the one for =. The message of BC32013 contains the
useful hint: to test whether two Object expressions are the same instance, use Is. A comparison of values needs a conversion first:
CInt(a) = CInt(b) or, for a type check, TypeOf a Is Integer.
The error catalogue
| Code | Message (abridged) | Typical cause and fix |
|---|---|---|
BC30512 |
Option Strict On disallows implicit conversions from 'X' to 'Y'. |
A narrowing conversion, or an unrelated pair such as |
BC30574 |
Option Strict On disallows late binding. |
A member was looked up on an |
BC30209 |
Option Strict On requires all variable declarations to have an 'As' clause. |
|
BC30210 |
Option Strict On requires all Function, Property, and Operator declarations to have an 'As' clause. |
A function or property without its return type. |
BC30211 |
Option Strict On requires that all method parameters have an 'As' clause. |
A parameter without its type. |
BC30038 |
Option Strict On prohibits operands of type Object for operator 'x'. |
Arithmetic or concatenation over an |
BC32013 |
Option Strict On disallows operands of type Object for operator '='. |
The comparison operators over two |
BC42016 |
Implicit conversion from 'X' to 'Y'. |
The warning counterpart of BC30512 in |
BC42017 |
Late bound resolution; runtime errors could occur. |
The warning counterpart of BC30574. |
BC42020 |
Variable declaration without an 'As' clause; type of Object assumed. |
The warning counterpart of BC30209. |
Source: Visual Basic error messages
The error-message index of the documentation does not describe every one of these numbers (BC30512, BC30574 and BC30209 are described in the compiler output and in the Option Strict article, not on a page of their own), so the table above is built from the compiler’s messages as printed by SDK 10.0.402.
What Option Strict Off does instead
Rewriting a legacy project for Option Strict On is easier when you know what each error replaces. The following behaviour was
observed on SDK 10.0.402 in a throwaway project with a file-level Option Strict Off; it is listed here as a description of
what the conversions do, and the page deliberately contains no listing of that code.
Statement under Option Strict Off |
What happens |
|---|---|
|
Compiles; |
|
Compiles; |
|
Compiles and parses the string. |
|
Compiles and parses the string with the current culture. |
|
Compiles; the text is |
|
Is the |
|
Is the |
|
Compiles; throws |
|
Compiles; throws |
Each of these is a run-time failure or a silent change of meaning that Option Strict On turns into a compile-time message. The
rounding of the first row is the banker’s rounding of the conversion functions, covered on
Type Conversions.
Widening, inference and the constants
What Option Strict On still accepts is the reason it is practical. The following program is complete and warning-free. It shows the
inferred types, the widening conversions that stay implicit, the rule for constants, the For Each exception and the operators
that return a fixed type:
Option Strict On
Option Infer On
Imports System.Collections.Generic
Module Program
Sub Main()
' Option Infer On: the type comes from the initializer.
Dim title = "Dune"
Dim copies = 3
Dim price = 19.99D
Dim ratio = copies / 2
Dim shelf = New List(Of String) From {"Dune", "Emma"}
Dim nothingYet = Nothing
Console.WriteLine(TypeName(title) & ", " & TypeName(copies) & ", " & TypeName(price))
Console.WriteLine(TypeName(ratio) & ", " & TypeName(shelf) & ", " & TypeName(nothingYet))
' Widening conversions are implicit and allowed under Option Strict On.
Dim small As Short = 12
Dim wide As Long = copies
Dim real As Double = wide
Dim money As Decimal = copies
Dim letter As Char = "A"c
Dim text As String = letter
Dim boxed As Object = text
Console.WriteLine(small.ToString() & " " & wide.ToString() & " " & real.ToString() & " " & money.ToString() & " " & text & " " & TypeName(boxed))
' A constant that fits needs no cast, a run-time value does.
Dim byteValue As Byte = 200
Dim fromRuntime As Integer = copies * 100
Dim narrowed As Byte = CByte(fromRuntime Mod 256)
Console.WriteLine(byteValue.ToString() & " " & narrowed.ToString())
' For Each is the documented exception: element conversions are not checked.
For Each n As Integer In New Long() {1L, 2L}
Console.Write(n.ToString() & " ")
Next
Console.WriteLine()
' Integer division and Mod keep integers, / and ^ always return Double.
Console.WriteLine((7 \ 2).ToString() & " " & (7 Mod 2).ToString() & " " & (7 / 2).ToString() & " " & (2 ^ 3).ToString())
Console.WriteLine(TypeName(7 \ 2) & " " & TypeName(7 / 2) & " " & TypeName(2 ^ 3))
End Sub
End Module
String, Integer, Decimal
Double, List(Of String), Nothing
12 3 3 3 A String
200 44
1 2
3 1 3.5 8
Integer Double Double
Source: Data Type Summary
The line Dim byteValue As Byte = 200 compiles because 200 is a constant that fits a Byte; Dim byteValue As Byte = 300 is
BC30439 ("Constant expression not representable in type 'Byte'") under every setting. The run-time value on the next line has to be
narrowed explicitly, and CByte(fromRuntime Mod 256) makes the intent visible and the range impossible to exceed. Also notice that
7 / 2 is 3.5 and 7 \ 2 is 3: Option Strict On never changes what an operator returns, only what may be assigned to what.
Custom mode and the SDK NoWarn trap
Option Strict has a third state, Custom, in which the compiler applies the Strict-On analysis but reports each finding as a
warning instead of an error. It is what Visual Studio shows on the Compile page when the three warning configurations are not all
Error and not all None, and it is what the -optionstrict:custom switch of vbc selects. It is the right tool for a migration:
turn it on, then move warnings to errors one category at a time. The ten warnings are BC41999, BC42016 to BC42022, BC42032 and BC42036
(implicit conversion, late bound resolution, implicit Object types and the related cases).
An SDK-style project has two traps that the documentation does not mention:
-
The MSBuild property
OptionStrictis a Boolean for the compiler task. Writing<OptionStrict>Custom</OptionStrict>fails the build witherror MSB4030: "Custom" is an invalid value for the "OptionStrict" parameter of the "Vbc" task. Custom mode is selected with the separate propertyOptionStrictType:<OptionStrictType>Custom</OptionStrictType>. -
The SDK’s props file defines
NoWarnas41999,42016,42017,42018,42019,42020,42021,42022,42032,42036when the project has not set it. Those are exactly the warnings of Custom mode, so a project that setsOptionStrictTypetoCustomand nothing else builds with no diagnostics at all. Any non-emptyNoWarnvalue of your own replaces the default, and a project that wants Custom mode has to set one (the number is arbitrary;42353is a warning number that this project never produces).
The following project does both and escalates two of the ten warnings. The source file has no Option statements, so the project
properties decide:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>Ex5</RootNamespace>
<OptionStrictType>Custom</OptionStrictType>
<!-- The SDK suppresses the Option Strict warnings by default; any non-empty NoWarn replaces that list. -->
<NoWarn>42353</NoWarn>
<!-- Escalate two of the ten: implicit conversion and late binding. -->
<WarningsAsErrors>$(WarningsAsErrors);42016;42017</WarningsAsErrors>
<NuGetAudit>false</NuGetAudit>
</PropertyGroup>
</Project>
Module Program
Sub Main()
Dim price As Double = 19.99
Dim whole As Integer = price
Dim anything As Object = "text"
Console.WriteLine(anything.Length)
Dim untyped
Console.WriteLine(whole)
End Sub
End Module
Program.vb(4,32): error BC42016: Implicit conversion from 'Double' to 'Integer'.
Program.vb(6,27): error BC42017: Late bound resolution; runtime errors could occur.
Program.vb(7,13): warning BC42020: Variable declaration without an 'As' clause; type of Object assumed.
Program.vb(7,13): warning BC42024: Unused local variable: 'untyped'.
Source: -warnaserror
BC42016 and BC42017 are errors because they are in WarningsAsErrors; BC42020 and BC42024 stay warnings. The build fails, so there is no run output, and the
example shows that the same findings that Option Strict On reports as BC30512 and BC30574 appear here under the warning numbers.
A warning number can also be moved in a .editorconfig, which is more convenient when the project has many files and a few
exceptions. With OptionStrictType set to Custom and NoWarn replaced as above, this file made BC42016 an error and silenced
BC42020, while BC42017 stayed a warning:
root = true
[*.vb]
dotnet_diagnostic.BC42016.severity = error
dotnet_diagnostic.BC42020.severity = none
Source: -nowarn
The command-line forms of the same switches are -optionstrict+, -optionstrict-, -optionstrict:custom, -nowarn:42016,42017 and
-warnaserror:42016,42017. The Visual Studio walkthrough is Learn’s
Configuring warnings in Visual Basic.
For a project that is already at Option Strict On, none of this is needed: Custom mode is a path towards On, not a place to stay.
Option Compare
Option Compare selects the default method used when two strings are compared:
-
Binarycompares the numeric values of the characters (UTF-16 code units, in practice). The comparison is case-sensitive and culture-independent, fast, and gives the orderA < B < Z < a < b < z. -
Textcompares by the lexical meaning of the characters in the current culture, ignoring case, kana type and character width. It is slower and gives the orderA = a < B = b < Z = z(Learn’s example adds the accented letters of code page 1252).
Learn’s description of Text is "determined by your system’s locale", so a program that depends on the culture-specific part can
behave differently on two machines. The case-insensitive part is reliable.
The setting affects:
-
The comparison operators
=,<>,<,<=,>,>=overStringoperands. -
Like, whose literal characters and ranges are compared by the same method ("abc" Like "A*"isTrueunderTextandFalseunderBinary; theLikeoperator is supported on .NET, contrary to a stale note on its Learn page). -
Select Caseover strings. -
The runtime-library functions that have an optional
Compareparameter:InStr,InStrRev,StrComp,Replace,Split,Filter. The compiler passes the file’s setting for that argument, which means that the default value shown in the documentation is not what the function receives (see the next listing:Replacedefaults toBinaryin its signature, but on aTextfile it replaces both cases).
The setting does not affect anything in the .NET Base Class Library:
-
String.Equals,String.Compare,String.Contains,String.StartsWithand the==-like methods follow their own rules (StringComparison), which default to ordinal forEqualsandContains. -
Collections and hashes:
Dictionary(Of String, T)uses the default comparer (ordinal) unless one is given. It is the most common surprise:"Shelf" = "SHELF"isTruein the file butContainsKey("SHELF")on a dictionary keyed by"Shelf"isFalse. -
LINQ methods such as
Distinct,ContainsandGroupByover strings use the comparer of the method (ordinal by default), not the file’s option. AWhere t = "DUNE"inside a query expression is different: there=is the Visual Basic operator, so it followsOption Compare. The ordering ofOrder Byis culture-dependent and is not controlled by the option either.
The next program shows the whole matrix on one file with Option Compare Text:
Option Strict On
Option Compare Text
Imports System.Collections.Generic
Imports System.Linq
Imports Microsoft.VisualBasic
Module Program
Sub Main()
Dim stored As String = "Shelf"
Dim typed As String = "SHELF"
' Operators and Select Case follow the file's Option Compare setting.
Console.WriteLine("stored = typed : " & (stored = typed).ToString())
Console.WriteLine("stored <> typed : " & (stored <> typed).ToString())
Console.WriteLine("abc Like A* : " & ("abc" Like "A*").ToString())
Select Case typed
Case "shelf"
Console.WriteLine("Select Case : matched")
Case Else
Console.WriteLine("Select Case : missed")
End Select
' Runtime-library functions with an optional Compare argument take the file's default.
Console.WriteLine("InStr default : " & InStr("Library Shelf", "shelf").ToString())
Console.WriteLine("InStr Binary : " & InStr(1, "Library Shelf", "shelf", CompareMethod.Binary).ToString())
Console.WriteLine("StrComp default : " & StrComp(stored, typed).ToString())
Console.WriteLine("StrComp Binary : " & StrComp(stored, typed, CompareMethod.Binary).ToString())
Console.WriteLine("Replace default : " & Replace("Shelf SHELF", "shelf", "x"))
Console.WriteLine("Split default parts : " & Split("a-B-c", "b").Length.ToString())
' The .NET Base Class Library ignores Option Compare completely.
Console.WriteLine("String.Equals : " & String.Equals(stored, typed).ToString())
Console.WriteLine("stored.Contains(HEL) : " & stored.Contains("HEL").ToString())
Dim keys As New Dictionary(Of String, Integer)()
keys("Shelf") = 1
Console.WriteLine("Dictionary.ContainsKey : " & keys.ContainsKey("SHELF").ToString())
' In a query expression, = is the VB operator; Distinct uses the .NET default comparer.
Dim titles As String() = {"dune", "Dune", "Emma", "emma"}
Dim matches = From t In titles Where t = "DUNE" Select t
Console.WriteLine("Query Where t = DUNE : " & matches.Count().ToString())
Console.WriteLine("Distinct count : " & titles.Distinct().Count().ToString())
End Sub
End Module
stored = typed : True
stored <> typed : False
abc Like A* : True
Select Case : matched
InStr default : 9
InStr Binary : 0
StrComp default : 0
StrComp Binary : 1
Replace default : x x
Split default parts : 2
String.Equals : False
stored.Contains(HEL) : False
Dictionary.ContainsKey : False
Query Where t = DUNE : 2
Distinct count : 4
Source: Strings.InStr
InStr("Library Shelf", "shelf") is 9 because the match ignores case; the explicit CompareMethod.Binary argument in the next
line gives 0. StrComp returns 0, -1 or 1 and follows the same rule. The last three lines are the .NET side: the dictionary, Equals and
Contains are ordinal whatever the file says.
Guidance on the choice:
-
Use
Binary(the default) everywhere you can. It is predictable, matches C#, and is the best performing option. For identifiers, keys, ISBNs, file names and protocol text the ordinal comparison is the correct one. Case-insensitive logic belongs at the call site, where it is visible:String.Equals(a, b, StringComparison.OrdinalIgnoreCase)for identifiers, orStringComparer.CurrentCultureIgnoreCasefor user-facing sorting. -
Use
Option Compare Textonly in a file whose whole purpose is case-insensitive text matching with the VB operators (a small command-parsing module, for example), and say so in a comment. It is a file-level setting precisely so that it stays local. -
Never set
Textfor a whole project. Code in other files then compares differently from what its author expects, and a reader cannot see why. -
In a library meant for others, state
Option Compare Binaryexplicitly at the top of the file if the logic depends on it, so that a project-levelOptionCompareproperty cannot change it.
The recommended configuration
For a new solution, set the four values once in a Directory.Build.props file at the repository root. This is the file the
shelf solution uses (it builds all of its projects, including the library, the console application and the tests, warning-free):
<Project>
<PropertyGroup>
<OptionExplicit>On</OptionExplicit>
<OptionStrict>On</OptionStrict>
<OptionInfer>On</OptionInfer>
<OptionCompare>Binary</OptionCompare>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
Source: -optionstrict
Notes on the choice:
-
Explicit OnandInfer Onare already the SDK defaults, and are written out so that the file documents the intent and survives a change of defaults.Strict Onis the one that changes behaviour. -
Infer Onis not a loophole ofStrict On; it is what letsDim books = catalog.Search(term)stay readable. The type is still static and checked. -
In an existing project, the migration path is
OptionStrictTypeCustom with the SDKNoWarnreplaced, escalating one category of warning at a time. Late binding (BC42017) is the category that needs the most thought, because it points at Office automation, COM and reflection code where a typed alternative may not exist. For such a file, the file-level statement is the supported escape hatch, and the page Early and Late Binding shows its single legitimate use. -
The
.editorconfigseverities (dotnet_diagnostic.BCnnnnn.severity) complement the options and do not replace them. -
Generated files such as designer files carry their own
Optionstatements and are not affected by the properties of the project for the settings they declare. -
The Visual Studio VB Defaults page changes only the initial values of new projects. Changing them in the IDE does not change an existing project; the property in the project file does.
Differences from C#
| Concern | Visual Basic | C# |
|---|---|---|
Undeclared variable |
Error with |
Always an error. |
Implicit narrowing |
Error with |
Always an error; |
Late binding |
Allowed with |
Only with the explicit |
Type inference |
|
|
String comparison |
|
|
Where it is set |
File statement, project property, |
Language version, nullable and analyzer severities; no equivalent of the four options. |
The practical conclusion for a team that uses both languages is that Visual Basic with Option Strict On is the same
language as C# for the purposes of conversions and type safety; the rest of the section assumes that setting.
Pitfalls
| Pitfall | What to do |
|---|---|
A project created from the SDK template has |
Set the properties in |
|
Grep for files without the statement, or set the property. A |
|
Use |
Custom mode builds with no warnings at all. |
The SDK |
A |
The option affects operators and VB functions only. Give the dictionary a comparer. |
A culture-dependent |
Tests that run on a machine with another culture may order or compare differently. Use |
The |
Not checked at compile time. Use |
|
Keep |
Where the books are silent or wrong
Neither of the two reference books for this section mentions Option Strict: they teach Option Explicit as a historical Visual
Basic 6 setting and mix declared and implicit variables. Quickstart Guide p. 31 labels "implicit declaration" what is really
type inference through Option Infer, and its Option Explicit example is wrong in both dialects. The correct reading is the one
on this page: Option Explicit is about declaring names, Option Infer is about typing a declared name from its initializer,
and Option Strict is about what may be converted and bound at run time. Mastering VB.NET p. 43 assigns the result of / and of ^ to an Integer variable, which is an error under Option Strict On
because both operators return Double; the correct forms are the \ operator or an explicit conversion, as shown above. Both books
were written for a toolchain whose templates compile such code, and the SDK’s own Option Strict Off default is the reason it still
compiles in a new project today.