C# & .NET (C# 14, .NET 10 LTS) Module 2 – Types, variables and operators
Enums and flag enums
Define C# enums with explicit values, combine options in a [Flags] enum, parse input safely and handle the values no member names, such as (TrainClass)7.
What you will learn
- Define an enum with explicit underlying values, and convert between it and its number
- Combine, test and print the options of a [Flags] enum
- Validate parsed or cast enum values with Enum.TryParse and Enum.IsDefined
- Handle the values that no member names in a switch expression over an enum
- Choose how to store an enum, as a number or as a name, so that a new version reads it right
Before you start
What you’ll learn · Before you start
What you will learn
- Define an enum with explicit underlying values, and convert between it and its number
- Combine, test and print the options of a [Flags] enum
- Validate parsed or cast enum values with Enum.TryParse and Enum.IsDefined
- Handle the values that no member names in a switch expression over an enum
- Choose how to store an enum, as a number or as a name, so that a new version reads it right
Before you start
On this page
(TrainClass)7 compiles and runs in C#, although TrainClass names only four classes of ticket, numbered 1 to 4: the
program prints 7 and carries on. enum: A value type that gives names to constants of an integer type, int unless the declaration names another. Any number of that type converts to the enum, whether a member names it or not. give names to numbers, and every number of the enum’s integer type is a value
of it, named or not. A flags enum: An enum marked [Flags] whose members are powers of two, so each owns one bit and | combines several in one value. ToString names every set bit, such as Window, Lower. gives each option one bit, so one value can hold several options at once.
You will define enums with explicit numbers, combine options in flag enums, read an enum from text without letting a 7 through, and handle the values that no member names.
An enum names numbers, and every number of its type is a value
An enum declares a type whose values are named constants of an integer type, int unless
you choose another. That integer type is the enum’s underlying type: The integer type that an enum stores its values in: int unless the declaration names another, as in enum Berth : byte. It decides the size of a value and the numbers it can hold.: it sets the size
of each value and the numbers it can hold. Writing each number yourself, as a database or an API stores it, keeps the
numbers fixed when members are added later:
using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
// The class of a train ticket, with the number a booking system stores for each.
TrainClass chosen = TrainClass.AC3;
Console.WriteLine($"{chosen} is stored as {(int)chosen}");
Console.WriteLine($"(TrainClass)4 is {(TrainClass)4}");
// Any int converts to the enum, named or not.
TrainClass unknown = (TrainClass)7;
Console.WriteLine($"(TrainClass)7 prints as {unknown}, defined: {Enum.IsDefined(unknown)}");
TrainClass fresh = default;
Console.WriteLine($"default(TrainClass) prints as {fresh}, defined: {Enum.IsDefined(fresh)}");
Console.WriteLine($"members: {string.Join(", ", Enum.GetValues<TrainClass>().Select(c => $"{c} = {(int)c}"))}");
Console.WriteLine($"AC3 < AC2: {TrainClass.AC3 < TrainClass.AC2}, AC3 + 1 = {TrainClass.AC3 + 1}");
Console.WriteLine($"underlying type: {Enum.GetUnderlyingType(typeof(TrainClass)).Name}");
enum TrainClass
{
Sleeper = 1,
AC3 = 2,
AC2 = 3,
AC1 = 4,
} Output
AC3 is stored as 2 (TrainClass)4 is AC1 (TrainClass)7 prints as 7, defined: False default(TrainClass) prints as 0, defined: False members: Sleeper = 1, AC3 = 2, AC2 = 3, AC1 = 4 AC3 < AC2: True, AC3 + 1 = AC2 underlying type: Int32
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run basics.cs
(int)chosen gave 2, the number a booking system would store for AC3, and (TrainClass)4 gave AC1 back: explicit
casts convert both ways. (TrainClass)7 was accepted as well and printed as 7, because any int converts to the
enum, and only Enum.IsDefined said that no member has the value. The default is 0 although no member has it, and
Microsoft’s reference advises almost always defining a member with the value 0, such as None or Unknown, so that a
new variable means something. Underneath, enums stay numbers: AC3 < AC2 compared 2 with 3, and AC3 + 1 gave AC2.
Trusting a cast or a stored number
(TrainClass)code never fails, so a code of 7 read from a database becomes a TrainClass that no member names.
Check Enum.IsDefined(value) where numbers enter the program, or reject the value in a switch with a throwing arm.
Checkpoint
Check: Enums as named numbers
A flags enum gives every option its own bit
Some choices combine: a passenger may want a window and a lower berth. Flag enums give each option a power of two, so
each owns one bit and | combines any set of them in one value. The [Flags] attribute tells .NET that values are
combinations, and ToString names each option they hold:
using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
// Seat preferences: each option owns one bit, so any set of them fits in one value.
SeatPreference wanted = SeatPreference.Window | SeatPreference.Lower;
Console.WriteLine($"Window | Lower = {wanted} ({(int)wanted}, bits {Bits(wanted)})");
Console.WriteLine($"HasFlag(Lower) {wanted.HasFlag(SeatPreference.Lower)}, HasFlag(Aisle) {wanted.HasFlag(SeatPreference.Aisle)}");
wanted |= SeatPreference.Side;
Console.WriteLine($"after |= Side: {wanted} (bits {Bits(wanted)})");
wanted &= ~SeatPreference.Window;
Console.WriteLine($"after &= ~Window: {wanted} (bits {Bits(wanted)})");
Console.WriteLine($"None = {SeatPreference.None}, Lower | Upper = {SeatPreference.Lower | SeatPreference.Upper}");
Console.WriteLine($"(SeatPreference)13 = {(SeatPreference)13}");
Console.WriteLine($"(SeatPreference)64 = {(SeatPreference)64}, (SeatPreference)69 = {(SeatPreference)69}");
// The same numbers in an enum without [Flags] print as numbers.
Console.WriteLine($"without [Flags], 5 = {(PlainPreference)5}");
// A [Flags] enum of .NET itself: Unix file permissions, 755 in octal.
UnixFileMode mode = (UnixFileMode)Convert.ToInt32("755", 8);
Console.WriteLine($"755 = {mode}");
static string Bits(SeatPreference p) => Convert.ToString((int)p, 2).PadLeft(5, '0');
[Flags]
enum SeatPreference
{
None = 0,
Window = 1,
Aisle = 2,
Lower = 4,
Upper = 8,
Side = 16,
AnyBerth = Lower | Upper,
}
enum PlainPreference
{
None = 0,
Window = 1,
Aisle = 2,
Lower = 4,
Upper = 8,
Side = 16,
} Output
Window | Lower = Window, Lower (5, bits 00101) HasFlag(Lower) True, HasFlag(Aisle) False after |= Side: Window, Lower, Side (bits 10101) after &= ~Window: Lower, Side (bits 10100) None = None, Lower | Upper = AnyBerth (SeatPreference)13 = Window, AnyBerth (SeatPreference)64 = 64, (SeatPreference)69 = 69 without [Flags], 5 = 5 755 = OtherExecute, OtherRead, GroupExecute, GroupRead, UserExecute, UserWrite, UserRead
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run flags.cs
Window | Lower printed as Window, Lower, the value 5, bits 00101. HasFlag tested one option, |= added Side and
&= ~ removed Window, the same bit operators that the operators lesson used on plain integers. None names zero, and a member can name
a combination: AnyBerth is Lower | Upper. Step through how each value became text:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- 5 arrives. flags.cs prints Window | Lower, the value 5, bits 00101. No member has the value 5, so the bits it holds are named one by one.
- Two names. Bit 1 is Window and bit 4 is Lower, and no bit is left over, so the text is both names, lowest first: Window, Lower.
- A combination member. AnyBerth is declared as Lower | Upper, 12, so the value Lower | Upper equals a member, and its one name is printed: AnyBerth.
- 13. 13 is Window, Lower and Upper. The run printed Window, AnyBerth: the combination member took bits 4 and 8, and Window took bit 1.
- A bit with no name. 69 is 64 + 4 + 1, and no member has bit 64. With a bit left over that no member names, the whole value prints as a number: 69, and 64 alone as 64.
- Without [Flags]. PlainPreference has the same members without [Flags]. There the value 5 printed as 5: without the attribute, only a value that one member equals gets a name.
A bit that no member names makes the whole value print as a number, 64 and 69, and without [Flags] the same 5
printed as 5. .NET uses the same design for its own options: UnixFileMode printed 755 as the names of the seven
permission bits it sets.
Checkpoint
Check: Flags enums and how they print
Enum.TryParse reads names, and numbers too
Text from a form or a file has to become an enum value. Enum.TryParse reads a member’s name, ignoring case when you
ask, and returns false for a name it does not know:
using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
// Eight texts a form might send, read with Enum.TryParse, ignoring case.
string[] inputs = ["AC3", "ac3", " AC2 ", "Sleeper, AC1", "7", "-1", "AC4", ""];
Console.WriteLine($"{"input",-16} {"TryParse",-15} IsDefined");
foreach (string input in inputs)
{
bool parsed = Enum.TryParse(input, ignoreCase: true, out TrainClass value);
string defined = parsed ? Enum.IsDefined(value).ToString() : "-";
Console.WriteLine($"{$"\"{input}\"",-16} {$"{parsed}, {value}",-15} {defined}");
}
bool exactCase = Enum.TryParse("ac3", out TrainClass _);
Console.WriteLine($"without ignoreCase, \"ac3\" gives {exactCase}");
enum TrainClass
{
Sleeper = 1,
AC3 = 2,
AC2 = 3,
AC1 = 4,
} Output
input TryParse IsDefined "AC3" True, AC3 True "ac3" True, AC3 True " AC2 " True, AC2 True "Sleeper, AC1" True, 5 False "7" True, 7 False "-1" True, -1 False "AC4" False, 0 - "" False, 0 - without ignoreCase, "ac3" gives False
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run parse.cs
ac3 and " AC2 ", with its spaces, were read. Three results need a second look: 7 and -1 were accepted as numbers,
and Sleeper, AC1 as a list combined into 5, although TrainClass is not a flags enum. Microsoft’s documentation of
TryParse describes both: a list is combined with a bitwise OR, and text that is a number becomes that number even
when no member has it. Follow
four of the inputs through the two checks:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- "ac3". TryParse with ignoreCase reads ac3 as AC3, and IsDefined confirms that a member has its value, 2. The value can be used.
- "7". TryParse accepts the digits 7 and returns the value 7, which no member names. Only IsDefined catches it and sends it to the rejection.
- "Sleeper, AC1". A comma-separated list is read as Sleeper | AC1, the value 5, although TrainClass is not a flags enum. IsDefined rejects 5 as well.
- "AC4". AC4 is a name that no member has, so TryParse itself returns false. Its result is 0, the default, which is no member either.
- Two checks. TryParse says whether the text could be read; IsDefined says whether the value is one of the members. Input from outside the program needs both.
Key idea
An enum value is a number that has a name when a member has that value. A cast, a stored number and parsed text can all hold a number without a name, so check each value where it enters the program.
Checkpoint
Check: Reading an enum from text
Validating enum input, with every trap of TryParse reproduced Pro: opens with a Pro, Premium or Ultimate pass
Flags need more care than an ordinary enum when text becomes a value. The runs below show what TryParse and
IsDefined say about seven texts, then build a parser for typed seat preferences that accepts names only.
In this part:
- What TryParse and IsDefined say about a flags enum
- A parser for typed preferences: names only, any case, every problem reported
1 runnable example (C#) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Switch expressions over enums: CS8509, CS8524 and the discard arm Pro: opens with a Pro, Premium or Ultimate pass
A switch expression must give a value for every input, and an enum has more inputs than names. The session below
builds five versions of one switch over TrainClass and shows which version lets the compiler find a member you
forgot.
In this part:
- Five versions of one switch, built and run
- Choosing the version for the enum you have
2 runnable examples (C#, Python) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Enums over time: stored numbers, JSON and members of their own Pro: opens with a Pro, Premium or Ultimate pass
An enum outlives the code that declares it: its numbers are stored in databases, sent as JSON and read by the next version. The runs below show what a careless new version does to stored numbers, how JSON writes enums, and how C# 14 gives an enum members of its own.
In this part:
- Numbers that a new version reads differently
- Members of their own, with C# 14 extension members
2 runnable examples (C#) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Practise: add a member and predict the text
Give bit 64 a name
Copy flags.cs to your computer, add Accessible = 64 to SeatPreference and predict what (SeatPreference)69
prints now. Run it with dotnet run flags.cs, then remove [Flags] and predict once more.
Then test yourself on enum values, flags and parsing; the questions follow your answers, and a wrong choice tells you what it missed:
Practice that adapts to you
Practise this lesson
9 questions about 9 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · What is a [Flags] enum, and how is it different from an ordinary enum?
An ordinary enum names values that exclude each other, such as the one class of a ticket. A [Flags] enum names
options that combine: each member is a power of two, so each owns one bit, and | stores any set of them in one value,
such as Window | Lower, which is 5. The attribute changes how a value prints: ToString names every option it holds,
Window, Lower, where an ordinary enum would print 5. Test an option with HasFlag or (value & option) != 0, and
define None = 0 for the empty set.
-
Fresher to mid
An API takes a TrainClass in its JSON body, and a client sends the number 7, which no member has. What does your service do with it today, where should the value be checked, and how?
-
Mid to senior
A switch expression over an enum has an arm for every member, yet the build warns CS8524. A teammate wants to add _ => throw new InvalidOperationException(). Explain the warning, what the discard changes, and which fix still tells you when someone adds a member.
-
Mid to senior
A release adds ChairCar to the TrainClass enum, right after Sleeper and without explicit numbers. The next morning, existing bookings show the wrong class of travel. Explain what happened, how you would repair the data, and how you would store enums so that this cannot happen again.
Key takeaways
- An enum gives names to the numbers of an integer type, and any number of that type is a value of the enum, so
(TrainClass)7compiles and prints 7. - Flag enums give every option one bit,
|combines options, and[Flags]makesToStringname each one. Enum.TryParsereads names, numbers and comma-separated lists, so parsed input still needs a check that its value has a name.- Check enum values where they enter the program, because a cast, a stored number and parsed text never check them.
Terms in this lesson
- enum
- A value type that gives names to constants of an integer type, int unless the declaration names another. Any number of that type converts to the enum, whether a member names it or not.
- flags enum
- An enum marked [Flags] whose members are powers of two, so each owns one bit and | combines several in one value. ToString names every set bit, such as Window, Lower.
- underlying type
- The integer type that an enum stores its values in: int unless the declaration names another, as in enum Berth : byte. It decides the size of a value and the numbers it can hold.
References
- Enumeration types (Microsoft Learn)
- Enum class (Microsoft Learn)
- Enum.TryParse method (Microsoft Learn)
- FlagsAttribute class (guidelines for flags enums) (Microsoft Learn)
- Enumeration format strings (Microsoft Learn)
- UnixFileMode enum (Microsoft Learn)
- Resolve pattern matching errors and warnings (CS8509, CS8524) (Microsoft Learn)
- The if and switch statements (Microsoft Learn)
- Compiler options: errors and warnings (WarningsAsErrors, NoWarn) (Microsoft Learn)
- JsonStringEnumConverter class (Microsoft Learn)
- Extension member declarations (Microsoft Learn)
- The Java Language Specification, Java SE 25: Enum Classes (Oracle)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress