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

Four things to take from the output:

  • Dim whenBorn As Date = Nothing is not an error and not a "null date": it is Date.MinValue. A Date can never be missing; use Date? when "unknown" is a legitimate value (see below).

  • count = Nothing is true whenever count is 0. For a value type the comparison with Nothing compares with the default value, so it is not a test for "no value".

  • A String is a reference type, so Nothing is 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: "" = Nothing is True, while "" Is Nothing is False. This is a deliberate compatibility rule inherited from Visual Basic 6 and described under Strings and Nothing.

  • The generic helper DefaultOf(Of T) returns 0 for Integer and a missing reference for Author, both written as Return 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

x Is Nothing

reference types and nullable value types

True when there is no object (or, for Integer?, no value). Applied to a non-nullable value type such as Integer, it does not compile: BC30020 ("`Is` operator does not accept operands of type Integer. Operands must be reference or nullable types")

x IsNot Nothing

same as Is

Negation; the idiom for "has an object". Not Not x Is Nothing

x = Nothing

value types, String, nullable value types

On a value type: compares with the default value (0, False, …​), so it is a bug if you meant "missing". On a String: true for Nothing and for "". On Integer?: result is Nothing (three-valued logic below), which an If treats as false. On Object or another class without an = operator: error BC32013 under Option Strict On ("Option Strict On disallows operands of type Object for operator =. Use the Is operator to test for object identity")

IsNothing(x)

anything (the argument is boxed to Object)

A legacy runtime function in Microsoft.VisualBasic.Information; it works for every type but boxes value types, and Is Nothing is clearer

String.IsNullOrEmpty(s)

strings

True for Nothing and ""; the usual guard before using a string argument

String.IsNullOrWhiteSpace(s)

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

The tools shown above, from the strictest to the most forgiving:

  • Test and branch: If writer IsNot Nothing Then ..., or If 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 Nothing as empty. Len(title) returns 0 for Nothing and title & "!" treats Nothing as ""; both are legacy Visual Basic runtime behaviours that the String instance members (title.Length) do not share. String.IsNullOrEmpty, String.IsNullOrWhiteSpace and String.Equals(a, b) (a static, null-safe comparison) are the .NET equivalents.

  • Supply a default with the If operator: 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 = "" Then is 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 for null.

  • Never use s = Nothing to test for an empty string; it is true for "" too, and it hides the intention. Write String.IsNullOrEmpty(s).

  • A column that can be NULL in a database arrives as DBNull.Value, a different thing again, not as Nothing (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

HasValue

True when a value is present

Value

The value; throws InvalidOperationException ("Nullable object must have a value") when there is none, which is a programming error, not a data error: test HasValue first

GetValueOrDefault()

The value, or the default of T (0, False, Date.MinValue) when there is none

GetValueOrDefault(fallback)

The value, or fallback

ToString()

The value formatted, or the empty string when there is none; interpolation prints an empty string for Nothing

Equals, GetHashCode

Two values with no value are equal (Nullable.Equals(a, b) is True), and the hash code of a missing value is 0

CType(x, T) or x.Value

Explicit conversion from T? to T. The implicit conversion from T to T? always works, the other direction is BC30512 under Option Strict On ("disallows implicit conversions from Integer? to `Integer`")

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 + b is Integer? (here with no value) and a + c is 12.

  • Comparison (=, <>, <, <=, >, >=): the result is a Boolean? that has no value when either operand has no value. In C#, null == null is true, null != null is false, and x < y with a null is false; in Visual Basic every comparison involving a missing value is Nothing.

  • An If, While or Do ... Until condition treats a Nothing result as False (checked: a While n < 3 loop with a missing n is never entered). That is why neither If a = b Then nor If a <> b Then is taken when both are missing, and it is why If Not (a = b) is also not taken: Not Nothing is Nothing.

  • Logical operators And, Or, Not, Xor, AndAlso and OrElse on Boolean? follow SQL-style three-valued logic. A missing operand only matters when the other operand does not already decide the result: False And Nothing is False and True Or Nothing is True. VB also allows AndAlso and OrElse on Boolean? (C# does not allow && and || on bool?), with the same truth tables as And and Or plus 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

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.HasValue does not compile, because .HasValue is applied to the Integer that 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 for Sub and Function calls, 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

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 pass Nothing to 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 of Path.GetDirectoryName("/") would get for dereferencing a possible null is missing, so in VB treat every string? 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 from System.Diagnostics.CodeAnalysis as documentation, but VB does not read them back, and ArgumentNullException.ThrowIfNull is 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

Nothing, also the default of any type

null (references and nullable values) and default (any type)

Test for no object

x Is Nothing, x IsNot Nothing

x is null, x is not null, x == null

Value type compared with the literal

i = Nothing is true for 0

i == null does not compile for int

Null coalescing

If(a, b) (VB 9), no assignment form

a ?? b, a ??= b

Null-conditional

a?.b, a?(i) (VB 14)

a?.b, a?[i], with ?. assignment since C# 14

Lifted comparison with a missing value

Nothing (acts as false in a condition)

false for ordering, true for null == null

Lifted AndAlso/OrElse on bool?

allowed

not allowed (&& needs bool)

Nullable reference types

none (annotations in metadata are ignored)

C# 8 with #nullable enable and flow analysis

Conditional with null branch

If(c, 1, Nothing) is an Integer (0)

C# 9 target-typed c ? 1 : null to int? works

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 = Nothing on a value type is a default-value comparison, and on a String it is also true for "". Use Is Nothing or String.IsNullOrEmpty.

  • A Date is never missing; its default is Date.MinValue (0001-01-01), so a sentinel check (If d = Date.MinValue) or Date? is required.

  • If(c, 1, Nothing) is 0, not a missing Integer?.

  • If a = b and If a <> b can both be skipped when an operand of nullable type is missing.

  • Value throws InvalidOperationException when there is no value; GetValueOrDefault and If(x, fallback) do not.

  • ?. returns a nullable value for value-typed members: s?.Length is Integer?, so an Integer target needs If(s?.Length, 0).

  • Reading an unassigned String local gives BC42104 as a warning (an error under TreatWarningsAsErrors); initialise with = Nothing only when you mean it.

  • A SQL NULL is DBNull.Value, not Nothing.

  • No VB annotation, analyzer setting or project property enables nullable-reference-type warnings.