Nothing and Nullable Value Types
|
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. |
Visual Basic has a single keyword, Nothing, for two ideas that most languages keep apart: "no object" and "the
default value of this type". Assigning Nothing to a reference variable clears it, as null does in C#, but assigning it to
a value-typed variable resets that variable to its zero value, so Dim count As Integer = Nothing is legal and gives 0.
This page covers that rule and its consequences, the comparison operators that have to be used with it, the nullable
value types (Nullable(Of T), written Integer?) with their "lifted" operators and three-valued logic, the If operator
as the language’s null-coalescing operator, the null-conditional operators ?. and ?(), and the null-related features
VB lacks. It was written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and checked on 2026-10-19; every program was
built with Option Strict On and TreatWarningsAsErrors, and run for real.
The running scenario is the shelf library catalogue of this section: an Author may have an unknown birth date
(Date?), a Book may be looked up and not found, and a loan may not have a return date yet. The page
Data Types and Literals has the full type list,
Operators and Expressions has the operator precedence
and Is/IsNot, and Type Conversions explains why
CType(Nothing, Integer) is 0. For the C# equivalent of this page see
Nullable Types and Null Safety.
Nothing is the default value of the type
The Visual Basic Language Reference defines Nothing as the default value of a data type: the compiler converts it to
whatever type the context asks for. For a reference type, the default value is "no object". For a value type, it is the
type’s zero-initialised state: 0 for the numeric types, False for Boolean, Date.MinValue (midnight on 1 January 0001)
for Date, and a structure with every member set to its own default. Nothing is also the only way to write the default
value of a generic type parameter, which C# spells default(T).
Option Strict On
Public Structure Point2D
Public X As Integer
Public Y As Integer
End Structure
Public Class Author
Public Property Name As String
End Class
Module Ex01
Function DefaultOf(Of T)() As T
Return Nothing
End Function
Sub Main()
Dim count As Integer = Nothing
Dim flag As Boolean = Nothing
Dim whenBorn As Date = Nothing
Dim title As String = Nothing
Dim writer As Author = Nothing
Dim spot As Point2D = Nothing
Console.WriteLine($"Integer : {count}")
Console.WriteLine($"Boolean : {flag}")
Console.WriteLine($"Date : {whenBorn:yyyy-MM-dd} (Date.MinValue = {whenBorn = Date.MinValue})")
Console.WriteLine($"String : title Is Nothing = {title Is Nothing}")
Console.WriteLine($"Author : writer Is Nothing = {writer Is Nothing}")
Console.WriteLine($"Point2D : ({spot.X}, {spot.Y})")
' Nothing means "the default value", so for value types the comparison below is not a null test.
count = 0
Console.WriteLine($"count = Nothing -> {count = Nothing}")
Dim empty As String = ""
Console.WriteLine($"empty = Nothing -> {empty = Nothing}")
Console.WriteLine($"empty Is Nothing -> {empty Is Nothing}")
Console.WriteLine($"String.IsNullOrEmpty(empty) -> {String.IsNullOrEmpty(empty)}")
Console.WriteLine($"DefaultOf(Of Integer) = {DefaultOf(Of Integer)()}")
Console.WriteLine($"DefaultOf(Of Author) Is Nothing = {DefaultOf(Of Author)() Is Nothing}")
Console.WriteLine($"writer IsNot Nothing = {writer IsNot Nothing}")
End Sub
End Module
Integer : 0
Boolean : False
Date : 0001-01-01 (Date.MinValue = True)
String : title Is Nothing = True
Author : writer Is Nothing = True
Point2D : (0, 0)
count = Nothing -> True
empty = Nothing -> True
empty Is Nothing -> False
String.IsNullOrEmpty(empty) -> True
DefaultOf(Of Integer) = 0
DefaultOf(Of Author) Is Nothing = True
writer IsNot Nothing = False
Source: Nothing (Visual Basic)
Four things to take from the output:
-
Dim whenBorn As Date = Nothingis not an error and not a "null date": it isDate.MinValue. ADatecan never be missing; useDate?when "unknown" is a legitimate value (see below). -
count = Nothingis true whenevercountis0. For a value type the comparison withNothingcompares with the default value, so it is not a test for "no value". -
A
Stringis a reference type, soNothingis a string that does not exist, and the empty string""is a string that exists and has no characters. But the=operator on strings treats the two alike:"" = NothingisTrue, while"" Is NothingisFalse. This is a deliberate compatibility rule inherited from Visual Basic 6 and described under Strings and Nothing. -
The generic helper
DefaultOf(Of T)returns0forIntegerand a missing reference forAuthor, both written asReturn Nothing.
Is Nothing, IsNot Nothing and = Nothing
Use Is Nothing and IsNot Nothing to test a reference for "no object". Is is the reference-comparison operator: it asks
whether two expressions refer to the same object, and Nothing is the "no object" reference, so x Is Nothing is exactly
the null test. IsNot (VB 2005, version 8) is its negation, with the same precedence as the comparison operators, so
If person IsNot Nothing Then replaces the older and clumsier If Not person Is Nothing Then.
| Expression | Allowed on | Meaning and pitfalls |
|---|---|---|
|
reference types and nullable value types |
True when there is no object (or, for |
|
same as |
Negation; the idiom for "has an object". Not |
|
value types, |
On a value type: compares with the default value ( |
|
anything (the argument is boxed to |
A legacy runtime function in |
|
strings |
True for |
|
strings |
Also true for strings that contain only white-space characters |
The rule of thumb is: Is Nothing for objects, HasValue or Is Nothing for nullable values, and never = Nothing unless you
specifically want a comparison with the default value.
NullReferenceException and the defensive tools
Calling a member through a reference that is Nothing throws System.NullReferenceException at run time. The compiler can warn
about the clearest cases only: BC42104 ("Variable t is used before it has been assigned a value. A null reference exception could
result at runtime") fires for a local String or class variable that is read before any assignment, and it is a warning, so with
TreatWarningsAsErrors it stops the build. An explicit = Nothing initializer silences it, and the compiler does not track
Nothing through method calls at all: VB has no nullable-reference flow analysis (see below).
Option Strict On
Public Class Author
Public Property Name As String
End Class
Module Ex02
Sub Main()
Dim writer As Author = Nothing
Dim title As String = Nothing
Try
Console.WriteLine(writer.Name.Length)
Catch ex As NullReferenceException
Console.WriteLine("NullReferenceException: " & ex.Message)
End Try
Console.WriteLine($"Len(title) = {Len(title)}")
Console.WriteLine($"title & ""!"" = '{title & "!"}'")
Console.WriteLine($"String.IsNullOrEmpty = {String.IsNullOrEmpty(title)}")
Console.WriteLine($"IsNullOrWhiteSpace("" "") = {String.IsNullOrWhiteSpace(" ")}")
Console.WriteLine($"IsNothing(writer) = {IsNothing(writer)}")
Console.WriteLine($"If(title, ""(none)"") = {If(title, "(none)")}")
Console.WriteLine($"title = ""abc"" = {title = "abc"}")
Console.WriteLine($"String.Equals(title, ""abc"") = {String.Equals(title, "abc")}")
Dim names As String() = {"Ada", Nothing, "Alan"}
Dim total = 0
For Each name In names
total += If(name Is Nothing, 0, name.Length)
Next
Console.WriteLine($"Total length = {total}")
End Sub
End Module
NullReferenceException: Object reference not set to an instance of an object.
Len(title) = 0
title & "!" = '!'
String.IsNullOrEmpty = True
IsNullOrWhiteSpace(" ") = True
IsNothing(writer) = True
If(title, "(none)") = (none)
title = "abc" = False
String.Equals(title, "abc") = False
Total length = 7
Source: String.IsNullOrEmpty Method
The tools shown above, from the strictest to the most forgiving:
-
Test and branch:
If writer IsNot Nothing Then ..., orIf name Is Nothing Then Return. This is the safest style for code that must tell "absent" from "empty". -
Use a library function that already treats
Nothingas empty.Len(title)returns0forNothingandtitle & "!"treatsNothingas""; both are legacy Visual Basic runtime behaviours that theStringinstance members (title.Length) do not share.String.IsNullOrEmpty,String.IsNullOrWhiteSpaceandString.Equals(a, b)(a static, null-safe comparison) are the .NET equivalents. -
Supply a default with the
Ifoperator:If(title, "(none)")(see below). -
Short-circuit the chain with
?.(see below).
Strings and Nothing
Visual Basic 6 had no null string, so the compatibility rules of the language treat a String that is Nothing like the empty
string in every operation that predates .NET: & concatenation, the = and <> operators, Len, and the Microsoft.VisualBasic.Strings
functions. Only a member call on the string (s.Length, s.ToUpper()) and the operator Is tell them apart. Practical
consequences:
-
If s = "" Thenis true for both a missing and an empty string, which is usually what a legacy VB program means, but the same test written in C# (s == "") is false fornull. -
Never use
s = Nothingto test for an empty string; it is true for""too, and it hides the intention. WriteString.IsNullOrEmpty(s). -
A column that can be
NULLin a database arrives asDBNull.Value, a different thing again, not asNothing(see Boxing, DBNull and generic constraints).
Nullable value types
A value type cannot be Nothing in the "no value" sense, and for numbers, dates and flags a missing value is common (a birth
date that nobody knows, a loan that has not been returned yet). Visual Basic 2005 (version 8) added the generic structure
System.Nullable(Of T), with a shorthand: a question mark after the type name, Integer?, or after the variable name,
Dim c? As Integer. The two spellings, and an Integer? parameter or property, are the same type. T must be a value type: the
compiler rejects Nullable(Of String) with BC33101 ("Type String must be a value type or a type argument constrained to Structure in
order to be used with Nullable or nullable modifier `?`").
Option Strict On
Public Class Author
Public Property Name As String
Public Property BornOn As Date?
Public Sub New(name As String, Optional bornOn As Date? = Nothing)
Me.Name = name
Me.BornOn = bornOn
End Sub
End Class
Module Ex03
Sub Main()
Dim a As Integer? = 5
Dim b As Nullable(Of Integer) = Nothing
Dim c? As Integer = 7
Console.WriteLine($"a.HasValue = {a.HasValue}, a.Value = {a.Value}")
Console.WriteLine($"b.HasValue = {b.HasValue}, GetValueOrDefault = {b.GetValueOrDefault()}, with fallback = {b.GetValueOrDefault(-1)}")
Console.WriteLine($"c = {c}; type of a: {a.GetType().Name}; type of c: {c.GetType().Name}")
Try
Console.WriteLine(b.Value)
Catch ex As InvalidOperationException
Console.WriteLine("InvalidOperationException: " & ex.Message)
End Try
Dim lovelace As New Author("Ada Lovelace", #12/10/1815#)
Dim anonymous As New Author("Anonymous")
For Each writer In {lovelace, anonymous}
If writer.BornOn.HasValue Then
Console.WriteLine($"{writer.Name} was born on {writer.BornOn.Value:yyyy-MM-dd}")
Else
Console.WriteLine($"{writer.Name} has no recorded birth date")
End If
Next
' Arithmetic is lifted: any Nothing operand gives Nothing.
Dim sum = a + b
Console.WriteLine($"a + b has value: {sum.HasValue}")
Console.WriteLine($"a + c = {a + c}")
Dim parsed As Integer? = Nothing
Dim number As Integer
If Integer.TryParse("42", number) Then parsed = number
Console.WriteLine($"parsed = {parsed}")
Console.WriteLine($"Nothing prints as: '{b}'")
End Sub
End Module
a.HasValue = True, a.Value = 5
b.HasValue = False, GetValueOrDefault = 0, with fallback = -1
c = 7; type of a: Int32; type of c: Int32
InvalidOperationException: Nullable object must have a value.
Ada Lovelace was born on 1815-12-10
Anonymous has no recorded birth date
a + b has value: False
a + c = 12
parsed = 42
Nothing prints as: ''
The members of Nullable(Of T) that matter:
| Member | Behaviour |
|---|---|
|
|
|
The value; throws |
|
The value, or the default of |
|
The value, or |
|
The value formatted, or the empty string when there is none; interpolation prints an empty string for |
|
Two values with no value are equal ( |
|
Explicit conversion from |
Because Integer? is a structure, a.GetType() on a variable with a value returns Int32, not Nullable(Of Int32): the
method call boxes the value, and boxing a nullable produces either a boxed Integer or a Nothing reference (see the boxing
section below).
Lifted operators and three-valued logic
Every arithmetic, comparison, bitwise and logical operator defined for T is lifted to T?. The rules differ from C# and are a
frequent source of surprises:
-
Arithmetic (
+,-,*,/,\,Mod,^): if either operand has no value, the result has no value.a + bisInteger?(here with no value) anda + cis12. -
Comparison (
=,<>,<,<=,>,>=): the result is aBoolean?that has no value when either operand has no value. In C#,null == nullistrue,null != nullisfalse, andx < ywith a null isfalse; in Visual Basic every comparison involving a missing value isNothing. -
An
If,WhileorDo ... Untilcondition treats aNothingresult asFalse(checked: aWhile n < 3loop with a missingnis never entered). That is why neitherIf a = b ThennorIf a <> b Thenis taken when both are missing, and it is whyIf Not (a = b)is also not taken:Not NothingisNothing. -
Logical operators
And,Or,Not,Xor,AndAlsoandOrElseonBoolean?follow SQL-style three-valued logic. A missing operand only matters when the other operand does not already decide the result:False And NothingisFalseandTrue Or NothingisTrue. VB also allowsAndAlsoandOrElseonBoolean?(C# does not allow&&and||onbool?), with the same truth tables asAndandOrplus short-circuiting.
Option Strict On
Module Ex04
Function Show(value As Boolean?) As String
Return If(value.HasValue, value.Value.ToString(), "Nothing")
End Function
Sub Main()
Dim states As Boolean?() = {True, False, Nothing}
Console.WriteLine("x y And Or AndAlso OrElse")
For Each x In states
For Each y In states
Console.WriteLine($"{Show(x),-8} {Show(y),-8} {Show(x And y),-8} {Show(x Or y),-8} {Show(x AndAlso y),-8} {Show(x OrElse y),-8}")
Next
Next
Console.WriteLine($"Not Nothing = {Show(Not states(2))}")
Dim a As Integer? = Nothing
Dim b As Integer? = Nothing
Console.WriteLine($"a = b gives {Show(a = b)}")
Console.WriteLine($"a <> b gives {Show(a <> b)}")
Console.WriteLine($"a < 1 gives {Show(a < 1)}")
If a = b Then
Console.WriteLine("If a = b: taken")
Else
Console.WriteLine("If a = b: NOT taken (Nothing counts as False)")
End If
If a <> b Then
Console.WriteLine("If a <> b: taken")
Else
Console.WriteLine("If a <> b: NOT taken (Nothing counts as False)")
End If
If Not (a = b) Then
Console.WriteLine("If Not (a = b): taken")
Else
Console.WriteLine("If Not (a = b): NOT taken")
End If
Console.WriteLine($"Both missing: {Not a.HasValue AndAlso Not b.HasValue}")
Console.WriteLine($"Equals: {a.Equals(b)}, Nullable.Equals: {Nullable.Equals(a, b)}")
Console.WriteLine($"Compare: {Nullable.Compare(a, 3)}")
End Sub
End Module
x y And Or AndAlso OrElse
True True True True True True
True False False True False True
True Nothing Nothing True Nothing True
False True False True False True
False False False False False False
False Nothing False Nothing False Nothing
Nothing True Nothing True Nothing True
Nothing False False Nothing False Nothing
Nothing Nothing Nothing Nothing Nothing Nothing
Not Nothing = Nothing
a = b gives Nothing
a <> b gives Nothing
a < 1 gives Nothing
If a = b: NOT taken (Nothing counts as False)
If a <> b: NOT taken (Nothing counts as False)
If Not (a = b): NOT taken
Both missing: True
Equals: True, Nullable.Equals: True
Compare: -1
Reading the table row by row: Nothing And False is False, Nothing Or True is True, every other combination with a missing operand
is missing. To test for "both missing" or "equal including missing", ask HasValue explicitly, or use Nullable.Equals(a, b) /
a.Equals(b), which treat two missing values as equal like C#'s ==. Nullable.Compare(a, b) orders a missing value before every
real value, which is also how List(Of Integer?).Sort() orders them (see the last example on this page).
The If operator as null coalescing
If(a, b) with two arguments (VB 9, Visual Studio 2008) is the null-coalescing operator: it returns a if a is not Nothing, otherwise
b. It evaluates b only when it is needed, and it works with reference types (If(title, "n/a")), nullable value types
(If(maybe, 10) unwraps to Integer) and chains, in the same way as C#'s ??. VB has no ??=; write the assignment out:
name = If(name, "default"). The three-argument form If(condition, whenTrue, whenFalse) is the conditional operator and also
short-circuits. It must not be confused with the old IIf function, which evaluates both branches and returns Object (see
Operators and Expressions).
There is one trap in the three-argument form that does not exist in C# 9 and later. The type of If(c, 1, Nothing) is the dominant
type of its branches, which is Integer, so Nothing becomes 0. VB 17.13 does not target-type the expression: assigning it to an
Integer? does not change that. Give one branch the nullable type explicitly with CType(1, Integer?).
Option Strict On
Module Ex05
Function PickLabel(isRush As Boolean) As Integer?
' The type of the If expression is Integer (the dominant type of 1 and Nothing), not Integer?.
Return If(isRush, 1, Nothing)
End Function
Function PickLabelCorrectly(isRush As Boolean) As Integer?
' Make one branch Integer? so that Nothing stays "no value".
Return If(isRush, CType(1, Integer?), Nothing)
End Function
Sub Main()
Dim maybe As Integer? = Nothing
Dim fallback = If(maybe, 10)
Console.WriteLine($"If(maybe, 10) = {fallback}")
Dim name As String = Nothing
Console.WriteLine($"If(name, ""n/a"") = {If(name, "n/a")}")
Dim chained = If(name, If(Environment.GetEnvironmentVariable("SHELF_NO_SUCH_VARIABLE"), "default"))
Console.WriteLine($"chain = {chained}")
Dim viaTernary As Integer? = If(False, 1, Nothing)
Dim untyped = If(False, 1, Nothing)
Console.WriteLine($"Integer? target: HasValue = {viaTernary.HasValue}, value = {viaTernary}")
Console.WriteLine($"inferred type : {untyped.GetType().Name}, value = {untyped}")
Console.WriteLine($"PickLabel(False).HasValue = {PickLabel(False).HasValue}")
Console.WriteLine($"PickLabelCorrectly(False).HasValue = {PickLabelCorrectly(False).HasValue}")
Dim text As String = "Shelf"
Dim missing As String = Nothing
Console.WriteLine($"text?.Length = {text?.Length}")
Console.WriteLine($"missing?.Length = '{missing?.Length}' (HasValue = {(missing?.Length).HasValue})")
Console.WriteLine($"missing?.ToUpperInvariant() Is Nothing = {missing?.ToUpperInvariant() Is Nothing}")
Console.WriteLine($"If(missing?.Length, 0) = {If(missing?.Length, 0)}")
Dim items As String() = Nothing
Console.WriteLine($"items?(0) Is Nothing = {items?(0) Is Nothing}")
Dim names As New Dictionary(Of Integer, String) From {{1, "Ada"}}
Console.WriteLine($"names?(1) = {names?(1)}")
Dim nothingDict As Dictionary(Of Integer, String) = Nothing
Console.WriteLine($"nothingDict?(1) Is Nothing = {nothingDict?(1) Is Nothing}")
Dim callback As Action = Nothing
callback?.Invoke()
Console.WriteLine("callback?.Invoke() did nothing")
End Sub
End Module
If(maybe, 10) = 10
If(name, "n/a") = n/a
chain = default
Integer? target: HasValue = True, value = 0
inferred type : Int32, value = 0
PickLabel(False).HasValue = True
PickLabelCorrectly(False).HasValue = False
text?.Length = 5
missing?.Length = '' (HasValue = False)
missing?.ToUpperInvariant() Is Nothing = True
If(missing?.Length, 0) = 0
items?(0) Is Nothing = True
names?(1) = Ada
nothingDict?(1) Is Nothing = True
callback?.Invoke() did nothing
Source: If Operator (Visual Basic)
Null-conditional operators
The null-conditional member access ?. and index access ?(...) arrived with Visual Basic 14 (Visual Studio 2015). x?.Member
evaluates to Nothing when x is Nothing and to x.Member otherwise; the whole rest of the chain is skipped. The type of the result is
the member’s type made nullable: text?.Length is Integer?. Combine it with the binary If to supply a default:
If(missing?.Length, 0). callback?.Invoke() is the thread-safe way to raise a delegate or event that may have no handlers (the delegate is read
once). Gotchas:
-
?.binds into the chain that follows it:missing?.Length.HasValuedoes not compile, because.HasValueis applied to theIntegerthat the chain would produce. Parenthesise:(missing?.Length).HasValue. -
?.cannot be the target of an assignment (person?.Name = "x"gives BC30068, "Expression is a value and therefore cannot be the target of an assignment"), and there is no VB equivalent of??=or of the C# null-forgiving operator!(in VB the!character is the dictionary-lookup operator,dict!key). -
x?.Method()as a statement is allowed forSubandFunctioncalls, and for a missing receiver nothing happens.
Boxing, DBNull and generic constraints
Converting a Nullable(Of T) to Object boxes it in a special way: a value becomes a boxed T (Int32 here, not a boxed Nullable), and
a missing value becomes a Nothing reference. Consequently o.GetType() on a boxed missing value throws NullReferenceException, and
TypeOf o Is Integer is true for a boxed Integer? that has a value. Nullable.GetUnderlyingType(GetType(Integer?)) returns Integer
and, for a non-nullable type, Nothing. DBNull.Value is a singleton object that ADO.NET returns for a SQL NULL: it is not Nothing,
so row("Isbn") Is Nothing does not find missing columns; test with Convert.IsDBNull (or IsDBNull) as the example shows, and map to
Nothing or Integer? at the data-access boundary (see
Data Access: ADO.NET and EF Core).
Option Strict On
Module Ex06
Function Coalesce(Of T As Structure)(value As T?, fallback As T) As T
Return If(value.HasValue, value.Value, fallback)
End Function
Sub Main()
Dim present As Integer? = 3
Dim absent As Integer? = Nothing
Dim o1 As Object = present
Dim o2 As Object = absent
Console.WriteLine($"boxed present: {o1.GetType().Name} = {o1}")
Console.WriteLine($"boxed absent Is Nothing = {o2 Is Nothing}")
Try
Console.WriteLine(absent.GetType().Name)
Catch ex As NullReferenceException
Console.WriteLine("absent.GetType(): NullReferenceException")
End Try
Console.WriteLine($"Coalesce(present, 9) = {Coalesce(present, 9)}")
Console.WriteLine($"Coalesce(absent, 9) = {Coalesce(absent, 9)}")
Console.WriteLine($"Coalesce(Of Date) on Nothing = {Coalesce(CType(Nothing, Date?), #1/1/2000#):yyyy-MM-dd}")
Console.WriteLine($"Nullable.GetUnderlyingType(Integer?) = {Nullable.GetUnderlyingType(GetType(Integer?))}")
Console.WriteLine($"Nullable.GetUnderlyingType(Integer) = '{Nullable.GetUnderlyingType(GetType(Integer))}'")
Dim fromDb As Object = DBNull.Value
Dim copies As Integer? = If(Convert.IsDBNull(fromDb), Nothing, CType(fromDb, Integer?))
Console.WriteLine($"DBNull.Value mapped to Nothing: HasValue = {copies.HasValue}")
Console.WriteLine($"DBNull.Value Is Nothing = {fromDb Is Nothing}")
Dim sorted As New List(Of Integer?) From {5, Nothing, 2}
sorted.Sort()
Console.WriteLine("Sorted: " & String.Join(", ", sorted.Select(Function(n) If(n.HasValue, n.Value.ToString(), "Nothing"))))
End Sub
End Module
boxed present: Int32 = 3
boxed absent Is Nothing = True
absent.GetType(): NullReferenceException
Coalesce(present, 9) = 3
Coalesce(absent, 9) = 9
Coalesce(Of Date) on Nothing = 2000-01-01
Nullable.GetUnderlyingType(Integer?) = System.Int32
Nullable.GetUnderlyingType(Integer) = ''
DBNull.Value mapped to Nothing: HasValue = False
DBNull.Value Is Nothing = False
Sorted: Nothing, 2, 5
Source: Nullable<T> Struct
Notes on the constraint: Coalesce(Of T As Structure)(value As T?, fallback As T) is the shape that works for every nullable value type; a
Structure constraint is needed because T? is only legal for value types. A VB program can also compare nullable values for ordering: the
default comparer sorts a missing value first.
VB does not have nullable reference types
C# 8 added nullable reference types (NRT): a compile-time analysis in which string means "not null", string? means "may be null", and the
compiler warns about dereferences. Visual Basic 17.13 has none of it, and the language strategy (stable, consumption-only, no new syntax)
means it will not get it. What that means in practice:
-
In VB every reference-typed variable can be
Nothing. There is no syntax to declare otherwise (String?is rejected with BC33101) and no warning when you passNothingto a parameter or dereference a result. -
A project property
<Nullable>enable</Nullable>is accepted by the VB build and has no effect (the project for the next example sets it). -
The BCL’s nullable annotations and attributes (
NotNullWhen,MaybeNullWhen,NotNull,AllowNull,DoesNotReturn) are ordinary attributes in metadata that the VB compiler ignores. VB code can still call annotated APIs ("consumption"), but the annotations do not protect it. The warning a C# caller ofPath.GetDirectoryName("/")would get for dereferencing a possiblenullis missing, so in VB treat everystring?in the C# documentation as "can be `Nothing`". -
Code that you write for a library used by C# callers can use
<AllowNull>,<NotNull>and the other attributes fromSystem.Diagnostics.CodeAnalysisas documentation, but VB does not read them back, andArgumentNullException.ThrowIfNullis the checked guard (see Structured Exception Handling).
Option Strict On
Imports System.IO
Module Ex07
Sub Main()
' Path.GetDirectoryName is annotated "string?" in C#: it returns null for a root path.
Dim parent As String = Path.GetDirectoryName("/")
Console.WriteLine($"GetDirectoryName(""/"") Is Nothing = {parent Is Nothing}")
Try
Console.WriteLine(parent.Length)
Catch ex As NullReferenceException
Console.WriteLine("Compiled without a warning, failed at run time: NullReferenceException")
End Try
Dim lookup As New Dictionary(Of String, String) From {{"lang", "VB"}}
Dim found As String = Nothing
If lookup.TryGetValue("missing", found) Then
Console.WriteLine(found)
Else
Console.WriteLine($"TryGetValue left found = {If(found, "Nothing")}")
End If
Dim safe = If(Path.GetDirectoryName("/"), "(root)")
Console.WriteLine($"Defensive version: {safe}")
End Sub
End Module
GetDirectoryName("/") Is Nothing = True
Compiled without a warning, failed at run time: NullReferenceException
TryGetValue left found = Nothing
Defensive version: (root)
Comparison with C# and the books
| Topic | Visual Basic | C# |
|---|---|---|
The null literal |
|
|
Test for no object |
|
|
Value type compared with the literal |
|
|
Null coalescing |
|
|
Null-conditional |
|
|
Lifted comparison with a missing value |
|
|
Lifted |
allowed |
not allowed ( |
Nullable reference types |
none (annotations in metadata are ignored) |
C# 8 with |
Conditional with |
|
C# 9 target-typed |
Where older material is outdated. Tutorials and books written for Visual Basic 6 or early VB.NET describe Nothing mostly as the way to
release an object (Set obj = Nothing) and treat nullable value types as a footnote. They miss three things that matter today: setting a local
variable to Nothing at the end of a procedure does not free anything earlier (the .NET garbage collector decides, see
Object Lifetime and Disposal), the null-conditional ?. (VB 14), and the fact
that a Boolean? comparison is a three-valued value, not False. Their advice to compare strings with = "" hides the difference between Nothing and the empty
string described above; use String.IsNullOrEmpty.
Pitfalls checklist
-
x = Nothingon a value type is a default-value comparison, and on aStringit is also true for"". UseIs NothingorString.IsNullOrEmpty. -
A
Dateis never missing; its default isDate.MinValue(0001-01-01), so a sentinel check (If d = Date.MinValue) orDate?is required. -
If(c, 1, Nothing)is0, not a missingInteger?. -
If a = bandIf a <> bcan both be skipped when an operand of nullable type is missing. -
ValuethrowsInvalidOperationExceptionwhen there is no value;GetValueOrDefaultandIf(x, fallback)do not. -
?.returns a nullable value for value-typed members:s?.LengthisInteger?, so anIntegertarget needsIf(s?.Length, 0). -
Reading an unassigned
Stringlocal gives BC42104 as a warning (an error underTreatWarningsAsErrors); initialise with= Nothingonly when you mean it. -
A SQL
NULLisDBNull.Value, notNothing. -
No VB annotation, analyzer setting or project property enables nullable-reference-type warnings.