Enumerations

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and was checked on 2026-10-19. Every example is a complete console program that starts with Option Strict On, was built with warnings treated as errors and was run in the .NET 10 SDK container (Linux); the output shown is real output. The examples that are meant to fail say so, and show the compiler messages that the SDK printed. The project-level imports that the SDK adds (Microsoft.VisualBasic, System, System.Collections.Generic, System.Linq and others) are why Console, [Enum] and ControlChars need no Imports.

An enumeration is a value type with a fixed set of named constants over an integral type. It replaces "magic numbers" in arguments, state fields and switches, and it is the standard way to model a closed set of alternatives that fits a number. The running example is the LoanState enumeration of the shelf library catalogue: a Loan is Active, Returned or Overdue. Visual Basic enumerations are the .NET System.Enum types, so everything you know from C# applies, with three differences that make this page longer than you might expect: enumerations behave like numbers in several VB operators (including &, which prints the number, not the name), integer literals do not convert to an enumeration under Option Strict On, and a few Microsoft.VisualBasic members are enumerations or constants that only exist in this language’s runtime library.

Declaring an enumeration

[<attributes>] [accessmodifier] [Shadows] Enum Name [As IntegralType]
    Member1 [= constant-expression]
    Member2
End Enum
  • The members are listed one per line. An Enum can be declared at namespace level, or inside a class, structure, module or interface, but not inside a procedure. Without an access modifier a top-level Enum is Friend and a nested one is Public, as for other types (see Variables, Constants and Scope).

  • The underlying type is Integer unless an As clause names Byte, SByte, Short, UShort, Integer, UInteger, Long or ULong. Anything else (Double, String, Char) is rejected with BC30650.

  • A member without an initializer is the previous member plus one; the first one is 0. An initializer is a constant expression, which may use earlier members and constants.

  • Two members may have the same value. They are aliases: ToString and [Enum].GetName return one of the names, and the documentation does not promise which.

  • Shadows hides an enumeration of the same name in a base class.

  • Enum is a reserved word, so the class System.Enum is written [Enum] (or System.Enum) in code.

Option Strict On

Public Enum LoanState
    Active = 1
    Returned = 2
    Overdue = 3
End Enum

Module Program
    Sub Main()
        Dim state As LoanState = LoanState.Active
        Console.WriteLine(state.ToString())                        ' the member name
        Console.WriteLine(state)                                   ' the underlying value: see below
        Console.WriteLine(CInt(state))                             ' the underlying value
        Console.WriteLine(state.GetType().Name & " over " & [Enum].GetUnderlyingType(GetType(LoanState)).Name)

        ' A variable that was never assigned is 0, even though no member has the value 0.
        Dim blank As LoanState
        Console.WriteLine("default: " & blank.ToString() & " (defined: " & [Enum].IsDefined(GetType(LoanState), blank) & ")")

        ' Casting does not validate.
        Dim made = CType(99, LoanState)
        Console.WriteLine("cast 99: " & made.ToString() & " (defined: " & [Enum].IsDefined(GetType(LoanState), made) & ")")

        ' Comparison and arithmetic work on the underlying values.
        Console.WriteLine(LoanState.Overdue > LoanState.Active)
        Console.WriteLine(TypeName(state + 1) & ": " & (state + 1).ToString())
        Console.WriteLine(NameOf(LoanState.Returned) & " -> " & LoanState.Returned.ToString("D"))
    End Sub
End Module
Active
1
1
LoanState over Int32
default: 0 (defined: False)
cast 99: 99 (defined: False)
True
Integer: 2
Returned -> 2

Source: Enum Statement

Three lessons are in this output.

  1. The default value of an enumeration variable is 0, whether or not a member has that value. blank prints as 0, not as a name, and IsDefined is False. Always give an enumeration an explicit "none" member with the value 0, or start the real members at 1 on purpose and treat 0 as "unset", as LoanState does.

  2. CType(99, LoanState) succeeds. A conversion between an integer and an enumeration is a numeric conversion: it does not check that the value names a member. Validate external input with [Enum].IsDefined, as shown below.

  3. The second line printed 1, although the variable holds LoanState.Active. That is not an accident; it is how overload resolution and the & operator treat enumerations in Visual Basic.

