Projects, MSBuild and the dotnet CLI
|
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, vbc compiler version 5.9.0-1.26505.111) and was checked on
2026-10-19. Everything marked "built and run" was produced in the .NET 10 SDK container (Linux, arm64) with the real dotnet
and vbc commands; the output shown is the output printed. Compiler and MSBuild messages are quoted as printed, so root
namespaces and paths in them (/w/...) come from the scratch projects. Project examples are small console programs that start
with Option Strict On, or are project files with no code at all. Anything that needs Windows is labelled "built, not run" or
"evaluated only". Properties and options are listed by their real names, and the lists were checked against the
files the SDK ships (the VB .props and .targets files, Microsoft.VisualBasic.Core.targets and the output of vbc -help).
The project file is where almost every compiler setting in a Visual Basic program lives, and the VB defaults differ from the
C# defaults in ways that matter: the SDK ships OptionStrict as Off and a NoWarn list that hides the Option Strict
warnings, and several properties known from C# do nothing. Reading this page you will learn what a modern .vbproj contains, how each
property becomes a vbc option (all of them, by name), which properties are shared with C# and which exist only for VB, how
to set the language version, imports, conditional-compilation symbols and warning policy, how to share settings across a solution,
how the dotnet command line drives the same machinery, how Visual Studio’s property pages map onto it, and how the legacy (non-SDK)
project format differs. The language-level meaning of Option Strict and its siblings is on
Option Statements, the #If directive and symbols on
Conditional Compilation and Directives, the My
types on The My Namespace, and packaging and publish modes on
Libraries, Packaging and Deployment.
The SDK-style project file
A Visual Basic project today is an SDK-style project: a short XML file whose root element names an SDK
(Sdk="Microsoft.NET.Sdk"). The SDK supplies, through imported .props and .targets files, everything a project used to list by
hand: the source files (a glob), the references to the framework, the default imports, the compiler settings and the build targets.
This is what dotnet new writes for the three most common project types (the sources are in the next sections; the listing shows
the project files exactly as the templates wrote them):
<!-- dotnet new classlib -lang VB -o Shelf.Core -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Shelf.Core</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
</Project>
<!-- dotnet new console -lang VB -o Shelf.Cli, then: dotnet add Shelf.Cli reference Shelf.Core -->
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Shelf.Core\Shelf.Core.vbproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>Shelf.Cli</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
</Project>
<!-- dotnet new mstest -lang VB -o Shelf.Tests -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest" Version="4.0.2" />
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
</Project>
Three things stand out. A library has no OutputType (the default is Library), a console application has OutputType Exe, and
RootNamespace is written into Visual Basic templates (C# templates have no such property, see the next table). The
test template carries LangVersion latest (harmless: it means the newest version) and three C#-isms, ImplicitUsings, Nullable and a Using item, that have no effect in a Visual Basic project: the captured
vbc command lines in this page do not change when they are set (see
Properties that do nothing in Visual Basic). The
test template’s own sources and the way the tests run are on Testing.
The project name matters more in VB than in C#, because three names are derived from it and they are independent:
| Property | Controls | Default |
|---|---|---|
|
The file name of the output ( |
The project file name without extension. |
|
The namespace the compiler prefixes to every |
The project file name, with characters that are not valid in an identifier replaced. |
|
The NuGet package id (see Libraries, Packaging and Deployment). |
|
What the SDK adds to your project
The following are not in the project file above, but are in force for every Microsoft.NET.Sdk Visual Basic project. Each was observed with
dotnet msbuild -getProperty / -getItem, or in the captured command line below.
| Implicit | What it is |
|---|---|
Source files |
|
Framework reference |
The |
Visual Basic runtime |
|
Global imports |
|
Option defaults |
|
Warning defaults |
|
Conditional symbols |
|
Generated source |
|
Analyzers |
The .NET analyzers ( |
Where does it come from? The Sdk attribute makes MSBuild import Sdk.props before the project body and Sdk.targets after it. The
VB-specific pieces are two small files in the SDK’s Sdks/Microsoft.NET.Sdk/targets folder, selected by the project extension:
| File (under the SDK folder) | Contents |
|---|---|
|
Imported by |
|
Imported by |
|
The |
|
Behaviour shared with C#: |
Because those are plain text files in the installed SDK, every statement about defaults on this page can be checked on your machine
with dotnet msbuild -getProperty:Name, and the files are the authoritative reference when the documentation lags behind (it
does, as the sections below point out).
The default items and the NETSDK1022 error
The glob is the single most common cause of a first migration error: a project converted from the old format still lists every file, so the SDK sees each file twice.
<!-- P.vbproj : SDK-style project that also lists a file the glob already included -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>P</RootNamespace>
<NuGetAudit>false</NuGetAudit>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.vb" />
</ItemGroup>
</Project>
$ dotnet build
/usr/share/dotnet/sdk/10.0.402/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.DefaultItems.Shared.targets(190,5): error NETSDK1022: Duplicate 'Compile' items were included. The .NET SDK includes 'Compile' items from your project directory by default. You can
(The message continues with the two ways out; the line is cut here.) You either delete the Compile item, or switch the glob off
with EnableDefaultCompileItems set to false and list every file yourself. To exclude files from the glob, use Remove:
<ItemGroup>
<Compile Remove="Old/**" />
</ItemGroup>
With Old/Legacy.vb (a second Sub Main in the same folder tree) the project built with 0 warnings and 0 errors and printed main, and
dotnet msbuild -getItem:Compile listed only Program.vb: the removed file was never compiled. With EnableDefaultCompileItems
set to false and <Compile Include="Program.vb" /> the same query also listed only Program.vb. Built and run: 2 projects.
From project properties to vbc options
dotnet build does not understand Visual Basic; it asks MSBuild to evaluate the project and run its targets. One of those targets,
CoreCompile, calls the Vbc task, and the task turns property values into a command line for the Roslyn Visual Basic compiler.
So a property is never "applied" by the compiler: each property named below is read by the SDK’s targets and turned into
zero or more vbc options. The diagram shows the path, and the table after it lists the option each property produces.
The second diagram groups the properties by what they control and shows the vbc options they become. Where a property
has no option of its own (Configuration, DefineDebug) it feeds an option indirectly; the full table is in
the property reference and the option side is in the vbc option reference.
The real command line
The next project sets 18 properties and one Import item. It is a deliberately busy .vbproj; every property is explained in the
sections below.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>Ks</RootNamespace>
<AssemblyName>Ks</AssemblyName>
<StartupObject>Ks.Program</StartupObject>
<LangVersion>latest</LangVersion>
<OptionStrict>On</OptionStrict>
<OptionExplicit>On</OptionExplicit>
<OptionInfer>On</OptionInfer>
<OptionCompare>Text</OptionCompare>
<RemoveIntegerChecks>true</RemoveIntegerChecks>
<DefineConstants>$(DefineConstants),FEATURE_X=1,GREETING="hi"</DefineConstants>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsNotAsErrors>BC42024</WarningsNotAsErrors>
<NoWarn>$(NoWarn);BC42350</NoWarn>
<Deterministic>true</Deterministic>
<DebugType>embedded</DebugType>
<Optimize>false</Optimize>
<PlatformTarget>x64</PlatformTarget>
<NuGetAudit>false</NuGetAudit>
</PropertyGroup>
<ItemGroup>
<Import Include="System.Text" />
</ItemGroup>
</Project>
dotnet build -v:diag prints the command line the Vbc task ran. Shown here one option per line with the 167 reference assemblies and a few paths shortened and the NETnn_0_OR_GREATER version symbols dropped from /define:
<sdk>/Roslyn/bincore/vbc
/noconfig
/imports:System.Text,Microsoft.VisualBasic,System,System.Collections,System.Collections.Generic,System.Diagnostics,System.Linq,System.Xml.Linq,System.Threading.Tasks
/optioncompare:Text
/optionexplicit+
/optionstrict+
/nowarn:41999,42016,42017,42018,42019,42020,42021,42022,42032,42036,BC42350,BC41997
/nosdkpath
/optioninfer+
/nostdlib
/errorreport:prompt
/platform:x64
/removeintchecks+
/rootnamespace:Ks
/highentropyva+
/vbruntime:<ref>/Microsoft.VisualBasic.dll
/doc:obj/Debug/net10.0/Ks.xml
/define:"CONFIG=\"Debug\",DEBUG=-1,TRACE=-1,PLATFORM=\"AnyCPU\",,FEATURE_X=1,GREETING=\"hi\",_MyType=\"Empty\",NET=-1,NET10_0=-1,NETCOREAPP=-1"
/reference:<ref>/Microsoft.CSharp.dll,<ref>/Microsoft.VisualBasic.Core.dll,... (167 assemblies)
/main:Ks.Program
/debug+
/debug:embedded
/filealign:512
/optimize-
/out:obj/Debug/net10.0/Ks.dll
/refout:obj/Debug/net10.0/refint/Ks.dll
/target:exe
/warnaserror+
/utf8output
/deterministic+
/langversion:latest
/analyzerconfig:obj/Debug/net10.0/Ks.GeneratedMSBuildEditorConfig.editorconfig
/analyzerconfig:<sdk>/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_10_default.globalconfig
/analyzer:<sdk>/Sdks/Microsoft.NET.Sdk/targets/../analyzers/Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers.dll
/analyzer:<sdk>/Sdks/Microsoft.NET.Sdk/targets/../analyzers/Microsoft.CodeAnalysis.NetAnalyzers.dll
Program.vb
"obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.vb"
obj/Debug/net10.0/Ks.AssemblyInfo.vb
/warnaserror+:NU1605,SYSLIB0011
/warnaserror-:BC42024
(<sdk> is /usr/share/dotnet/sdk/10.0.402 and <ref> is the .NET 10 reference pack folder.) Read it against the project:
-
OptionCompareTextbecame/optioncompare:Text;OptionStrictOnbecame/optionstrict+;RemoveIntegerChecksbecame/removeintchecks+. -
NoWarngot the SDK’s default list plusBC42350plusBC41997, which the SDK appends for .NET (Core) targets. The leadingBCis accepted. -
DefineConstantsis a comma separated list; the SDK prependsCONFIG,DEBUG,TRACEandPLATFORM, leaves an empty entry (,,) where the empty$(DefineConstants)was, and appends_MyType="Empty"and the framework symbols. The string valueGREETING="hi"had its quotes escaped. -
StartupObjectbecame/main:Ks.Program, andGenerateDocumentationFilebecame/doc:with a path underobj. -
TreatWarningsAsErrorsbecame/warnaserror+,WarningsNotAsErrorsa trailing/warnaserror-:BC42024, and the SDK’s ownWarningsAsErrors(NU1605;SYSLIB0011) a/warnaserror+:list. -
/noconfig,/nostdlib,/nosdkpath,/errorreport:prompt,/highentropyva+,/filealign:512,/utf8output,/refout:and the two analyzers were not asked for: they are SDK defaults. -
The generated
AssemblyInfo.vband the assembly-attributes file are compiled together with your source.
The command line is the same one you would type by hand (see Running vbc by hand); only -v:diag or a binary log shows it
during a normal build.
Printing the command line without compiling
Reading -v:diag output (it is roughly 50,000 lines for a tiny project) is clumsy. The SDK has a switch meant for IDEs,
ProvideCommandLineArgs, which makes the Vbc task return its arguments as an item instead of running the compiler. With the
-getTargetResult switch of dotnet msbuild you get one JSON entry per argument:
dotnet msbuild Map.vbproj -nologo -v:q -t:ResolveReferences,CoreCompile \
-p:SkipCompilerExecution=true -p:ProvideCommandLineArgs=true -getTargetResult:CoreCompile
This is how every row of the property table below was produced: the command is run with the baseline project and once more with
one extra -p:Name=Value, and the lines that changed are the option(s) that property controls. The baseline (a dotnet new console -lang VB
project renamed Map, default configuration) is:
/noconfig
/imports:Microsoft.VisualBasic,System,System.Collections,System.Collections.Generic,System.Diagnostics,System.Linq,System.Xml.Linq,System.Threading.Tasks
/optioncompare:Binary
/optionexplicit+
/optionstrict:custom
/nowarn:41999,42016,42017,42018,42019,42020,42021,42022,42032,42036,BC41997
/nosdkpath
/optioninfer+
/nostdlib
/errorreport:prompt
/rootnamespace:Map
/highentropyva+
/vbruntime:<ref>/Microsoft.VisualBasic.dll
/define:"CONFIG=\"Debug\",DEBUG=-1,TRACE=-1,PLATFORM=\"AnyCPU\",_MyType=\"Empty\",NET=-1,NET10_0=-1,NETCOREAPP=-1,..."
/debug+
/debug:portable
/filealign:512
/optimize-
/out:obj/Debug/net10.0/Map.dll
/refout:obj/Debug/net10.0/refint/Map.dll
/target:exe
/warnaserror-
/utf8output
/deterministic+
/warnaserror+:NU1605,SYSLIB0011
The first surprise is /optionstrict:custom. The SDK’s default of OptionStrict Off does not
become /optionstrict-; it becomes custom, a mode that raises the individual Option Strict diagnostics as warnings. Those warnings
(BC42016 to BC42022 and a few more) are then silenced by the SDK’s default NoWarn, which is why a default project accepts
implicit narrowing silently. The consequence is demonstrated in The NoWarn trap.
Default values of a VB console project
dotnet msbuild -getProperty:Name prints the final value of a property after evaluation, with no build. Run on a fresh dotnet new console -lang VB
project renamed App (net10.0, Debug unless stated), it gives the following. An empty value means "not set"; the SDK’s targets then behave as the third column says.
| Property | Value | Meaning of the default |
|---|---|---|
|
|
Set by the console template; a library project has no value and builds as |
|
|
Both come from the project file name. |
|
(empty) |
The compiler looks for exactly one |
|
(empty) |
No |
|
|
Becomes |
|
|
Same as the language defaults. |
|
(empty) |
Integer overflow is checked; |
|
|
The SDK passes the reference pack’s |
|
|
Decide whether |
|
(empty) |
Your own symbols; the SDK builds the final list in |
|
|
No |
|
|
The SDK default list, hidden warnings of Option Strict Custom and similar. |
|
|
Warnings are reported and do not fail the build. |
|
|
Two diagnostics are errors in every VB project. |
|
|
Release optimizes and emits no PDB. |
|
|
|
|
(empty), |
|
|
|
|
|
|
The SDK writes |
|
|
The .NET analyzers run; code-style rules are IDE-only unless you opt in (see XML Documentation, Analyzers and Code Style). |
|
(empty), (empty) |
The build server is used (the targets default it to |
|
|
The |
|
(empty) |
Not set, and |
|
|
Derived from |
The Configuration changes the generated options as follows (captured with the same method, one extra -p:Configuration=...):
| Configuration | Symbols in /define |
Debug options | Other options |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
The symbol that equals the upper-cased configuration name (RELEASE, STAGING) comes from ImplicitConfigurationDefine; set
DisableImplicitConfigurationDefines to true to stop that. DEBUG and TRACE come from DefineDebug and DefineTrace, which the
VB .props file sets per configuration. Setting DefineDebug to false on a Debug configuration does not remove DEBUG: the observed
list still contains DEBUG=-1 (it moves to the end), because the implicit configuration define re-adds it. To really drop
DEBUG in a Debug build, change Configuration, or set DisableImplicitConfigurationDefines and DefineDebug to false together.
Property reference
Every MSBuild property the Visual Basic targets read is listed here by group, each with its default, the vbc option it
produces (verified with the method above) and its purpose. The Vbc task parameter list in
The Vbc task parameters is the complete set; properties that exist only to be read by other targets (Version,
Company, PackageId) are covered in
Libraries, Packaging and Deployment.
The values in the "Observed" column were emitted by the property named, in an otherwise default project.
Output and identity
| Property | Observed vbc option |
Default | Purpose |
|---|---|---|---|
|
|
|
The kind of output. |
|
none; selects the reference pack, the |
set by the template |
The target ( |
|
none |
empty |
A semicolon separated list; MSBuild builds the project once per entry. |
|
|
project name |
Name of the output file and the assembly identity. |
|
|
project name |
Prefix applied to every namespace. See the previous section on names. |
|
|
empty |
The type that contains |
|
|
empty ( |
CPU the assembly may run on. |
|
|
|
Meaningful for .NET Framework executables. |
|
|
|
Alignment of PE sections. |
|
|
|
High-entropy address space layout randomization. |
|
|
empty |
Preferred load address of a library or module. |
|
|
empty ( |
Minimum Windows subsystem version written into the PE header. |
|
none |
|
Read by the SDK to generate |
Language
| Property | Observed vbc option |
Default | Purpose |
|---|---|---|---|
|
|
empty (no option) |
Restricts the compiler to the syntax of an older language version. Values and the tested feature matrix are in LangVersion. |
|
|
|
|
|
|
|
Require declaration of variables. A file-level |
|
|
|
Allow local type inference. |
|
|
|
|
|
|
empty (checked) |
Removes the overflow checks on |
|
defines |
empty ( |
Chooses which |
|
|
computed |
Which Visual Basic runtime the program uses (see MyType and the Visual Basic runtime). |
|
|
|
Take the runtime from the |
|
|
empty |
Do not reference the Visual Basic runtime at all (advanced; the program can then not use the |
|
|
empty |
Your conditional-compilation symbols. See DefineConstants and the implicit symbols. |
|
|
per configuration |
See the Configuration table above. |
|
|
eight namespaces (nine on .NET Framework) |
Namespaces imported into every file. See Global imports. |
|
removes the default |
equals |
Stop the SDK adding its |
|
|
empty |
Experimental compiler feature flags, passed unchanged as |
Diagnostics and documentation
| Property | Observed vbc option |
Default | Purpose |
|---|---|---|---|
|
|
|
Writes the XML documentation file (see XML Documentation, Analyzers and Code Style). |
|
|
empty |
Choose the file name; setting it enables documentation generation by itself, and the path is relative to |
|
|
|
Warnings that are not reported. The |
|
|
|
|
|
|
|
Every warning becomes an error. |
|
|
|
Named warnings become errors. The SDK already contains two; append with |
|
|
empty |
Keeps named warnings as warnings even when |
|
|
empty |
A legacy |
|
|
empty |
Writes all compiler and analyzer diagnostics in SARIF format. |
|
|
|
Prints analyzer timing. |
|
|
empty (analyzers run) |
Do not run analyzers in this build. |
|
change the |
|
Select the .NET analyzer rule set and whether IDE code-style rules also run in the build. |
|
|
|
What the compiler does after an internal error. |
|
no option when |
|
Emit compiler messages in UTF-8. |
Code generation, debugging and determinism
| Property | Observed vbc option |
Default | Purpose |
|---|---|---|---|
|
|
|
Enable compiler optimizations. |
|
|
|
Emit debugging information at all. |
|
|
|
Kind of PDB. |
|
|
|
The same sources always produce the same bytes. |
|
|
empty |
Rewrites source paths embedded in PDBs and attributes, for reproducible builds. |
|
|
empty |
Embeds Source Link data in the PDB; the compiler reports BC37264 if no PDB is emitted. |
|
|
|
Embed source files into the PDB (needs a PDB: BC37265 otherwise). |
|
|
empty (SHA256 is the compiler default) |
Algorithm for the source file checksums stored in the PDB. |
|
|
empty |
Encoding to use for source files that have no byte-order mark. |
|
|
|
Also emit a reference assembly (the metadata-only assembly other projects compile against). |
|
|
empty |
Emit only the reference assembly. |
|
|
empty |
Instrumentation for coverage tools. The runtime helper types must be available (BC35000 otherwise). |
|
|
empty |
Folder where source generators write their output. |
Signing
| Property | Observed vbc option |
Purpose |
|---|---|---|
|
|
|
|
|
Set directly (as the test above did) it is passed straight through. |
|
|
Public signing, the cross-platform way: only the public key is needed. Without a key the compiler reports BC37254 (shown below). |
|
|
Reserve space for the signature. Without a public key: warning BC40060. |
|
|
Key container signing; on .NET (Core) the compiler reports BC36981 "Assembly signing not supported". Use a key file. |
Resources and Win32 metadata
| Property or item | Observed vbc option |
Purpose |
|---|---|---|
|
|
Icon of the executable. |
|
|
Native application manifest (UAC, DPI, supported OS). |
|
|
Do not embed the default manifest. |
|
|
A compiled Win32 resource file. |
|
|
Managed resources ( |
|
|
The file stays outside the assembly. |
Windows desktop projects
| Property | Effect (observed on net10.0-windows, evaluated on Linux with EnableWindowsTargeting) |
|---|---|
|
Adds the framework reference |
|
The same with |
|
Lets a non-Windows machine restore and build a |
|
Gives |
|
The value the VB WinForms template writes: use the |
The winforms and wpf templates and what runs on startup are covered by
Windows Forms Applications and
WPF Applications.
The compiler host and miscellaneous
| Property | Observed vbc option |
Purpose |
|---|---|---|
|
none |
|
|
none |
Run another |
|
|
Compiler message verbosity. |
|
none |
Environment variables to set for the compiler process. |
|
|
An extra response file whose lines are options. |
|
|
Folders searched for references. |
|
|
Do not reference the standard libraries automatically. |
|
|
Do not search the compiler’s own SDK folder for the standard library. The task can pass |
|
|
.NET Compact Framework (obsolete). |
|
|
Language of compiler messages. |
|
|
Reports all |
|
|
Only valid for |
|
|
Passed by the task, but this compiler ignores it and prints warning BC2007 (unrecognized option). |
|
none |
Used to print the command line without compiling (see above). |
|
none |
Internals used by the IDE and the build server. |
Items that feed vbc
| Item | Observed vbc option |
Purpose |
|---|---|---|
|
the source file names |
Source files (default glob |
|
|
Everything resolved to an assembly path (167 for a plain |
|
|
Windows only (see COM Interop and Office Automation). |
|
|
Global imports, one namespace per item. |
|
|
Roslyn analyzers and source generators from packages or the SDK. |
|
|
Non-code files visible to analyzers and generators. |
|
|
The |
|
|
Netmodules to combine into the assembly. |
|
|
See above. |
Properties that do nothing in Visual Basic
C# project files and templates use several properties that look portable and are not. For each, the table says what was tested.
| Property or item | Result in a VB project |
|---|---|
|
No effect. |
|
No effect. With |
|
Rejected by the build: |
|
No effect: neither changed the |
|
Not a compiler setting: it is written to |
What the properties do at run time
The next project reads its own build settings back, so one program shows five properties at once: RootNamespace, Version,
OptionCompare, RemoveIntegerChecks and the conditional-compilation symbols. The project file sets OptionStrict and
TreatWarningsAsErrors, and DefineConstants with two symbols of its own. Its root namespace is Shelf.Build. The code has no Imports for
System, System.Collections.Generic or System.Linq: they come from the SDK’s implicit Import items.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>Shelf.Build</RootNamespace>
<OptionStrict>On</OptionStrict>
<Version>1.2.3-beta.1</Version>
<DefineConstants>$(DefineConstants),FEATURE_LOANS=True,EDITION="Community"</DefineConstants>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NuGetAudit>false</NuGetAudit>
</PropertyGroup>
</Project>
Option Strict On
Imports System.Reflection
Namespace Reports
Public Module Program
Public Sub Main()
' No Imports statement names System, System.Collections.Generic or System.Linq:
' they come from the SDK's implicit Import items.
Dim titles As New List(Of String) From {"Dune", "Emma"}
Console.WriteLine("Titles: " & String.Join(", ", titles.OrderBy(Function(t) t)))
Console.WriteLine("Type name: " & GetType(Program).FullName)
Console.WriteLine("Assembly: " & Assembly.GetExecutingAssembly().GetName().Name)
Dim info As String = Assembly.GetExecutingAssembly() _
.GetCustomAttribute(Of AssemblyInformationalVersionAttribute)().InformationalVersion
Console.WriteLine("Version: " & info.Split("+"c)(0))
Console.WriteLine("Option Compare: ""a"" = ""A"" is " & ("a" = "A").ToString())
Dim big As Integer = Integer.MaxValue
Try
big += 1
Console.WriteLine("Integer overflow check: removed (wrapped to " & big.ToString() & ")")
Catch ex As OverflowException
Console.WriteLine("Integer overflow check: active (" & ex.GetType().Name & ")")
End Try
Dim symbols As New List(Of String)
#If DEBUG Then
symbols.Add("DEBUG")
#End If
#If TRACE Then
symbols.Add("TRACE")
#End If
#If NET10_0_OR_GREATER Then
symbols.Add("NET10_0_OR_GREATER")
#End If
#If FEATURE_LOANS Then
symbols.Add("FEATURE_LOANS")
#End If
#If CONFIG = "Release" Then
symbols.Add("CONFIG=Release")
#ElseIf CONFIG = "Debug" Then
symbols.Add("CONFIG=Debug")
#End If
#If EDITION = "Community" Then
symbols.Add("EDITION=Community")
#End If
Console.WriteLine("Symbols: " & String.Join(" ", symbols))
End Sub
End Module
End Namespace
Five builds of the same source, each with --no-incremental into its own output folder (dotnet build -o out/<label>), then dotnet out/<label>/Props.dll.
Each run changes one thing on the command line. A command-line -p: property wins over the project file:
== debug: dotnet build
Titles: Dune, Emma
Type name: Shelf.Build.Reports.Program
Assembly: Props
Version: 1.2.3-beta.1
Option Compare: "a" = "A" is False
Integer overflow check: active (OverflowException)
Symbols: DEBUG TRACE NET10_0_OR_GREATER FEATURE_LOANS CONFIG=Debug EDITION=Community
== release: dotnet build -c Release
Titles: Dune, Emma
Type name: Shelf.Build.Reports.Program
Assembly: Props
Version: 1.2.3-beta.1
Option Compare: "a" = "A" is False
Integer overflow check: active (OverflowException)
Symbols: TRACE NET10_0_OR_GREATER FEATURE_LOANS CONFIG=Release EDITION=Community
== nocheck: dotnet build -p:RemoveIntegerChecks=true
Titles: Dune, Emma
Type name: Shelf.Build.Reports.Program
Assembly: Props
Version: 1.2.3-beta.1
Option Compare: "a" = "A" is False
Integer overflow check: removed (wrapped to -2147483648)
Symbols: DEBUG TRACE NET10_0_OR_GREATER FEATURE_LOANS CONFIG=Debug EDITION=Community
== text: dotnet build -p:OptionCompare=Text
Titles: Dune, Emma
Type name: Shelf.Build.Reports.Program
Assembly: Props
Version: 1.2.3-beta.1
Option Compare: "a" = "A" is True
Integer overflow check: active (OverflowException)
Symbols: DEBUG TRACE NET10_0_OR_GREATER FEATURE_LOANS CONFIG=Debug EDITION=Community
== ns: dotnet build -p:RootNamespace=Acme
Titles: Dune, Emma
Type name: Acme.Reports.Program
Assembly: Props
Version: 1.2.3-beta.1
Option Compare: "a" = "A" is False
Integer overflow check: active (OverflowException)
Symbols: DEBUG TRACE NET10_0_OR_GREATER FEATURE_LOANS CONFIG=Debug EDITION=Community
Read the outputs against the properties. Namespace Reports in the code became Shelf.Build.Reports, and Acme.Reports when the root
namespace changed: the Namespace statement never contains the root namespace. The assembly name is Props, from the file
name, whatever the root namespace says. The informational version is the Version value with the + and commit hash that the SDK appends
when source control information is present; the code strips that suffix before printing. Release removed DEBUG and
TRACE stayed. RemoveIntegerChecks turned Integer.MaxValue + 1 from an OverflowException into a silent wrap to
-2147483648: a program whose output depends on a project property needs the property in its documentation. OptionCompare
Text made "a" = "A" true for the whole project, which a file-level Option Compare Binary would override again. Built and run: 1 project, 5 builds.
Source: -removeintchecks, -rootnamespace, -optioncompare
Project settings that break or change the build
Each of the following projects is the default Exe project for net10.0 with root namespace E plus the one line shown. The lines
printed are the diagnostics of dotnet build (the Build succeeded summary is omitted). Several are errors that only appear because
a project property was set; they are the messages you will meet when you change a setting in an existing solution.
OptionStrict in the project file
<OptionStrict>On</OptionStrict>
Public Module Program
Public Sub Main()
Dim price As Decimal = 12.5D
Dim whole As Integer = price
Dim anything As Object = whole
Console.WriteLine(anything.Length)
End Sub
End Module
Program.vb(4,32): error BC30512: Option Strict On disallows implicit conversions from 'Decimal' to 'Integer'.
Program.vb(6,27): error BC30574: Option Strict On disallows late binding.
The same two statements build with 0 warnings and 0 errors under the SDK default (OptionStrict Off, which vbc receives as
/optionstrict:custom), and the late-bound anything.Length is only found at run time: run in that default project, the narrowing silently rounded 3.5 to 4 and the late-bound Length call on an Integer threw MissingMemberException. This is the whole case for setting OptionStrict in Directory.Build.props, as the Shelf solution does. The error list
of Option Strict On is on Option Statements.
OptionExplicit
Public Module Program
Public Sub Main()
total = 3
Console.WriteLine(total)
End Sub
End Module
Program.vb(3,9): error BC30451: 'total' is not declared. It may be inaccessible due to its protection level.
Program.vb(4,27): error BC30451: 'total' is not declared. It may be inaccessible due to its protection level.
With <OptionExplicit>Off</OptionExplicit> the same file builds with 0 warnings and 0 errors, and total becomes an implicitly declared Object.
Nothing in a modern project has a reason to switch it off. A file can still say Option Explicit Off, which wins over the project.
Implicit imports switched off
DisableImplicitNamespaceImports set to true removes the SDK’s eight Import items (all of Microsoft.VisualBasic,
System, System.Collections, System.Collections.Generic, System.Diagnostics, System.Linq, System.Xml.Linq,
System.Threading.Tasks):
Public Module Program
Public Sub Main()
Dim names As New List(Of String)
Console.WriteLine(names.Count)
End Sub
End Module
Program.vb(3,26): error BC30002: Type 'List' is not defined.
Program.vb(4,9): error BC30451: 'Console' is not declared. It may be inaccessible due to its protection level.
Every file now needs its own Imports System and Imports System.Collections.Generic. This is the setting to use when you want a
project where nothing is imported without being written; the price is that Microsoft.VisualBasic is no longer imported either: Left("abc", 2) then fails with error BC30451: 'Left' is not declared. It may be inaccessible due to its protection level. until the file says Imports Microsoft.VisualBasic. Keywords such as CInt are part of the language and keep working.
LangVersion
<LangVersion>14</LangVersion>
Public Module Program
Public Sub Main()
Dim pair = (Title:="Dune", Year:=1965)
Dim big As Integer = 1_000_000
Console.WriteLine(pair.Title & " " & big.ToString())
End Sub
End Module
Program.vb(3,20): error BC36716: Visual Basic 14.0 does not support tuples.
Program.vb(4,30): error BC36716: Visual Basic 14.0 does not support digit separators.
With <LangVersion>15</LangVersion> the same file built with 0 warnings and 0 errors. With a value that does not exist
(17.14) the compiler stops before reading any source: vbc : error BC2014: the value '17.14' is invalid for option 'langversion'.
Two entry points and StartupObject
Public Module First
Public Sub Main()
Console.WriteLine("First")
End Sub
End Module
Public Module Second
Public Sub Main()
Console.WriteLine("Second")
End Sub
End Module
vbc : error BC30738: 'Sub Main' is declared more than once in 'E': E.First.Main(), E.Second.Main()
With <StartupObject>E.Second</StartupObject> (the root namespace E plus the module name; without the root namespace prefix the name is not found)
the program built and printed Second.
Warnings policy
One file with an unused local variable:
Option Strict On
Public Module Program
Public Sub Main()
Dim unused As Integer
Console.WriteLine("hello")
End Sub
End Module
| Project line(s) | Result |
|---|---|
(none) |
|
|
|
|
|
|
|
|
nothing: the warning is suppressed before it can be escalated |
The NoWarn trap
The SDK’s default NoWarn hides BC42016 to BC42022, the warnings of the custom Option Strict mode. Assigning NoWarn without
$(NoWarn) throws that list away and makes those warnings appear in every file of the project:
Public Module Program
Public Sub Main()
Dim count As Integer = 3.5
Console.WriteLine(count)
End Sub
End Module
<NoWarn>BC42024</NoWarn>
Program.vb(3,32): warning BC42016: Implicit conversion from 'Double' to 'Integer'.
With the default NoWarn the same file builds silently (and prints 4: the narrowing is real, only the warning is hidden). Write <NoWarn>$(NoWarn);BC42024</NoWarn> to append. A project that sets
OptionStrict to On is not exposed to this trap, because there is nothing left to warn about.
OptionStrict is a Boolean in MSBuild
The Vbc task declares OptionStrict as a Boolean, so the third state of the language statement (Option Strict Custom does not exist as a
statement either) is not available as a project value:
<OptionStrict>Custom</OptionStrict>
Microsoft.VisualBasic.Core.targets(103,10): error MSB4030: "Custom" is an invalid value for the "OptionStrict" parameter of the "Vbc" task. The "OptionStrict" parameter is of type "System.Boolean".
Off is what already gives the custom mode, as the command lines above show.
DefineConstants separators
DefineConstants is passed to /define, which separates entries by commas. The semicolon that MSBuild uses elsewhere (and that C# projects
use for this very property) breaks the build:
<DefineConstants>ALPHA=1;BETA=True</DefineConstants>
vbc : error BC31030: Conditional compilation constant 'ALPHA=1 ^^ ^^ ;BETA=True' is not valid: End of statement expected.
Appending with $(DefineConstants);ALPHA=1;BETA=True fails the same way (BC31030, "Identifier expected" for the empty first entry). Both of
<DefineConstants>ALPHA=1,BETA=True</DefineConstants>
<DefineConstants>$(DefineConstants),ALPHA=1,BETA=True</DefineConstants>
built, and a program using #If ALPHA = 1, #If BETA and #If DEBUG printed ALPHA is 1, BETA is on, DEBUG is on.
The ordering of the options the task writes (a ,, empty entry for an empty $(DefineConstants)) is harmless. Built and run: 19 small projects in this section, plus 4 variants of the DefineConstants project.
Source: -define, -nowarn, -warnaserror, -main
LangVersion
LangVersion limits the compiler to the syntax of an older Visual Basic version and nothing else: it does not change the
target framework, the library surface or the runtime behaviour. It becomes /langversion:<value>. The compiler itself lists what it accepts:
$ vbc -langversion:?
Supported language versions:
default
9
10
11
12
14
15
15.3
15.5
16
16.9
17.13 (default)
latest
Note that the list ends at 17.13 and has no 17 or 17.0. Each candidate value was passed to the real compiler:
| Value | Result |
|---|---|
|
|
|
accepted ( |
|
accepted (a whole number may be written with |
|
accepted; both mean 17.13 on this SDK; the value is not case sensitive |
|
|
default means the latest major version and latest the latest version including minor versions; they are the same today because the
last one is a minor version of 17. The compiler -help text says the same. VB 17.0 added consumption of
CallerArgumentExpression and 17.13 honours OverloadResolutionPriority and the unmanaged constraint, but neither is a
syntax change, which is why neither has a LangVersion value of its own other than the final 17.13.
To see which syntax each version gates, ten probes (one tiny library each) were compiled with each value. The cell is ok, or the
first error code the compiler reported (BC36716 is "Visual Basic N does not support feature", BC30241 is the named-argument
error):
| Probe | 9 | 10 | 11 | 12 | 14 | 15 | 15.3 | 15.5 | 16 | 16.9 | latest |
|---|---|---|---|---|---|---|---|---|---|---|---|
Auto-implemented property |
BC36716 |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
|
BC36716 |
BC36716 |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
|
BC36716 |
BC36716 |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
String interpolation |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
Null-conditional |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
ok |
ok |
ok |
ok |
ok |
ok |
ok |
Tuple literal |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
ok |
ok |
ok |
ok |
ok |
ok |
Digit separator |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
ok |
ok |
ok |
ok |
ok |
ok |
Tuple name inference |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
ok |
ok |
ok |
ok |
ok |
ok |
Named argument before a positional one |
BC30241 |
BC30241 |
BC30241 |
BC30241 |
BC30241 |
BC30241 |
BC30241 |
ok |
ok |
ok |
ok |
|
BC36716 |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
BC36716 |
ok |
ok |
ok |
ok |
The gates match the history of the language and the Learn what’s-new page: auto-implemented properties arrived with 10, Async and
iterators with 11, interpolation and ?. with 14, tuples and digit separators with 15, non-trailing named arguments and Private Protected with 15.5 (the compiler says
BC30241 for the first before 15.5). A library that must compile for an old consumer’s language version can therefore pin LangVersion and get an error on any newer syntax.
Nothing in the language added syntax after 15.5 apart from places where a comment is now allowed (16.0); the later versions (16.9, 17.0, 17.13) are consumption features: init-only
properties, required members, OverloadResolutionPriority and CallerArgumentExpression need no new syntax. See
History, Versions and Language Strategy for the story.
Where the documentation lags: the Learn -langversion page and the "Select the Visual Basic language version" page list values only up to 16.9, and the -langversion page says the option can be set "only by using
the command line" (its sibling page describes the project property and Directory.Build.props, which is the one that is right: <LangVersion> in the project file produces the option, as the 14 example above
showed, and the compiler accepts 17.13). Built and run: 10 probes by 11 versions, plus the value list.
Global imports and the missing global using
C# 10 introduced global using and ImplicitUsings. Visual Basic had global imports first, and they are an item of the project, not
a statement of the language: <Import Include="System.Text" /> becomes an entry in /imports:. There is no
Global Imports statement; each file still declares its own Imports. The SDK adds eight Import items for you (the eight
namespaces in the table above; System.Data is the ninth when the target is .NET Framework). The VB templates add more:
| Template | Import items written into the .vbproj |
|---|---|
|
none (the SDK ones apply) |
|
|
|
|
The Import item in a project must not be confused with MSBuild’s own <Import Project="..." /> element, which pulls in
another project file. They look alike and are unrelated. Two further behaviours follow from the tests above:
-
Setting
UseWindowsFormsorUseWPFdoes not addImportitems; the template does. If you create the project by hand, add them yourself. -
The VB
mstesttemplate still writes a C#-style<Using Include="..." />item that does nothing in VB (see the table of properties that do nothing). The tests use an ordinaryImportsstatement in the file.
An alias is possible in a file (Imports Sb = System.Text.StringBuilder) but not as a project-wide import. Because the compiler imports
nothing implicitly beyond this list, a type of another namespace (StringBuilder, Regex, HttpClient) always needs a file-level Imports or an Import item,
exactly as in the failing ImplicitUsings example above.
DefineConstants and the implicit symbols
The symbols in /define: are the union of what you put in DefineConstants and what the SDK adds. Here are all the implicit ones observed, with
the project that produced them:
| Symbol | Value or meaning | Source |
|---|---|---|
|
String: |
|
|
|
|
|
|
|
|
String: |
|
|
String: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Number: |
Defined by the compiler, not by MSBuild; it does not appear on the command line |
A program that tests the compiler-provided and implicit symbols and ran in a default net10.0 console project printed:
VBC_VER is 17.13 or later
_MyType is Empty
PLATFORM is AnyCPU
NET10_0
not NETFRAMEWORK
(the code is #If VBC_VER >= 17.13 Then, #If _MyType = "Empty" Then, #If PLATFORM = "AnyCPU" Then, #If NET10_0 Then, #If NETFRAMEWORK Then
with #Else.) Comparing a string symbol with a string literal works in #If. The
#If directive, #Const and the Conditional attribute are on
Conditional Compilation and Directives.
The three gotchas of the property itself:
-
The separator is a comma (shown above); a semicolon fails with BC31030.
-
A string symbol is written with plain double quotes in the element (
EDITION="Community", as in the demonstration above); the generated command line escapes them. -
DefineDebugfalsedoes not removeDEBUG(see the Configuration table).
MyType and the Visual Basic runtime
Two groups of settings decide what the Microsoft.VisualBasic library looks like to the program. MyType selects the My classes the compiler
generates; VBRuntime selects which Microsoft.VisualBasic is linked. The SDK’s own comment in Microsoft.NET.Sdk.VisualBasic.targets states the defaults, and the captured command
lines agree with it:
| Target | /vbruntime option observed |
Why |
|---|---|---|
|
|
|
|
|
The Windows Desktop reference pack contains the runtime with the Windows Forms application services. |
|
|
The .NET Framework’s own |
|
|
|
For a library that targets netstandard2.0 the embedded runtime means the library carries its own copy of the Visual Basic runtime functions it uses, and some runtime functions are
not available to it (the effects are on
Libraries, Packaging and Deployment).
The properties are VBRuntime (a path, Embed or Default), UseReferencedVBRuntime and NoVBRuntimeReference; MyType is a plain
string whose values include Console, Windows, WindowsForms, WindowsFormsWithCustomSubMain, Web, Empty and Custom. On .NET 10, Console fails with
BC30002 ("Type … is not defined") and Web does not build at all; Empty is the default and
the right choice for console programs and libraries. WindowsFormsWithCustomSubMain is for Windows Forms projects that write their own Sub Main, as
the template does. The My namespace and what each value generates are on The My Namespace.
Multi-targeting
A library that must serve .NET Framework and .NET consumers lists several target frameworks in TargetFrameworks and uses the
framework symbols in #If. The captured command lines for one project differing only in -p:TargetFramework:
| Option | net10.0 |
net8.0 |
net481 |
netstandard2.0 |
|---|---|---|---|---|
|
eight namespaces |
eight namespaces |
nine (adds |
eight namespaces |
|
SDK list plus |
same |
SDK list only (the SDK adds |
SDK list only |
|
reference pack file |
the |
NuGet cache |
|
|
|
|
|
|
|
yes |
yes |
no |
no |
|
no |
no |
|
no |
|
|
same |
|
|
A complete example is a library Lib that targets net10.0;net481, with Platform.Describe returning a different string per target through the symbols, and a net10.0 console program that
references it. The project of the library:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0;net481</TargetFrameworks>
<RootNamespace>Shelf.Lib</RootNamespace>
<OptionStrict>On</OptionStrict>
<NuGetAudit>false</NuGetAudit>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net481'">
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>
Option Strict On
Public NotInheritable Class Platform
Private Sub New()
End Sub
Public Shared Function Describe() As String
#If NETFRAMEWORK Then
Return ".NET Framework build (NET481 defined)"
#ElseIf NET10_0_OR_GREATER Then
Return ".NET 10 build (NET10_0_OR_GREATER defined)"
#Else
Return "other build"
#End If
End Function
End Class
$ dotnet build App
Lib -> Lib/bin/Debug/net10.0/Lib.dll
App -> App/bin/Debug/net10.0/App.dll
$ dotnet run --project App --no-build
.NET 10 build (NET10_0_OR_GREATER defined)
$ dotnet build Lib -f net481
Lib -> Lib/bin/Debug/net481/Lib.dll
A consumer that targets net10.0 makes MSBuild build only the matching target of the library, so bin/Debug held only net10.0 after the first build. dotnet build Lib -f net481
built the other one, on Linux, using the reference assemblies package. A net481 build can be compiled anywhere but run only on Windows. The conditional Reference item is the
usual way to add an assembly that only the .NET Framework target needs. Built and run: 3 projects (the net481 output compiled, not run).
Sharing settings across a solution
Every setting above belongs in the project file if it is specific to the project and in one of four solution-level files if it is shared. MSBuild looks upward from each project directory, and the first file of each kind it
finds is imported automatically: Directory.Build.props before the project body (so project files can override it), Directory.Build.targets after the SDK targets
(so it can override them), Directory.Packages.props for central package versions, and global.json (read by the dotnet host) for the SDK version. The buildprops, buildtargets,
packagesprops, globaljson, editorconfig, nugetconfig and gitignore short names of dotnet new create starters for them.
The experiment: a repository with Directory.Build.props (sets TargetFramework, OptionStrict On, LangVersion, TreatWarningsAsErrors, Deterministic, Version and
ManagePackageVersionsCentrally), Directory.Build.targets (prints the effective values after every build), Directory.Packages.props (one PackageVersion) and
global.json; and two projects, src/Core that adds nothing and src/Tools that sets OptionStrict Off and Version 0.1.0.
<!-- Directory.Build.props -->
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OptionStrict>On</OptionStrict>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Deterministic>true</Deterministic>
<Version>2.0.0</Version>
<NuGetAudit>false</NuGetAudit>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
</Project>
<!-- Directory.Build.targets -->
<Project>
<Target Name="ShowOptions" AfterTargets="Build">
<Message Importance="high" Text="$(MSBuildProjectName): OptionStrict=$(OptionStrict) Version=$(Version) RootNamespace=$(RootNamespace)" />
</Target>
</Project>
<!-- Directory.Packages.props -->
<Project>
<ItemGroup>
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
</Project>
<!-- global.json -->
{ "sdk": { "version": "10.0.100", "rollForward": "latestFeature" } }
<!-- src/Core/Core.vbproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Shelf.Core</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup>
</Project>
<!-- src/Tools/Tools.vbproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Shelf.Tools</RootNamespace>
<OptionStrict>Off</OptionStrict>
<Version>0.1.0</Version>
</PropertyGroup>
</Project>
(The PackageReference in Core.vbproj has no Version: with central package management the version comes from Directory.Packages.props.)
$ dotnet build src/Core
Core: OptionStrict=On Version=2.0.0 RootNamespace=Shelf.Core
$ dotnet build src/Tools
Tools: OptionStrict=Off Version=0.1.0 RootNamespace=Shelf.Tools
$ dotnet msbuild src/Core/Core.vbproj -getProperty:OptionStrict,TreatWarningsAsErrors,Version
OptionStrict: On TreatWarningsAsErrors: true Version: 2.0.0
$ dotnet msbuild src/Tools/Tools.vbproj -getProperty:OptionStrict,TreatWarningsAsErrors,Version
OptionStrict: Off TreatWarningsAsErrors: true Version: 0.1.0
$ dotnet msbuild src/Tools/Tools.vbproj -p:OptionStrict=On -getProperty:OptionStrict
On
$ dotnet --version
10.0.402
$ dotnet list src/Core package
Top-level Package Requested Resolved
> Microsoft.Extensions.Logging.Abstractions 10.0.0 10.0.0
(-getProperty prints JSON; the two JSON objects are shown as one line each.) The project value won over Directory.Build.props
(Tools kept Off and 0.1.0); the command-line -p:OptionStrict=On won over the project; global.json asked for 10.0.100 with latestFeature
and the installed 10.0.402 satisfied it. The central package version 10.0.0 was applied to a PackageReference without a Version. Built and run: 2 projects.
Nested files. Only the nearest Directory.Build.props is imported. A file deeper in the tree that wants the parent’s settings imports it explicitly, which is what a
tests folder normally does. This was run with tests/Directory.Build.props:
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<PropertyGroup>
<IsPackable>false</IsPackable>
<WarningsNotAsErrors>$(WarningsNotAsErrors);BC42024</WarningsNotAsErrors>
</PropertyGroup>
<ItemGroup>
<Import Include="System.Text" />
</ItemGroup>
</Project>
The test project got the parent’s OptionStrict On, TreatWarningsAsErrors true and Company Shelf, plus IsPackable false, WarningsNotAsErrors ;BC42024 and
a ninth Import item System.Text; the project in src/ got only the parent’s values and IsPackable true. Built and run: 2 projects.
The Shelf reference solution of this site sets OptionStrict, TreatWarningsAsErrors and NuGetAudit in one Directory.Build.props, which is what you
should do for any multi-project Visual Basic repository: it makes OptionStrict On a solution policy instead of a per-file chore. The .editorconfig
keys for style and analyzer severity are on XML Documentation, Analyzers and Code Style.
The dotnet command line for Visual Basic
Everything in an IDE is available from the dotnet command, and the command is what continuous integration runs (see
Testing for CI). The default language of dotnet new is C#; a Visual Basic project needs -lang VB (or --language VB).
The templates that have a Visual Basic version on SDK 10.0.402 (dotnet new list --language VB):
Template Name Short Name Language Tags
----------------------------- ------------------- -------- -----------------------
Class Library classlib C#,F#,VB Common/Library
Console App console C#,F#,VB Common/Console
MSTest Test Class mstest-class C#,F#,VB Test/MSTest
MSTest Test Project mstest C#,F#,VB Test/MSTest/Desktop/Web
NUnit Test Item nunit-test C#,F#,VB Test/NUnit
NUnit Test Project nunit C#,F#,VB Test/NUnit/Desktop/Web
Windows Forms App winforms C#,VB Common/WinForms
Windows Forms Class Library winformslib C#,VB Common/WinForms
Windows Forms Control Library winformscontrollib C#,VB Common/WinForms
WPF Application wpf C#,VB Common/WPF
WPF Class Library wpflib C#,VB Common/WPF
WPF Custom Control Library wpfcustomcontrollib C#,VB Common/WPF
WPF User Control Library wpfusercontrollib C#,VB Common/WPF
xUnit Test Project xunit C#,F#,VB Test/xUnit/Desktop/Web
There is no Visual Basic webapi, web, blazor, worker, grpc or MAUI template; the list above is the complete VB set. The configuration-file templates
(sln, slnf, globaljson, editorconfig, nugetconfig, gitignore, gitattributes, tool-manifest, buildprops, buildtargets, packagesprops, webconfig) have no language and apply to any project.
The console template’s own options show how little VB-specific there is: -f/--framework (one choice, net10.0), --langVersion (writes LangVersion), --no-restore and --aot.
A complete workflow
The following session builds the Shelf skeleton used by the other pages: a library, a console program that uses it and an MSTest project. The commands are the real ones; the
Directory.Build.props was written first (it sets OptionStrict On, TreatWarningsAsErrors true and NuGetAudit false, see above). First the scaffolding:
dotnet new sln -n Shelf
dotnet new classlib -lang VB -o Shelf.Core
dotnet new console -lang VB -o Shelf.Cli
dotnet new mstest -lang VB -o Shelf.Tests
dotnet sln add Shelf.Core Shelf.Cli Shelf.Tests
dotnet add Shelf.Cli reference Shelf.Core
dotnet add Shelf.Tests reference Shelf.Core
dotnet add Shelf.Core package Microsoft.Extensions.Logging.Abstractions --version 10.0.0
dotnet sln list
dotnet list Shelf.Cli reference
dotnet list Shelf.Core package
Project `Shelf.Core/Shelf.Core.vbproj` added to the solution.
Project `Shelf.Cli/Shelf.Cli.vbproj` added to the solution.
Project `Shelf.Tests/Shelf.Tests.vbproj` added to the solution.
Reference `..\Shelf.Core\Shelf.Core.vbproj` added to the project.
Reference `..\Shelf.Core\Shelf.Core.vbproj` added to the project.
info : PackageReference for package 'Microsoft.Extensions.Logging.Abstractions' version '10.0.0' added to file '/w/work/Shelf.Core/Shelf.Core.vbproj'.
Project(s)
----------
Shelf.Cli/Shelf.Cli.vbproj
Shelf.Core/Shelf.Core.vbproj
Shelf.Tests/Shelf.Tests.vbproj
Project reference(s)
--------------------
../Shelf.Core/Shelf.Core.vbproj
[net10.0]:
Top-level Package Requested Resolved
> Microsoft.Extensions.Logging.Abstractions 10.0.0 10.0.0
(Some chatty lines of the restore were left out.) dotnet new sln wrote Shelf.slnx, the XML solution format that is the default in SDK 10; its whole content is
three Project Path elements. Existing .sln files keep working and dotnet sln Old.sln migrate converted one:
.slnx file /w/t/Old.slnx generated. Note that the dotnet add reference command put the ProjectReference in a new ItemGroup above the PropertyGroup, which is
harmless: MSBuild does not care about the order of groups in the project body.
The dotnet new mstest project built and ran straight from the template under OptionStrict On and TreatWarningsAsErrors
(Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1), so the C#-style properties it carries are inert. Then the sources of the three projects were replaced:
Option Strict On
Namespace Domain
Public NotInheritable Class Isbn
Public ReadOnly Property Value As String
Public Sub New(value As String)
If Not IsValid(value) Then
Throw New ArgumentException("Not a valid ISBN-13: " & value, NameOf(value))
End If
Me.Value = value
End Sub
Public Shared Function IsValid(value As String) As Boolean
If value Is Nothing OrElse value.Length <> 13 Then
Return False
End If
Dim sum As Integer = 0
For i As Integer = 0 To 12
If Not Char.IsDigit(value(i)) Then
Return False
End If
Dim digit As Integer = Asc(value(i)) - Asc("0"c)
sum += If(i Mod 2 = 0, digit, digit * 3)
Next
Return sum Mod 10 = 0
End Function
End Class
End Namespace
Option Strict On
Imports Shelf.Core.Domain
Public Module Program
Public Function Main(args As String()) As Integer
Dim failures As Integer = 0
For Each candidate As String In args
If Isbn.IsValid(candidate) Then
Console.WriteLine(candidate & " is a valid ISBN-13")
Else
Console.WriteLine(candidate & " is not valid")
failures += 1
End If
Next
Return failures
End Function
End Module
Option Strict On
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports Shelf.Core.Domain
<TestClass>
Public Class IsbnTests
<TestMethod>
Public Sub Valid_isbn_is_accepted()
Assert.IsTrue(Isbn.IsValid("9780306406157"))
End Sub
<TestMethod>
Public Sub Wrong_check_digit_is_rejected()
Assert.IsFalse(Isbn.IsValid("9780306406158"))
End Sub
End Class
(The root namespaces are the project names, Shelf.Core, Shelf.Cli and Shelf.Tests, so Namespace Domain in the library is Shelf.Core.Domain; the console module and the test class
sit in their project’s root namespace and need no Namespace block.) The commands that use them:
dotnet build Shelf.slnx
dotnet run --project Shelf.Cli --no-build -- 9780306406157 123
dotnet test Shelf.Tests
dotnet pack Shelf.Core -c Release -o nupkgs
dotnet publish Shelf.Cli -c Release -o pub
dotnet pub/Shelf.Cli.dll 9780306406157
dotnet format Shelf.slnx --verify-no-changes
dotnet clean Shelf.slnx
dotnet test Shelf.Tests --list-tests
dotnet msbuild Shelf.Cli -pp:expanded.xml
Shelf.Core -> /w/work/Shelf.Core/bin/Debug/net10.0/Shelf.Core.dll
Shelf.Tests -> /w/work/Shelf.Tests/bin/Debug/net10.0/Shelf.Tests.dll
Shelf.Cli -> /w/work/Shelf.Cli/bin/Debug/net10.0/Shelf.Cli.dll
Build succeeded.
0 Warning(s)
0 Error(s)
9780306406157 is a valid ISBN-13
123 is not valid
exit code: 1
Passed! - Failed: 0, Passed: 2, Skipped: 0, Total: 2, Duration: 8 ms - Shelf.Tests.dll (net10.0)
The package Shelf.Core.1.0.0 is missing a readme. Go to https://aka.ms/nuget/authoring-best-practices/readme to learn why package readmes are important.
Successfully created package '/w/work/nupkgs/Shelf.Core.1.0.0.nupkg'.
Shelf.Cli -> /w/work/pub/
Shelf.Cli
Shelf.Cli.deps.json
Shelf.Cli.dll
Shelf.Cli.pdb
Shelf.Cli.runtimeconfig.json
Shelf.Core.dll
Shelf.Core.pdb
9780306406157 is a valid ISBN-13
The following Tests are available:
Valid_isbn_is_accepted
Wrong_check_digit_is_rejected
(exit code: 1 was added by the script after dotnet run; it is the process exit code of the program, the count of invalid ISBNs, which dotnet run passes through. The ls pub listing
is the second block of names: the apphost Shelf.Cli, its .deps.json and .runtimeconfig.json, the two assemblies and their PDBs. dotnet format --verify-no-changes and dotnet clean
printed nothing of note for this solution.) -pp:expanded.xml produced a 1.5 MB file for the console project in which Directory.Build.props (line 94) comes long before the Visual Basic .props (line 1090) and the project body, which is why a project can override what the file sets. Built and run: 3 projects, 2 tests.
What each command does to the MSBuild model
| Command | MSBuild equivalent | Visual Basic notes |
|---|---|---|
|
|
Reads |
|
|
|
|
build, then |
Everything after |
|
build, then the test host |
The VB |
|
|
|
|
|
Deletes the outputs of the build. |
|
(solution file edit) |
Works on |
|
(project file edit) |
Edits the |
|
(Roslyn workspace, not MSBuild targets) |
Applies |
|
the same MSBuild engine |
|
|
(file watcher around |
Hot Reload for VB: the VB targets add the |
Two useful orientation commands: dotnet --list-sdks printed 10.0.402 [/usr/share/dotnet/sdk], and dotnet msbuild -getProperty:MSBuildVersion,NETCoreSdkVersion,VisualBasicTargetsPath,VisualBasicCoreTargetsPath printed
18.9.15, 10.0.402, /usr/share/dotnet/sdk/10.0.402/Microsoft.VisualBasic.CurrentVersion.targets and
.../Sdks/Microsoft.NET.Sdk/targets/../../../Roslyn/Microsoft.VisualBasic.Core.targets: the last two are the files that do the work. Built and run: all of the above.
The vbc option reference
vbc is the Roslyn Visual Basic compiler. In the .NET SDK it is Roslyn/bincore/vbc.dll under the SDK folder
(/usr/share/dotnet/sdk/10.0.402/Roslyn/bincore/vbc.dll in the container), a framework-dependent program started with dotnet;
the vbc file next to it is its apphost. vbc -version printed 5.9.0-1.26505.111: that is the compiler version, not the language version (17.13, see above). Learn’s
"compiler options listed by category" page and the individual option pages describe an older compiler; the table below is the output of vbc -help of
this compiler, in its eight groups, plus five options the compiler accepts but does not list. The Learn page of an option (where one exists) is linked in the references.
In the "Option" column [+|-] means the option may be followed by + (on), - (off) or nothing (on).
Each option below was passed to the SDK 10.0.402 compiler on a small module. All were accepted; the ones that take a file reported a
problem with the (deliberately missing) file instead of "unrecognized option", which proves the option itself was understood. A made-up option
produces warning BC2007 and is ignored: vbc : warning BC2007: unrecognized option '-bogusoption'; ignored. That is also why a
misspelled switch in a response file does not stop a build, which is worth knowing when you read a build log.
Output file
| Option | Short | Purpose | MSBuild counterpart |
|---|---|---|---|
|
Name of the output file. |
|
|
|
|
Console application (the compiler default). |
|
|
|
Windows (GUI) application: no console is attached. |
|
|
|
Class library ( |
|
|
|
A |
|
|
|
Windows Runtime application that runs in an AppContainer. |
|
|
|
Windows Metadata intermediate file. |
|
|
Generate the XML documentation file. |
|
|
|
Generate the XML documentation file to the given path. |
|
|
|
Also write a reference assembly (metadata only) to the given path. |
|
Input files
| Option | Short | Purpose | MSBuild counterpart |
|---|---|---|---|
|
Reference metadata from the given netmodules and make them part of the output assembly. |
|
|
|
|
Embed the interop types of the given assembly instead of referencing it (no PIA needed at run time). |
|
|
Add the files matching the wildcard in the current directory and below. No MSBuild property: the |
|
|
|
|
Reference the given assemblies. |
|
|
|
Run the analyzers (and source generators) in the given assemblies. |
|
|
Non-code files that analyzers and generators may read. |
|
Resources
| Option | Short | Purpose | MSBuild counterpart |
|---|---|---|---|
|
|
Link a file as an external assembly resource; |
|
|
Do not embed the default Win32 manifest. |
|
|
|
|
Embed the file as an assembly resource. |
|
|
Icon ( |
|
|
|
Embed the given manifest. |
|
|
|
Use the given compiled Win32 resource file ( |
|
Code generation
| Option | Short | Purpose | MSBuild counterpart |
|---|---|---|---|
|
Enable optimizations. |
|
|
|
Remove the integer overflow checks (default off). |
|
|
|
Emit debugging information. |
|
|
|
Emit full debugging information (the help text calls it the default). |
|
|
|
Emit full debugging information (the same text as |
|
|
|
Cross-platform PDB format; the SDK default. |
|
|
|
Cross-platform debugging information inside the assembly. |
|
|
|
Produce a deterministic assembly (same input, same bytes). The task also writes |
|
|
|
Produce a reference assembly in place of the main output. |
|
|
|
Produce an assembly instrumented to collect coverage information. |
|
|
|
Source Link information to embed in the PDB (needs a PDB). |
|
Errors and warnings
| Option | Short | Purpose | MSBuild counterpart |
|---|---|---|---|
|
Disable all warnings. |
|
|
|
Disable the listed warnings (the |
|
|
|
Treat all warnings as errors. |
|
|
|
Treat the listed warnings as errors ( |
|
|
|
A rule set file that disables or changes diagnostics. |
|
|
|
Log all compiler and analyzer diagnostics in SARIF format; version |
|
|
|
Report analyzer information such as execution time. |
|
|
|
Skip the execution of diagnostic analyzers. |
|
Language
| Option | Short | Purpose | MSBuild counterpart |
|---|---|---|---|
|
|
Declare global conditional-compilation symbols ( |
|
|
Declare global imports of namespaces from the referenced assemblies. |
|
|
|
Print the allowed language versions. |
none |
|
|
Accept only the syntax of the given version: |
|
|
|
Require explicit declaration of variables. |
|
|
|
Allow local type inference. |
|
|
|
Root namespace of all type declarations. |
|
|
|
Enforce strict language semantics. |
|
|
|
Warn when strict semantics are not respected. |
|
|
|
Binary string comparisons (the default). |
|
|
|
Text-style (case-insensitive, culture-aware) string comparisons. |
|
Miscellaneous
| Option | Short | Purpose | MSBuild counterpart |
|---|---|---|---|
|
|
Display the usage message. |
none |
|
Do not auto-include a |
always set ( |
|
|
Do not display the compiler banner. |
none observed in the captured command lines |
|
|
Quiet output mode. |
|
|
|
Display verbose messages (each referenced assembly was listed). |
|
|
|
Concurrent build. |
none: the |
|
|
Display the compiler version and exit ( |
none |
Advanced
| Option | Short | Purpose | MSBuild counterpart |
|---|---|---|---|
|
Base address (hex) of a library or module. |
|
|
|
Algorithm for the source checksums stored in the PDB: |
|
|
|
Code page for opening source files that have no byte-order mark. |
|
|
|
Delay-sign with only the public part of the key. |
|
|
|
Public-sign with only the public part of the key. |
|
|
|
What to do after an internal compiler error: |
|
|
|
Folder for files generated during compilation (source generators). |
|
|
|
Alignment of the output file sections. |
|
|
|
Enable high-entropy ASLR. |
|
|
|
Strong-name key container. |
|
|
|
Strong-name key file. |
|
|
|
Directories (semicolon delimited) searched for metadata references. |
|
|
|
|
The class or module that contains |
|
|
Name of the assembly a module will be part of (only with |
|
|
|
Target the .NET Compact Framework. |
|
|
|
Do not reference the standard libraries automatically. |
|
|
|
Map source paths in compiler output. |
|
|
|
|
|
|
|
Preferred language of compiler messages. |
|
|
|
Do not search the default SDK path for standard library assemblies. |
|
|
|
Report all |
|
|
|
Path searched for standard library assemblies. |
|
|
|
Subsystem version of the output PE, |
|
|
|
Emit compiler output in UTF-8. |
|
|
|
Read command-line settings from a response file. |
|
|
|
|
|
|
|
Compile against the alternate runtime in the given file. |
|
Accepted but not listed by -help
| Option | Short | Purpose | MSBuild counterpart |
|---|---|---|---|
|
Experimental compiler feature flags. |
|
|
|
Embed all or the named source files in the PDB. |
|
|
|
An |
|
|
|
Do not reference the Visual Basic runtime. |
|
|
|
Passed by the task; this compiler prints warning BC2007 (unrecognized option; ignored). |
|
Observations from running the options
The results that were not simply "accepted":
| Option as passed | What the compiler said |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
accepted, with |
|
|
|
|
|
|
|
prints the supported values and exits 0 (see above); |
|
lists every reference as |
Two further results matter for scripts. The same options with a trailing + or - (-deterministic+, -deterministic-, -optimize-, -highentropyva-, -utf8output-, -delaysign-, -publicsign-,
-skipanalyzers-, -parallel-, -doc-, -removeintchecks-, -optionexplicit+, -debug-) were all accepted, including forms the help text shows without a suffix. And
-define:A=1,B was accepted (a symbol without a value is True), while ; as a separator belongs to the MSBuild property, not to the command-line option, as the DefineConstants experiment showed.
Running vbc by hand
Nothing prevents compiling without MSBuild; you need the reference assemblies and the right standard-library options because vbc on .NET does not find them itself.
The project below is one source file, a response file with the options and a response file listing the 167 reference assemblies (made with
ls $REF/*.dll | sed 's|^|-reference:|' > refs.rsp, where REF is the microsoft.netcore.app.ref folder .../ref/net10.0 of the reference pack):
Option Strict On
Public Module Hello
Public Sub Main()
Console.WriteLine("Hello from vbc " & Left("compiled by hand", 11))
End Sub
End Module
# build.rsp
-nologo
-noconfig
-nostdlib
-nosdkpath
-target:exe
-out:Hello.dll
-optionstrict+
-rootnamespace:Hand
-define:_MyType="Empty"
-imports:System,Microsoft.VisualBasic
-vbruntime:<ref>/Microsoft.VisualBasic.dll
@refs.rsp
Hello.vb
dotnet /usr/share/dotnet/sdk/10.0.402/Roslyn/bincore/vbc.dll @build.rsp
vbc : warning BC2025: ignoring /noconfig option because it was specified in a response file
vbc exit code: 0
The Hello.runtimeconfig.json that dotnet needs to run a framework-dependent program (the SDK writes it for you in a project build) is three lines of JSON that name the framework Microsoft.NETCore.App version 10.0.0; with it, dotnet Hello.dll printed:
Hello from vbc compiled by
Notes from the run: -noconfig must be on the real command line (BC2025 otherwise, a warning only); without -define:_MyType="Empty" the compiler generates its default My classes and reports BC30002 for the Windows-only types they use
(Type 'Global.Microsoft.VisualBasic.ApplicationServices.ApplicationBase' is not defined.), and the line is how the SDK stops that; and a response file is simply the options, one per line, expanded in place, which is how MSBuild itself invokes the compiler. Built and run: 1 program.
The Vbc task parameters
The complete interface between MSBuild and the compiler is the list of parameters that Microsoft.VisualBasic.Core.targets gives to the Vbc task in the CoreCompile target: 90 of them
in this SDK. For each, the table gives the property or item that feeds it, the compiler option it becomes and whether Learn’s Vbc task page documents it. (Learn lists 53 names,
and 50 of them match a parameter of the 90.) A property that is not in the "Fed by" column cannot influence the compiler through the SDK targets.
Vbc task parameter |
Fed by | Becomes | On Learn |
|---|---|---|---|
|
|
|
yes |
|
|
|
yes |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
yes |
|
|
|
no |
|
|
|
no |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
no |
|
|
|
yes |
|
|
|
yes |
|
|
|
no |
|
|
|
no |
|
|
/debug+ or /debug- |
yes |
|
|
(environment of the compiler process) |
no |
|
|
|
no |
|
|
|
yes |
|
|
|
no |
|
|
|
yes |
|
|
/doc+ (the SDK targets feed |
yes |
|
|
|
no |
|
|
|
no |
|
|
|
yes |
|
|
|
no |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
always |
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
no |
|
|
|
yes |
|
|
|
yes |
|
|
|
no |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
no |
|
|
(PDB path; used for winmdobj) |
no |
|
|
|
yes |
|
|
|
no |
|
|
|
no |
|
|
(diagnostics only) |
no |
|
|
(return the arguments as an item) |
no |
|
|
|
yes |
|
|
|
no |
|
|
|
yes |
|
|
|
no |
|
|
|
no |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
/runtimemetadataversion (ignored by vbc) |
no |
|
|
|
yes |
|
|
(build server pipe name) |
no |
|
|
|
no |
|
|
(do not run the compiler) |
no |
|
|
(source file names) |
yes |
|
|
|
no |
|
|
|
yes |
|
|
|
yes |
|
|
(used for task logic and symbols) |
no |
|
|
(which executable to run) |
no |
|
|
(folder of the executable) |
yes |
|
|
|
yes |
|
|
(legacy in-IDE compiler) |
yes |
|
|
(build server or not) |
no |
|
|
|
yes |
|
|
(path of the runtime) |
no |
|
|
/quiet or /verbose |
yes |
|
|
(IDE session id) |
no |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
The 40 parameters Learn does not document include most of the modern ones: analyzers and analyzer configs, ErrorLog, Features, PathMap, PublicSign,
SourceLink, EmbedAllSources, RefOnly, SkipAnalyzers, Instrument, ChecksumAlgorithm and HighEntropyVA. Reading the targets file is the reliable way to find
them. Source: Vbc task, MSBuild tasks
Visual Studio project properties
Visual Studio is a front end for the same project file: every setting it shows is an MSBuild property, and saving the page edits the .vbproj. For an SDK-style project, double-clicking
the project in Solution Explorer (or the Edit Project File command) opens the XML directly, which is the quickest way to see what a page really stores. No IDE was available for the
tests behind this page, so the table below takes the page and setting names from the Learn "Project Designer" pages for Visual Basic and the property names from the
verified sections above; check the page layout against your Visual Studio version (the two Learn pages state that they apply to Visual Studio 2017, and the project designer has been redone
since).
| Page and setting | MSBuild property | vbc option |
Notes |
|---|---|---|---|
Application: Assembly name |
|
|
Changing it also changes the output name. |
Application: Root namespace |
|
|
Empty means no prefix. |
Application: Target framework |
|
(reference pack, symbols) |
Multi-targeting is edited in the XML. |
Application: Application type |
|
|
Console Application, Windows Application, Class Library. |
Application: Startup object |
|
|
|
Application: Icon |
|
|
|
Application: Enable application framework |
|
( |
|
Application: Assembly Information |
|
(AssemblyInfo attributes) |
Written to the project file in SDK-style projects. |
Compile: Option explicit / strict / compare / infer |
|
|
The page also offers per-warning settings (implicit conversion, late binding, implicit type) that map to custom Option Strict. |
Compile: Disable all warnings / Treat all warnings as errors |
|
|
The per-warning table maps to |
Compile: Generate XML documentation file |
|
|
|
Compile: Target CPU, Prefer 32-bit |
|
|
|
Compile: Advanced compile options |
|
|
The names in the dialog (for example "Custom constants") are not the property names. |
Compile: Build output path |
|
(not a compiler option) |
|
References, Resources, Settings |
|
|
Settings files generate |
New projects start from the defaults of Tools > Options > Projects and Solutions > VB Defaults, which is why a project created in Visual Studio can have
OptionStrict On while one created with dotnet new has Off: the IDE writes the option into the new project, and the SDK default is only the fallback. The debugger,
Edit and Continue and Hot Reload are on
Debugging and Diagnostics.
Legacy (non-SDK) project files
Until .NET Core, every Visual Basic project used a verbose format that listed everything by hand. You meet it in .NET Framework solutions, in older repositories and in the books that predate the SDK. The sample below is a typical project of that kind for a console application (legacy format, shown for recognition; not built here).
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{00000000-0000-0000-0000-000000000000}</ProjectGuid>
<OutputType>Exe</OutputType>
<StartupObject>Shelf.Cli.Program</StartupObject>
<RootNamespace>Shelf.Cli</RootNamespace>
<AssemblyName>Shelf.Cli</AssemblyName>
<FileAlignment>512</FileAlignment>
<MyType>Console</MyType>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<OptionExplicit>On</OptionExplicit>
<OptionCompare>Binary</OptionCompare>
<OptionStrict>Off</OptionStrict>
<OptionInfer>On</OptionInfer>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<OutputPath>bin\Debug\</OutputPath>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Import Include="Microsoft.VisualBasic" />
<Import Include="System" />
<Import Include="System.Collections.Generic" />
<Import Include="System.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.vb" />
<Compile Include="My Project\AssemblyInfo.vb" />
<Compile Include="My Project\Application.Designer.vb">
<AutoGen>True</AutoGen>
<DependentUpon>Application.myapp</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="My Project\Application.myapp">
<Generator>MyApplicationCodeGenerator</Generator>
<LastGenOutput>Application.Designer.vb</LastGenOutput>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
</Project>
What the SDK-style project made redundant:
| Legacy construct | SDK-style replacement |
|---|---|
|
|
|
Not needed (the solution file still carries project ids) |
|
|
One |
The SDK defaults (the same |
A |
The |
|
Implicit framework references and |
|
The SDK’s implicit imports (keep only your own) |
|
Generated from |
|
Still used by Windows Forms projects that use the application framework; Console and library projects need only |
|
Unchanged in structure; they stay |
Almost every property name is the same in both formats (OutputType, RootNamespace, OptionStrict, MyType, DefineConstants), which is why a conversion is mostly deletion. A reasonable order is:
create an SDK-style project that targets the same .NET Framework version (net48, net481: it compiles anywhere with the reference assemblies package, as the multi-targeting example showed), delete the
lists the SDK now provides, replace packages.config with PackageReference, fix the AssemblyInfo duplicates, get the build green on the old framework, and only then change the target to net10.0 and deal with the APIs
that moved. Microsoft’s Upgrade Assistant and the app-modernization tooling can automate part of this; their current capabilities were not tested for this page. Web Forms and ASP.NET MVC projects
are .NET Framework projects, normally in the legacy format, as described in
Web, HTTP and Services. The strategy side is on
Migrating Legacy Visual Basic.
Pitfalls checklist
| What you see | Why | Fix |
|---|---|---|
BC42016 and other Option Strict warnings suddenly appear everywhere |
|
|
BC31030 "Conditional compilation constant … is not valid" |
Semicolon (or an empty first entry) in |
Comma separated: |
|
|
Use another configuration name or |
MSB4030 for |
The task parameter is a Boolean |
|
NETSDK1022 duplicate |
A legacy project still lists the files |
Delete the items, or |
BC30738 "'Sub Main' is declared more than once" |
Two entry points |
|
Namespaces come out as |
|
Write the namespace without the root namespace |
BC2014 for |
VB has no such values |
|
|
C# features |
|
BC30002 for |
Missing import (or |
|
BC30451 for |
|
|
BC30663 duplicate attribute |
Hand-written |
Delete one, or |
BC2007 "unrecognized option" |
An option the compiler does not know ( |
Remove it; warnings of this kind do not fail the build |
|
An incremental build kept the old output |
|
A project value is ignored |
A |
Remove the command-line property, or set it conditionally |
A |
It can be compiled anywhere, run only on Windows |
Build it in CI on any OS, test it on Windows |
|
On .NET 10 the |
Leave |
Differences from C# and from .NET Framework
| Topic | Visual Basic | C# |
|---|---|---|
Project templates |
Fewer: console, classlib, test, WinForms, WPF; none for web, worker or MAUI |
All of them |
Namespace prefix |
|
No such property; the |
Language strictness |
|
Equivalent behaviour is fixed (or |
Overflow |
|
|
Symbols |
|
Semicolon separated; |
Global imports |
|
|
Language versions |
|
Numbers up to the current one, |
Runtime library |
|
None |
Compared with .NET Framework. An SDK-style project can target net48 or net481 and keep MyType Windows / WindowsForms, System.Data in the default imports, /subsystemversion:6.00
and the Framework’s own Microsoft.VisualBasic.dll; a project on net10.0 gets _MyType="Empty" until the template sets another value, eight default imports and the reference pack’s runtime.
Where the books and the older documentation are outdated
The books that predate the SDK-style format describe Visual Basic through a .NET Framework console template and a Visual Studio of that time; on current tooling the same
programs are created with dotnet new console -lang VB or File > New in Visual Studio 2026 and build against .NET 10 (see
Getting Started). Beyond the books, a few official pages lag behind the product, and a reader following them gets a wrong
picture:
-
The Learn pages -langversion and Select the Visual Basic language version list
LangVersionvalues only up to16.9(the compiler also accepts17.13), and the first says the option can be set only on the command line, which is not true for a project. -
The Learn Vbc task page lists 53 parameters, 50 of them still current; the targets pass 90.
-
The Learn MSBuild properties for Microsoft.NET.Sdk page does not mention the Visual Basic-specific properties (
OptionStrict,OptionExplicit,OptionInfer,OptionCompare,RemoveIntegerChecks,MyType,VBRuntime,RootNamespace,StartupObjectandDefineConstantseach had zero matches in the page), so this page’s tables come from the SDK files themselves. -
The Learn compiler-option pages do not cover everything
vbc -helpprints; the options-analyzer,-additionalfile,-checksumalgorithm,-errorlog,-ruleset,-reportanalyzer,-skipanalyzers,-instrument,-sourcelink,-publicsign,-generatedfilesout,-parallel,-preferreduilang,-nosdkpath,-reportivtsand-versionhave no page named after them. -
The Learn Compile Page and Application Page of the Project Designer say they apply to Visual Studio 2017.
-
The default
OptionStrictisOffin the SDK although Learn and every style guide recommendOn; there is no project template that changes it.
Summary
-
A Visual Basic project is an SDK-style
.vbproj; the SDK adds the sources (glob), the references, eight global imports, the option defaults and aNoWarnlist. -
Properties become
vbcoptions through theVbctask (90 parameters).dotnet msbuild -getTargetResult:CoreCompilewithProvideCommandLineArgsshows the result without compiling. -
OptionStrictOffis passed as/optionstrict:customand relies on the defaultNoWarn; write$(NoWarn);..., or better setOptionStrictOninDirectory.Build.props. -
DefineConstantsis comma separated;DefineDebugfalsedoes not removeDEBUG;Importitems replaceImplicitUsings;NullableandUsingare inert. -
LangVersionaccepts9to16.9,17.13,defaultandlatest; the syntax gates are those of the language history, and no syntax has been added since 15.5. -
MyType,VBRuntimeandUseReferencedVBRuntimechoose theMyclasses and the runtime; they differ fornet10.0,net10.0-windows,net481andnetstandard2.0. -
Share settings with
Directory.Build.props,Directory.Packages.propsandglobal.json; a command-line-p:property wins over all of them. -
dotnet new -lang VB,sln,add,build,run,test,pack,publishandformatare the day-to-day commands;dotnet msbuild -ppshows the expanded project.
References
-
-optionstrict, -optionexplicit, -optioninfer, -optioncompare
-
-rootnamespace, -target, -main, -define, -imports, -vbruntime
-
-nowarn, -warnaserror, -doc, -debug, -optimize, -deterministic, -pathmap
-
-removeintchecks, -platform, -keyfile, -reference, -refout, -refonly
-
Response files, Sample compilation command lines, How to invoke the command-line compiler
-
dotnet new templates, dotnet new, dotnet build, dotnet run, dotnet test, dotnet pack, dotnet publish, dotnet clean, dotnet restore, dotnet watch, dotnet format, dotnet sln, dotnet reference add, dotnet package add, dotnet msbuild, global.json
-
Customize your build, MSBuild command-line reference, Reserved and well-known properties, Common MSBuild project properties, MSBuild items, Vbc task, MSBuild conditions
-
Central Package Management, PackageReference in project files, nuget.config
-
Application page, Project Designer (Visual Basic), Compile page, Project Designer (Visual Basic), References page, Project Designer (Visual Basic), Project properties reference