Printing an enumeration

In C#, Console.WriteLine(state) and "x" + state both print the member name, because the enumeration is boxed into an Object. Visual Basic prefers a better numeric conversion: an enumeration widens to its underlying type, so the Integer overload of WriteLine wins, and the & operator converts an enumeration operand with its numeric value. Whether a call prints the name or the number depends on which overload the compiler picks:

Option Strict On

Public Enum LoanState
    Active = 1
    Returned = 2
    Overdue = 3
End Enum

Module Program
    Sub Main()
        Dim state As LoanState = LoanState.Overdue
        Console.Write("Console.WriteLine(state)          : ")
        Console.WriteLine(state)
        Console.Write("Console.WriteLine(CObj(state))    : ")
        Console.WriteLine(CObj(state))
        Console.Write("Console.WriteLine(""{0}"", state)    : ")
        Console.WriteLine("{0}", state)
        Console.WriteLine("""text"" & state                    : " & "text " & state)
        Console.WriteLine("state.ToString()                : " & state.ToString())
        Console.WriteLine("$""{state}""                       : " & $"{state}")
        Console.WriteLine("String.Concat(""text "", state)   : " & String.Concat("text ", state))
        Console.WriteLine("CStr(state)                     : " & CStr(state))
        Console.WriteLine("Convert.ToString(state)         : " & Convert.ToString(state))
        Console.WriteLine("String.Format(""{0}"", state)     : " & String.Format("{0}", state))
    End Sub
End Module
Console.WriteLine(state)          : 3
Console.WriteLine(CObj(state))    : Overdue
Console.WriteLine("{0}", state)    : Overdue
"text" & state                    : text 3
state.ToString()                : Overdue
$"{state}"                       : Overdue
String.Concat("text ", state)   : text Overdue
CStr(state)                     : 3
Convert.ToString(state)         : 3
String.Format("{0}", state)     : Overdue
Expression Prints Why

state.ToString()

the name

Enum.ToString

$"{state}"

the name

interpolation formats the boxed value

String.Format("{0}", state), Console.WriteLine("{0}", state)

the name

the argument is an Object

String.Concat("text ", state), Console.WriteLine(CObj(state))

the name

converted to Object

Console.WriteLine(state), Console.Write(state)

the number

the Integer overload is chosen

"text " & state

the number

& converts the enumeration as its underlying type

CStr(state), Convert.ToString(state)

the number

same numeric conversion

The safe habit is to call .ToString() (or .ToString("G")) whenever an enumeration becomes text, and to use interpolation for messages. Logging frameworks that take an Object print the name, which is why the problem hides in the console output of test programs. Analyzers cannot see it, because every line is valid.

Flags enumerations

A set of independent options is modelled as an enumeration whose members are single bits, marked <Flags>. The attribute changes how ToString and Parse handle combinations ("Hardcover, Ebook" instead of a number); it does not change the arithmetic, which is plain bitwise logic that you must keep correct yourself.

Option Strict On

<Flags>
Public Enum BookFormat As Byte
    None = 0
    Hardcover = 1
    Paperback = 2
    Ebook = 4
    Audio = 8
    Print = Hardcover Or Paperback
    All = Print Or Ebook Or Audio
End Enum

Public Enum Plain
    A = 1
    B = 2
End Enum

Module Program
    Sub Main()
        Dim formats As BookFormat = BookFormat.Hardcover Or BookFormat.Ebook
        Console.WriteLine("formats          = " & formats.ToString() & " (" & CByte(formats) & ")")

        ' Test with And, or with HasFlag.
        Console.WriteLine("has Ebook        = " & ((formats And BookFormat.Ebook) = BookFormat.Ebook))
        Console.WriteLine("HasFlag Audio    = " & formats.HasFlag(BookFormat.Audio))
        Console.WriteLine("has any of Print = " & ((formats And BookFormat.Print) <> BookFormat.None))
        Console.WriteLine("HasFlag Print    = " & formats.HasFlag(BookFormat.Print))   ' needs ALL bits of Print
        Console.WriteLine("HasFlag None     = " & formats.HasFlag(BookFormat.None))    ' always True

        ' Set, clear and toggle.
        formats = formats Or BookFormat.Audio
        formats = formats And Not BookFormat.Hardcover
        formats = formats Xor BookFormat.Paperback
        Console.WriteLine("after set/clear/toggle = " & formats.ToString())

        ' Not sets every bit outside the members too.
        Dim inverted = Not BookFormat.Hardcover
        Console.WriteLine("Not Hardcover    = " & CByte(inverted) & " -> " & inverted.ToString())
        Console.WriteLine("masked with All  = " & (inverted And BookFormat.All).ToString())

        Console.WriteLine("None.ToString()  = " & BookFormat.None.ToString())
        Console.WriteLine("All.ToString()   = " & BookFormat.All.ToString())
        Console.WriteLine("without <Flags>: " & CType(3, Plain).ToString())
        Console.WriteLine("Parse            = " & [Enum].Parse(Of BookFormat)("Hardcover, Audio").ToString())
    End Sub
End Module
formats          = Hardcover, Ebook (5)
has Ebook        = True
HasFlag Audio    = False
has any of Print = True
HasFlag Print    = False
HasFlag None     = True
after set/clear/toggle = Paperback, Ebook, Audio
Not Hardcover    = 254 -> 254
masked with All  = Paperback, Ebook, Audio
None.ToString()  = None
All.ToString()   = All
without <Flags>: 3
Parse            = Hardcover, Audio

The idioms in the example, and the traps beside them:

Task Idiom Trap

set a flag

value = value Or Flag

Or is idempotent; + is not (adding a flag that is already set changes other bits)

clear a flag

value = value And Not Flag

Not Flag alone is every other bit, including undefined ones (254 for a Byte here)

toggle

value = value Xor Flag

test one flag

(value And Flag) = Flag

comparing with <> 0 is also correct for single-bit flags

test any of several

(value And Mask) <> None

HasFlag(Mask) means all bits of the mask

test for "no flags"

value = None

HasFlag(None) is always True

  • Give the members bit values (1, 2, 4, 8, and so on, or 1 << n) and a None = 0 member. A member such as Print = Hardcover Or Paperback is a legitimate named combination, and the All member is conventional. The underlying type must be wide enough for the highest bit; Byte allows eight flags, Integer thirty-two, ULong sixty-four.

  • A value that is not a power of two inside a <Flags> enumeration makes the text ambiguous and the tests unreliable. The compiler does not check it. Here C = 3 equals A Or B:

Option Strict On

<Flags>
Public Enum Wrong
    A = 1
    B = 2
    C = 3        ' not a power of two: A Or B
    D = 4
End Enum

Module Program
    Sub Main()
        Dim w As Wrong = Wrong.A Or Wrong.B
        Console.WriteLine(w.ToString() & " " & CInt(w))              ' prints C: ambiguous
        Console.WriteLine(Wrong.C.HasFlag(Wrong.A))        ' True, although C is not "A"
    End Sub
End Module
C 3
True
  • Not on an enumeration inverts every bit of the underlying type, so mask the result with the set of valid flags: (Not value) And All.

  • The And, Or, Xor and Not operators on enumeration operands produce the enumeration type (the example assigns the results back to a BookFormat variable under Option Strict On). AndAlso and OrElse are for Boolean.

  • HasFlag reads better; the bitwise test is the traditional form and works in expressions where a method call is awkward. The .NET design guidelines recommend a plural name for flags enumerations and a singular one otherwise.

System.Enum methods

System.Enum supplies the reflection-style helpers. The generic overloads (.NET 5 and later) avoid the GetType(...) argument and the DirectCast on the way out, and they are the ones to use on .NET 10.

Option Strict On

Public Enum LoanState
    Active = 1
    Returned = 2
    Overdue = 3
End Enum

Module Program
    Sub Main()
        For Each name In [Enum].GetNames(Of LoanState)()
            Console.Write(name & " ")
        Next
        Console.WriteLine()

        Dim parsed As LoanState
        Console.WriteLine("TryParse overdue (ignoreCase): " & [Enum].TryParse("overdue", True, parsed) & " -> " & parsed.ToString())
        Console.WriteLine("TryParse Lost                : " & [Enum].TryParse("Lost", parsed))
        Console.WriteLine("TryParse ""2""                 : " & [Enum].TryParse("2", parsed) & " -> " & parsed.ToString())
        Console.WriteLine("TryParse ""99""                : " & [Enum].TryParse("99", parsed) & " -> " & parsed.ToString() &
                          " (defined: " & [Enum].IsDefined(parsed) & ")")

        Try
            Console.WriteLine([Enum].Parse(Of LoanState)("Lost").ToString())
        Catch ex As ArgumentException
            Console.WriteLine("Parse(""Lost"") -> ArgumentException")
        End Try

        Dim state = LoanState.Overdue
        Console.WriteLine(state.ToString("G") & " " & state.ToString("D") & " " & state.ToString("X") & " " & state.ToString("F"))
        Console.WriteLine([Enum].GetName(state) & " / " & NameOf(LoanState.Overdue))
    End Sub
End Module
Active Returned Overdue
TryParse overdue (ignoreCase): True -> Overdue
TryParse Lost                : False
TryParse "2"                 : True -> Returned
TryParse "99"                : True -> 99 (defined: False)
Parse("Lost") -> ArgumentException
Overdue 3 00000003 Overdue
Overdue / Overdue
Member Notes

[Enum].GetValues(Of T)(), [Enum].GetNames(Of T)()

every defined value or name, ordered by the underlying value; an alias appears once per value in GetValues

[Enum].GetName(value), value.ToString()

the name, or the number as text if the value is not defined ("99")

[Enum].IsDefined(value)

True when the value is exactly one member; a combination of <Flags> members is not defined

[Enum].Parse(Of T)(text)

throws ArgumentException for an unknown name and OverflowException for a number outside the underlying type (verified below)

[Enum].TryParse(text, ignoreCase, result)

returns False instead; also accepts numeric text, defined or not

[Enum].GetUnderlyingType(type)

the integral type

ToString("G"), "D", "X", "F"

name, decimal, hexadecimal, forced flags format

TryParse("99", ...) returned True in the example, and the resulting value is not a member. So parsing text from a user, a file or a web request needs an extra [Enum].IsDefined check (or a Select Case with a Case Else) before the value is trusted. The same is true of CType.

NameOf(LoanState.Overdue) is the compile-time name and is the right choice for log messages and argument names, because it survives renames.

Select Case, For and conversions

Select Case is the natural consumer of an enumeration. Cover every member and add a Case Else for values outside the set, because (as shown above) any integer can be converted to the type. A Case can also name a range of members:

Option Strict On

Public Enum LoanState
    Active = 1
    Returned = 2
    Overdue = 3
End Enum

Module Program
    Private Function Describe(state As LoanState) As String
        Select Case state
            Case LoanState.Active
                Return "on loan"
            Case LoanState.Returned
                Return "back on the shelf"
            Case LoanState.Overdue
                Return "past its due date"
            Case Else
                Return "unknown state " & CInt(state)
        End Select
    End Function

    Private Function NeedsAttention(state As LoanState) As Boolean
        Select Case state
            Case LoanState.Active To LoanState.Overdue
                Return state <> LoanState.Returned
            Case Else
                Return False
        End Select
    End Function

    Sub Main()
        For Each state In [Enum].GetValues(Of LoanState)()
            Console.WriteLine(state.ToString().PadRight(9) & Describe(state).PadRight(20) & NeedsAttention(state))
        Next
        Console.WriteLine(Describe(CType(42, LoanState)))

        ' An enumeration can control a For loop.
        For s As LoanState = LoanState.Active To LoanState.Overdue
            Console.Write(s.ToString() & " ")
        Next
        Console.WriteLine()
    End Sub
End Module
Active   on loan             True
Returned back on the shelf   False
Overdue  past its due date   True
unknown state 42
Active Returned Overdue

An enumeration can drive a For loop (the control variable has the enumeration type), which is convenient for contiguous members and wrong for sparse ones. [Enum].GetValues(Of T)() is the general way to iterate.

Under Option Strict On a conversion from an integer to an enumeration must be explicit, even for a literal; the other direction is a widening conversion:

Option Strict On

Public Enum LoanState
    Active = 1
    Returned = 2
End Enum

Module Program
    Sub Main()
        Dim a As LoanState = 2
        Dim n As Integer = LoanState.Active
        Dim m As LoanState = n
        Console.WriteLine(a & CStr(n) & m)
    End Sub
End Module
Program.vb(10,30): error BC30512: Option Strict On disallows implicit conversions from 'Integer' to 'LoanState'.
Program.vb(12,30): error BC30512: Option Strict On disallows implicit conversions from 'Integer' to 'LoanState'.

With Option Strict Off, which is the SDK default for a project that does not set OptionStrict, both lines would compile and a wrong number would pass silently. Arithmetic is permitted on enumerations, but it works on the underlying values and returns the underlying type: state + 1 is an Integer (the first example shows TypeName reporting Integer), and you convert it back with CType and check the result with IsDefined if it matters.

CType and CByte-style conversions to an enumeration with a narrow underlying type do check the range, because Visual Basic compiles integer conversions with overflow checking (see Operators and Expressions). [Enum].Parse does too:

Option Strict On

Public Enum Small As Byte
    A = 1
End Enum

Module Program
    Sub Main()
        Try
            Console.WriteLine([Enum].Parse(Of Small)("300").ToString())
        Catch ex As OverflowException
            Console.WriteLine("Parse: OverflowException")
        End Try
        Dim big As Integer = 300
        Try
            Console.WriteLine(CType(big, Small).ToString())
        Catch ex As OverflowException
            Console.WriteLine("CType: OverflowException")
        End Try
    End Sub
End Module
Parse: OverflowException
CType: OverflowException

A constant that cannot fit is rejected at compile time with BC30439, so the program above converts a variable.

Imports, qualification and aliases

An enumeration member is normally written with the type name: LoanState.Active. Imports can import the members of an enumeration so that they are used unqualified, and an alias gives the type a short name. Both take the full name of the type, starting at the global namespace; a namespace declared as Namespace Shelf.Core in a project whose root namespace is ShelfDocs is really ShelfDocs.Shelf.Core, so the example declares Namespace Global.Shelf.Core.

Option Strict On

Imports Shelf.Core.LoanState                 ' import the members of an enumeration
Imports Status = Shelf.Core.LoanState        ' or give the type a short alias

Namespace Global.Shelf.Core
    Public Enum LoanState
        Active = 1
        Returned = 2
        Overdue = 3
    End Enum
End Namespace

Module Program
    Sub Main()
        Dim first As Status = Active           ' member used without the enum name
        Dim last As Status = Overdue
        Console.WriteLine(first.ToString() & " .. " & last.ToString() & " (" & GetType(Status).FullName & ")")
    End Sub
End Module
Active .. Overdue (Shelf.Core.LoanState)

Importing the members of two enumerations that share a member name makes the unqualified name ambiguous; qualify it. Prefer qualification in shared code: a bare Active says little at the use site.

Microsoft.VisualBasic constants and enumerations

The Microsoft.VisualBasic namespace is imported into every project. It holds constants and enumerations that were part of VB6, and several of them are still the shortest way to write something:

Option Strict On

Module Program
    Sub Main()
        ' The same line break, three ways.
        Dim a As String = vbCrLf
        Dim b As String = ControlChars.CrLf
        Dim c As String = Environment.NewLine
        Console.WriteLine(a = b)
        Console.WriteLine(b = c)                         ' True on Windows only
        Console.WriteLine("vbLf = " & AscW(vbLf) & ", vbTab = " & AscW(vbTab) & ", vbNullString is Nothing: " & (vbNullString Is Nothing))
        Console.WriteLine("Hello" & vbTab & "World")

        ' Microsoft.VisualBasic enumerations
        Console.WriteLine(CompareMethod.Text.ToString() & " = " & CInt(CompareMethod.Text))
        Console.WriteLine(InStr(1, "Shelf", "SHELF", CompareMethod.Text) & " vs " & InStr(1, "Shelf", "SHELF", CompareMethod.Binary))
        Console.WriteLine(MsgBoxResult.Yes.ToString() & " = " & CInt(MsgBoxResult.Yes))
        Console.WriteLine((MsgBoxStyle.YesNo Or MsgBoxStyle.Question).ToString())
        Console.WriteLine(DayOfWeek.Monday.ToString() & " = " & CInt(DayOfWeek.Monday) & "; " & FirstDayOfWeek.Monday.ToString() & " = " & CInt(FirstDayOfWeek.Monday) & "; vbMonday = " & vbMonday)
        Console.WriteLine(vbTrue & " " & vbFalse & " " & CBool(vbTrue))
    End Sub
End Module
True
False
vbLf = 10, vbTab = 9, vbNullString is Nothing: True
Hello	World
Text = 1
1 vs 0
Yes = 6
YesNo, Question
Monday = 1; Monday = 2; vbMonday = 2
-1 0 True
Name Use today

vbCrLf, vbNewLine, vbLf, vbCr, vbTab, vbBack, vbNullChar, vbNullString

vbCrLf is always CR LF, vbNewLine and Environment.NewLine follow the platform (LF on Linux and macOS, CR LF on Windows); the ControlChars class has the same characters (CrLf is a String, Tab is a Char); vbNullString is Nothing, not an empty string

vbTrue, vbFalse

-1 and 0, kept for VB6 code; CBool(-1) is True

vbMonday, FirstDayOfWeek.Monday, DayOfWeek.Monday

three different numbers for a Monday (vbMonday is 2, DayOfWeek.Monday is 1); use DayOfWeek and DateTime in new code

CompareMethod.Binary, CompareMethod.Text

argument of InStr, Replace, Split, StrComp and the Like operator; the .NET equivalent is StringComparison

MsgBoxResult, MsgBoxStyle, MsgBoxResult.Yes

values for MsgBox; in .NET 5 and later the MsgBox and InputBox functions themselves are part of Microsoft.VisualBasic.Forms and need a Windows Forms project

vbObjectError

base number for Err.Raise custom errors in legacy code

A MsgBoxStyle value such as YesNo Or Question is a <Flags> combination; the box itself is covered on The My Namespace and the string functions on Strings and Text.

Enumerations with behaviour

An enumeration cannot declare methods, but extension methods and attributes add what is missing. This is the usual way to attach display text or logic to the shelf LoanState:

Option Strict On

Imports System.ComponentModel
Imports System.Runtime.CompilerServices

Public Enum LoanState
    <Description("Out with a reader")> Active = 1
    <Description("Back on the shelf")> Returned = 2
    <Description("Past the due date")> Overdue = 3
End Enum

Public Module LoanStateExtensions
    <Extension>
    Public Function IsOpen(state As LoanState) As Boolean
        Return state = LoanState.Active OrElse state = LoanState.Overdue
    End Function

    <Extension>
    Public Function Describe(state As LoanState) As String
        Dim field = GetType(LoanState).GetField(state.ToString())
        If field Is Nothing Then Return state.ToString()
        Dim found = CType(Attribute.GetCustomAttribute(field, GetType(DescriptionAttribute)), DescriptionAttribute)
        Return If(found Is Nothing, state.ToString(), found.Description)
    End Function
End Module

Module Program
    Sub Main()
        For Each state In [Enum].GetValues(Of LoanState)()
            Console.WriteLine(state.ToString().PadRight(9) & state.Describe().PadRight(20) & state.IsOpen())
        Next
        Console.WriteLine(CType(7, LoanState).Describe())
    End Sub
End Module
Active   Out with a reader   True
Returned Back on the shelf   False
Overdue  Past the due date   True
7

Reading an attribute from the member with reflection is a run-time cost per call; cache the results for hot paths. Reflection over attributes is also what makes this unsuitable for Native AOT trimming without annotations (see Attributes, Caller Info and Reflection). When the list of values has to be read from outside, prefer a lookup Dictionary(Of LoanState, String).

Notice that the example calls the variable found. Naming it attribute fails: the compiler rejects Dim attribute = Attribute.GetCustomAttribute(...) with BC30980 (Type of 'attribute' cannot be inferred from an expression containing 'attribute') and BC42104, because names are not case sensitive and the variable hides the class Attribute inside its own initializer. This was reproduced on the .NET 10 SDK while writing the example.

Enumerations and JSON

System.Text.Json serializes an enumeration as its number by default. Add JsonStringEnumConverter to the options, or the <JsonConverter(GetType(JsonStringEnumConverter))> attribute to the type, to write and read the names. Names make files readable and survive reordering; numbers are smaller and survive renaming. Pick one and keep it, because changing it breaks stored data. Whatever is chosen, validate what you read, as above.

Option Strict On

Imports System.Text.Json
Imports System.Text.Json.Serialization

Public Enum LoanState
    Active = 1
    Returned = 2
    Overdue = 3
End Enum

Public Class LoanDto
    Public Property Isbn As String = "9780441172719"
    Public Property State As LoanState = LoanState.Overdue
End Class

Module Program
    Sub Main()
        Dim dto As New LoanDto()
        Console.WriteLine(JsonSerializer.Serialize(dto))                   ' number by default

        Dim options As New JsonSerializerOptions()
        options.Converters.Add(New JsonStringEnumConverter())
        Console.WriteLine(JsonSerializer.Serialize(dto, options))           ' name with the converter

        Dim back = JsonSerializer.Deserialize(Of LoanDto)("{""Isbn"":""x"",""State"":""Returned""}", options)
        Console.WriteLine(back.State.ToString())
    End Sub
End Module
{"Isbn":"9780441172719","State":3}
{"Isbn":"9780441172719","State":"Overdue"}
Returned

The serializer is the subject of Serialization, JSON and XML.

Errors the compiler reports

Option Strict On

Public Enum Bad As Double
    A
End Enum

Public Enum Small As Byte
    Big = 300
End Enum

Public Enum Wrong
    A
    A
End Enum

Module Program
    Sub Main()
        Console.WriteLine(Wrong.A.ToString())
    End Sub
End Module
Program.vb(13,5): error BC31421: 'A' is already declared in this enum.
Program.vb(8,11): error BC30439: Constant expression not representable in type 'Byte'.
Program.vb(3,20): error BC30650: Enums must be declared as an integral type.
Code Cause

BC30650

Enum Bad As Double: the underlying type is not integral

BC31421

two members with the same name in one Enum

BC30439

Big = 300 in a Byte enumeration: the constant does not fit

BC30512

an integer assigned to an enumeration variable under Option Strict On

BC30451

a member used unqualified without an Imports of the enumeration

BC40056

an Imports of an enumeration whose name does not resolve (often the root-namespace trap above)

Differences from C#

Topic Visual Basic C#

Default underlying type

Integer

int

Member separator

one per line, no commas

commas

Literal to enumeration

explicit under Option Strict On (CType(2, LoanState))

the literal 0 converts implicitly, other numbers need a cast

Console.WriteLine(e), string concatenation

number for WriteLine(e) and &; name for interpolation and String.Concat

name, everywhere the value is boxed

Imports of members

Imports Ns.LoanState makes members unqualified

using static Ns.LoanState;

Arithmetic on an enumeration

allowed; state + 1 is an Integer

allowed; state + 1 keeps the enumeration type

Reserved word

write [Enum] for System.Enum; Enum is a keyword

Enum is not a keyword

Methods on the type

extension methods only

extension methods only

Older material you may meet

Tutorials written for VB6 and VBA treat constants and enumerations as the same thing and use the Global Const, vbCrLf, vbYes and MsgBox constants everywhere. In Visual Basic .NET:

  • Enum members are typed; vbYes is a constant of type MsgBoxResult in the Microsoft.VisualBasic namespace.

  • Global Const does not exist: use a Const in a Module, or a Shared ReadOnly field in a class.

  • MsgBox and InputBox exist in .NET 10 only through the Windows Forms-dependent Microsoft.VisualBasic.Forms library, and do nothing useful in a service or a console program on Linux. Use Console or a UI toolkit’s own dialogs.

  • Windows API constants were declared with Public Const WM_USER = &H400 in modules; today, prefer an Enum with an underlying type that matches the API and a <Flags> attribute where the API treats it as a bit set (see Native Interop: Declare and P/Invoke).