C# & .NET (C# 14, .NET 10 LTS) Module 3 – Control flow and methods
if, switch statements and switch expressions
Choose between if and else if, a switch statement and a switch expression in C#, with case guards, no fall-through and the warnings for a missing case.
What you will learn
- Write if and else if chains whose tests run in the right order
- Replace nested checks with guard clauses that return early
- Write switch statements with several labels, case guards and goto case
- Rewrite a value mapping as a switch expression with relational patterns
- Predict the warning and the exception of a switch expression that misses a value
Before you start
What you’ll learn · Before you start
What you will learn
- Write if and else if chains whose tests run in the right order
- Replace nested checks with guard clauses that return early
- Write switch statements with several labels, case guards and goto case
- Rewrite a value mapping as a switch expression with relational patterns
- Predict the warning and the exception of a switch expression that misses a value
Before you start
On this page
An income of 950,000 falls in one of five bands, and the program that taxes it must pick exactly one rate. Written
with if and else if, that choice takes four tests in the right order; written as a switch expression, it takes
five arms that read like the table of bands itself.
You will step through the tests C# makes, replace nested checks with guard clauses, write switch statements that never fall through, and see the warning and the exception that a switch expression gives when it misses a value. Each program is shown with the output it printed.
if and else if test their conditions in order, and the first true one wins
An if statement runs its block only when its condition is true, and its else block runs otherwise. Put another
if in the else and you get a chain, else if. C# tests the conditions from the top, one at a time, stops at the
first one that is true, runs that block and skips everything after it. This lesson uses an illustrative tax scheme
with made-up bands, not any country’s law:
| Income | Rate |
|---|---|
| up to 250,000 | 0 % |
| 250,001 to 600,000 | 5 % |
| 600,001 to 1,100,000 | 10 % |
| 1,100,001 to 2,000,000 | 20 % |
| above 2,000,000 | 30 % |
Step through the chain for an income of 950,000:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The first test. The chain starts at the top with an income of 950,000. 950,000 <= 250,000 is false, so the first block is skipped and the next test runs.
- The second test. Reaching this test already means the income is above 250,000, so it really asks whether the income is from 250,001 to 600,000. 950,000 is not.
- The third test. 950,000 <= 1,100,000 is true. This is the band: above 600,000, because both tests above failed, and at most 1,100,000.
- Its block runs. rate = 10 runs, and the chain is finished. bands.cs printed 10 % for 950,000.
- Nothing below runs. The fourth test and the else block are skipped without being evaluated: once one condition is true, C# leaves the chain.
- A boundary value. For an income of exactly 250,000 the first test is already true, because <= includes its boundary, so it pays 0 %. 250,001 fails that test and pays 5 %.
950,000 failed the first two tests and passed the third, so the rate is 10 % and the fourth test never ran. Because
a test runs only when every test above it was false, the second test, income <= 600_000, really means “above
250,000 and at most 600,000”: the order of the tests carries half of each condition. The program below runs the
chain on six incomes, the boundaries included, next to a version that tests in the wrong order:
// The rate of an illustrative tax scheme (made-up bands, not any country's law), picked with if and else if.
// RateFor tests the bands from the lowest up. RateWrongOrder tests "more than" from the lowest up, which is a bug.
int[] incomes = [180_000, 250_000, 250_001, 950_000, 1_100_000, 2_400_000];
foreach (int income in incomes)
{
Console.WriteLine($"{income,9:N0}: {RateFor(income),2} % wrong order: {RateWrongOrder(income),2} %");
}
static int RateFor(int income)
{
int rate;
if (income <= 250_000)
{
rate = 0;
}
else if (income <= 600_000)
{
rate = 5;
}
else if (income <= 1_100_000)
{
rate = 10;
}
else if (income <= 2_000_000)
{
rate = 20;
}
else
{
rate = 30;
}
return rate;
}
static int RateWrongOrder(int income)
{
int rate = 0;
if (income > 250_000)
{
rate = 5;
}
else if (income > 600_000)
{
rate = 10;
}
else if (income > 1_100_000)
{
rate = 20;
}
else if (income > 2_000_000)
{
rate = 30;
}
return rate;
} Output
180,000: 0 % wrong order: 0 % 250,000: 0 % wrong order: 0 % 250,001: 5 % wrong order: 5 % 950,000: 10 % wrong order: 5 % 1,100,000: 10 % wrong order: 5 % 2,400,000: 30 % wrong order: 5 %
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run bands.cs
<= puts each boundary in the lower band: 250,000 pays 0 % and 250,001 pays 5 %, as the first lines show. Decide
for every boundary which band it belongs to before you write its test, because a boundary on the wrong side charges
real people the wrong amount.
Testing the widest condition first
RateWrongOrder starts with if (income > 250_000), which is true for every income above 250,000, so the tests
below it never run and 2,400,000 pays 5 %. Test from the lowest band up with <=, as RateFor does, or from the
highest band down with >.
Checkpoint
Check: if and else if chains
Guard clauses return early, so the main path stays flat
Every nested if pushes the code that does the real work one level deeper and its error message further from the
test that produces it. A guard clause: A check at the start of a method that returns or throws at once when an input is bad, so the code after it can assume the input is good and stays unindented. turns that around: it tests for one bad input at
the top of a method and leaves at once with return or throw, so the code below it can assume that case is ruled
out. Here are the same three checks of an order, nested and written as guard clauses:
// The same three checks written nested, and as guard clauses that return early. Both give the same answers.
(string? Item, int Quantity)[] orders = [(null, 2), ("", 2), ("pen", 0), ("pen", 3)];
foreach (var (item, quantity) in orders)
{
string nested = Nested(item, quantity);
string guarded = Guarded(item, quantity);
string input = $"({(item is null ? "null" : $"\"{item}\"")}, {quantity})";
Console.WriteLine($"{input,-12} {guarded,-28} same answer: {nested == guarded}");
}
static string Nested(string? item, int quantity)
{
if (item is not null)
{
if (item.Length > 0)
{
if (quantity >= 1)
{
return $"{quantity} x {item}";
}
else
{
return "quantity must be at least 1";
}
}
else
{
return "item is empty";
}
}
else
{
return "item is missing";
}
}
static string Guarded(string? item, int quantity)
{
if (item is null)
{
return "item is missing";
}
if (item.Length == 0)
{
return "item is empty";
}
if (quantity < 1)
{
return "quantity must be at least 1";
}
return $"{quantity} x {item}";
} Output
(null, 2) item is missing same answer: True
("", 2) item is empty same answer: True
("pen", 0) quantity must be at least 1 same answer: True
("pen", 3) 3 x pen same answer: True
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run guards.cs
Both methods gave the same answer for all four orders. Guarded reads from top to bottom: each test sits on the line
above its message, and the line that does the work, return $"{quantity} x {item}";, is not indented at all. In
Nested the message for a missing item is the method’s last return, 20 lines below the test that leads to it, and
the working line sits three blocks deep.
A guard clause also documents the method: its first lines list everything the method refuses. Return a value for an outcome the caller expects, such as an item that is out of stock, and throw an exception for a call that should never have been made, such as a negative quantity from code you control.
Checkpoint
Check: Guard clauses
A switch statement runs one section and never falls through
When one value decides between several actions, a switch statement lists them as sections. Each section has one or
more case labels and ends with break, return, throw or goto. C# evaluates the value once, compares it with
the labels from top to bottom, and runs the section of the first label that matches. default is taken only when no
label matches, wherever it is written. This cart program sends eight commands through one switch:
// A switch statement runs the one section whose case label matches. Each section ends with break, return, throw
// or goto, so control never falls through into the next section.
string[] commands = ["add", "ADD", "list", "remove", "rm", "rm", "help", "quit"];
int items = 0;
foreach (string command in commands)
{
switch (command.ToLowerInvariant())
{
case "add":
items++;
Console.WriteLine($"{command}: added, {items} in the cart");
break;
case "remove" when items == 0:
case "rm" when items == 0:
Console.WriteLine($"{command}: the cart is already empty");
break;
case "remove":
case "rm":
items--;
Console.WriteLine($"{command}: removed, {items} in the cart");
break;
default:
Console.WriteLine($"{command}: unknown command");
break;
case "help":
Console.WriteLine($"{command}: the commands are add, remove (or rm) and list");
goto case "list";
case "list":
Console.WriteLine($"{command}: {items} in the cart");
break;
}
} Output
add: added, 1 in the cart ADD: added, 2 in the cart list: 2 in the cart remove: removed, 1 in the cart rm: removed, 0 in the cart rm: the cart is already empty help: the commands are add, remove (or rm) and list help: 0 in the cart quit: unknown command
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run commands.cs
ADD matched case "add": because the switch looks at command.ToLowerInvariant(). Two labels can share a section,
as remove and rm do, and a when clause after a label adds a condition to it, a
case guard: A when clause after a case pattern, as in case "rm" when items == 0: the label or arm is chosen only when the pattern matches and the condition is true.. The second rm found the cart empty, so the guarded label above matched first
and nothing was removed. Order matters here as it did for if: written below the unguarded labels, the guarded ones
could never be reached, and the compiler would stop with an error. Step through how help finds its section:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- One value. The switch evaluates command.ToLowerInvariant() once. For the command help that gives "help", and every label is compared with that one value.
- Not add. "help" is not "add", so the add section is skipped and the next label is compared.
- Not remove or rm. None of the four remove and rm labels matches. A guarded label matches only when both its constant and its when condition do, so the guards make no difference here.
- default waits. default stands in the middle of the switch, but it is taken only when no other label matches, wherever it is written. The comparison goes on.
- help matches. case "help": matches, so its section runs and prints the line that lists the commands.
- goto case. The section ends with goto case "list"; instead of break, so control jumps into the list section on purpose. It is the only way one section continues into another.
- break leaves. The list section prints "help: 0 in the cart" and ends with break, which leaves the switch. The loop moves on to the last command, quit, which only default matches.
goto case "list"; is the one way to continue into another section, and it says so in the code. What C# never does
is fall-through: Letting the statements of one switch section run on into the next section. C# forbids it with error CS0163; goto case jumps to another section on purpose.: let a section without an ending run on into the next one. This
program leaves out the break of its add section, and the build stops:
// The add section has no break, so it would run on into the list section. C# refuses to build it.
string command = args.Length > 0 ? args[0] : "add";
int items = 0;
switch (command)
{
case "add":
items++;
Console.WriteLine("added");
case "list":
Console.WriteLine($"{items} in the cart");
break;
} from session import run
# fallthrough leaves out the break of its add section. It is built once.
run("""
cd fallthrough
!dotnet build -v q
""") Output
$ cd fallthrough
$ dotnet build -v q
fallthrough/Program.cs(6,5): error CS0163: Control cannot fall through from one case label ('case "add":') to another [fallthrough/App.csproj]
Build FAILED.
fallthrough/Program.cs(6,5): error CS0163: Control cannot fall through from one case label ('case "add":') to another [fallthrough/App.csproj]
0 Warning(s)
1 Error(s)
Time Elapsed …
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 fallthrough_session.py
Error CS0163 names the label whose section has no ending, case "add":. Add break; to stop there, or
goto case "list"; when running the list section next is really what you want.
Checkpoint
Check: Switch statements
A switch expression maps one value to one result
A switch statement chooses what to do; a switch expression: An expression such as income switch { <= 250_000 => 0, _ => 30 }: it tests its arms in the order written and evaluates to the result of the first arm whose pattern matches. chooses a value. The
input comes first, then switch and a list of arms in braces, separated by commas. Each arm is a pattern, an optional
when guard, => and the result. The arms are tested in the order they are written, and the first arm whose pattern
matches gives the value of the whole expression. The five bands become five arms, plus one for an income that is not
valid at all:
// The same made-up bands as a switch expression with relational patterns, compared with the if version.
int[] incomes = [180_000, 250_000, 250_001, 950_000, 1_100_000, 2_400_000];
foreach (int income in incomes)
{
Console.WriteLine($"{income,9:N0}: {Rate(income),2} %");
}
int compared = 0, differ = 0;
for (int income = 0; income <= 3_000_000; income += 500)
{
compared++;
if (Rate(income) != RateIf(income))
{
differ++;
}
}
Console.WriteLine($"{compared:N0} incomes from 0 to 3,000,000 compared with the if version: {differ} differ");
try
{
Rate(-5);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Rate(-5) threw {e.GetType().Name}");
}
static int Rate(int income) => income switch
{
< 0 => throw new ArgumentOutOfRangeException(nameof(income), income, "An income cannot be negative."),
<= 250_000 => 0,
<= 600_000 => 5,
<= 1_100_000 => 10,
<= 2_000_000 => 20,
_ => 30,
};
static int RateIf(int income)
{
if (income <= 250_000)
{
return 0;
}
if (income <= 600_000)
{
return 5;
}
if (income <= 1_100_000)
{
return 10;
}
if (income <= 2_000_000)
{
return 20;
}
return 30;
} Output
180,000: 0 % 250,000: 0 % 250,001: 5 % 950,000: 10 % 1,100,000: 10 % 2,400,000: 30 % 6,001 incomes from 0 to 3,000,000 compared with the if version: 0 differ Rate(-5) threw ArgumentOutOfRangeException
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run bands_switch.cs
< 0 and <= 250_000 are relational pattern: A pattern that compares the input with a constant using <, >, <= or >=, such as <= 250_000. Relational patterns were added in C# 9.: they compare the input with a
constant. Each arm’s pattern only has to state the top of its band, because an income below it was taken by an arm
above, exactly as in the if chain. The last arm, _, is the discard pattern: The pattern _, which matches any value. As the last arm of a switch expression it catches every input the other arms left., which
matches any value, so every income above 2,000,000 gets 30 %. An arm’s result can be a throw expression: a negative
income belongs to no band, and Rate(-5) threw ArgumentOutOfRangeException.
The program then compared the switch expression with an if version on 6,001 incomes, every 500 from 0 to
3,000,000, and they differed on none. The switch expression is shorter, it is one expression, so it can be the whole
body of a method after =>, and the compiler can check it in a way it cannot check the chain, as the next section
shows.
Key idea
A switch statement chooses between actions and a switch expression chooses a value. Both test their cases from the top and take the first that matches, so the order of the cases carries meaning.
Checkpoint
Check: Switch expressions
A switch expression that misses a value warns at build time and throws at run time
When no arm matches, a switch expression has no value to give, so the runtime throws
System.Runtime.CompilerServices.SwitchExpressionException. The compiler warns you first whenever it can find an
input that no arm covers. The first program below lost the arm for the top band; the second names every member of an
enum:
// The bands as a switch expression that forgot the top band, above 2,000,000.
int[] incomes = [950_000, 2_400_000];
foreach (int income in incomes)
{
Console.WriteLine($"{income}: {RateFor(income)} %");
}
static int RateFor(int income) => income switch
{
<= 250_000 => 0,
<= 600_000 => 5,
<= 1_100_000 => 10,
<= 2_000_000 => 20,
}; // The switch expression names every member of Status, yet a Status variable can hold other values too.
Console.WriteLine(Label(Status.Paid));
Console.WriteLine(Label((Status)7));
static string Label(Status status) => status switch
{
Status.New => "new",
Status.Paid => "paid",
Status.Shipped => "shipped",
};
enum Status
{
New,
Paid,
Shipped,
} from session import run
# missing-arm is a switch expression without the top band, and status names every member of an enum. Each is built,
# then run.
run("""
cd missing-arm
dotnet build -v q
!dotnet run --no-build
cd ../status
dotnet build -v q
!dotnet run --no-build
""") Output
$ cd missing-arm
$ dotnet build -v q
missing-arm/Program.cs(8,42): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '2000001' is not covered. [missing-arm/App.csproj]
Build succeeded.
missing-arm/Program.cs(8,42): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '2000001' is not covered. [missing-arm/App.csproj]
1 Warning(s)
0 Error(s)
Time Elapsed …
$ dotnet run --no-build
950000: 10 %
Unhandled exception. System.Runtime.CompilerServices.SwitchExpressionException: Non-exhaustive switch expression failed to match its input.
Unmatched value was 2400000.
at <PrivateImplementationDetails>.ThrowSwitchExpressionException(Object unmatchedValue)
at Program.<<Main>$>g__RateFor|0_0(Int32 income) in missing-arm/Program.cs:line 8
at Program.<Main>$(String[] args) in missing-arm/Program.cs:line 5
[exit status 134]
$ cd ../status
$ dotnet build -v q
status/Program.cs(5,46): warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(Status)3' is not covered. [status/App.csproj]
Build succeeded.
status/Program.cs(5,46): warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(Status)3' is not covered. [status/App.csproj]
1 Warning(s)
0 Error(s)
Time Elapsed …
$ dotnet run --no-build
paid
Unhandled exception. System.Runtime.CompilerServices.SwitchExpressionException: Non-exhaustive switch expression failed to match its input.
Unmatched value was 7.
at <PrivateImplementationDetails>.ThrowSwitchExpressionException(Object unmatchedValue)
at Program.<<Main>$>g__Label|0_0(Status status) in status/Program.cs:line 5
at Program.<Main>$(String[] args) in status/Program.cs:line 3
[exit status 134]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 missing_session.py
Warning CS8509 even names a value the arms miss, 2000001. A warning does not stop a build, so the program ran, printed
the rate for 950,000 and then threw for 2,400,000, with the unmatched value in the message. The enum version shows a
quieter gap. Its arms name all three members of Status, yet the compiler warns with CS8524, because an enum
variable can hold any value of its underlying int, and (Status)7 threw at run time.
Close the gap with a final arm that covers everything left, as bands_switch.cs did with _, and decide what that
arm should do: give a sensible value, or throw an exception that names the bad input. Above all, read these warnings
as bugs: each one is a value that would crash a running program.
Shipping a build with CS8509
A switch expression with warning CS8509 builds and passes every test that only feeds it values it covers, then
throws SwitchExpressionException in production on the first input it misses. Fix the warning where it appears:
add the missing arms or a final _ arm.
Checkpoint
Check: Missing cases, warnings and exceptions
if, a switch statement or a switch expression: choosing by what the code decides
All three choose one path, so choose by what the decision looks like:
| Write | When | In this lesson |
|---|---|---|
if and else if |
the conditions test different values, or are not one value compared with constants | the order checks of Guarded |
| a switch statement | one value picks one of several actions, each with its own statements | the cart commands |
| a switch expression | one value maps to one result | the rate of each band |
A rule of thumb
If every branch ends by assigning the same variable or returning a value, the code is a mapping: write it as a switch expression. Keep a switch statement for branches that do different work.
If you know Java, two differences matter. Java’s older colon form, case L:, falls through to the next label when a
break is missing, while C# refuses to build such a section. Java’s arrow form, case L ->, never falls through and
is close to a C# switch expression; but a Java switch expression that is not exhaustive does not compile, while C#
builds it with a warning and throws at run time.
Checkpoint
Check: Choosing if, a statement or an expression
Practise: map class codes to fare multipliers
fares.cs checks a method that you write. Its comments give five class codes with made-up multipliers, and nine
checks call your method with known codes, codes in lower case or with spaces around them, an unknown code and null.
The method you start from only throws, which fails all nine checks:
// Practice: give Multiplier a switch expression as its body, then run this file until all nine checks pass.
// Five class codes, with multipliers made up for this exercise: SL 1.0, CC 2.0, 3A 2.5, 2A 3.5 and 1A 6.0.
// A code matches whatever its case and the spaces around it; any other code, and null, throws an
// ArgumentException whose message names the code.
(string? Code, decimal? Expected)[] checks =
[
("SL", 1.0m), ("CC", 2.0m), ("3A", 2.5m), ("2A", 3.5m), ("1A", 6.0m),
("sl", 1.0m), (" 3a ", 2.5m), ("XX", null), (null, null),
];
int passed = 0;
foreach (var (code, expected) in checks)
{
string shown = code is null ? "null" : $"\"{code}\"";
string verdict;
try
{
decimal got = Multiplier(code);
verdict = got == expected ? "ok" : $"FAIL, returned {got} instead of {(expected is null ? "throwing" : expected)}";
}
catch (ArgumentException e) when (expected is null)
{
verdict = code is null || e.Message.Contains(code) ? "ok" : "FAIL, the message does not name the code";
}
catch (Exception e)
{
verdict = $"FAIL, {e.GetType().Name}";
}
if (verdict == "ok")
{
passed++;
}
Console.WriteLine($"Multiplier({shown}): {verdict}");
}
Console.WriteLine($"{passed} of {checks.Length} checks pass");
return passed == checks.Length ? 0 : 1;
static decimal Multiplier(string? code)
{
throw new NotImplementedException("Write a switch expression here.");
} Output (exit status 1)
Multiplier("SL"): FAIL, NotImplementedException
Multiplier("CC"): FAIL, NotImplementedException
Multiplier("3A"): FAIL, NotImplementedException
Multiplier("2A"): FAIL, NotImplementedException
Multiplier("1A"): FAIL, NotImplementedException
Multiplier("sl"): FAIL, NotImplementedException
Multiplier(" 3a "): FAIL, NotImplementedException
Multiplier("XX"): FAIL, NotImplementedException
Multiplier(null): FAIL, NotImplementedException
0 of 9 checks pass
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run fares.cs
Write Multiplier as a switch expression
Copy fares.cs to your computer and replace the body of Multiplier with a switch expression on
code?.Trim().ToUpperInvariant(). Make the last arm throw an ArgumentException whose message names the code, and
run the file with dotnet run fares.cs until it prints 9 of 9 checks pass.
Then test yourself below: one question at a time, chosen from the parts of this lesson you have answered least well, with a reason under every choice.
Practice that adapts to you
Practise this lesson
12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · How does a C# switch statement differ from a switch expression?
A switch statement chooses which statements to run: each section has case labels and must end with break,
return, throw or goto, because C# never lets one section fall through into the next. A switch expression,
added in C# 8, produces a value: income switch { <= 250_000 => 0, _ => 30 } is an expression, so it can be
assigned, returned or used as a method body. Both test their cases from the top and take the first that matches, and
both accept patterns and when guards. The compiler warns (CS8509) when a switch expression can miss an input, and at
run time such a miss throws SwitchExpressionException, while a switch statement without a matching case simply
does nothing. Use the statement for actions and the expression for mappings.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
You are porting Java billing code in which a switch on the plan relies on fall-through: case PREMIUM adds a premium fee and, with no break, runs on into case STANDARD, which adds the base fee and breaks. How do you write this in C#, and what do you check before you ship it?
-
Fresher to mid
A checkout method decides the delivery charge with if statements nested five deep on membership, cart total, distance and a coupon. Every change breaks a case that nobody tested. How would you restructure it, and how would you show that the new version charges exactly what the old one did?
Key takeaways
- An
ifandelse ifchain tests its conditions from the top and runs only the first block whose condition is true, so each test may rely on every test above it having failed. - Guard clauses test for bad input at the top of a method and return or throw at once, which keeps the main path unindented and each message next to its test.
- A switch statement runs the section of the first matching label, every section ends with
break,return,throworgoto, and falling through is a compile-time error, CS0163. - A switch expression maps a value to a result with arms tested in order; relational patterns compare with
constants, and
_matches everything left. - A switch expression that can miss a value builds with warning CS8509 or CS8524 and throws
SwitchExpressionExceptionwhen the missing value arrives.
Terms in this lesson
- case guard
- A when clause after a case pattern, as in case "rm" when items == 0: the label or arm is chosen only when the pattern matches and the condition is true.
- discard pattern
- The pattern _, which matches any value. As the last arm of a switch expression it catches every input the other arms left.
- fall-through
- Letting the statements of one switch section run on into the next section. C# forbids it with error CS0163; goto case jumps to another section on purpose.
- guard clause
- A check at the start of a method that returns or throws at once when an input is bad, so the code after it can assume the input is good and stays unindented.
- relational pattern
- A pattern that compares the input with a constant using <, >, <= or >=, such as <= 250_000. Relational patterns were added in C# 9.
- switch expression
- An expression such as income switch { <= 250_000 => 0, _ => 30 }: it tests its arms in the order written and evaluates to the result of the first arm whose pattern matches.
References
- if and switch statements: select a code path to execute (Microsoft Learn)
- switch expression: evaluate a pattern match expression (Microsoft Learn)
- Pattern matching: relational, logical and discard patterns (Microsoft Learn)
- Jump statements: break, continue, return and goto (Microsoft Learn)
- SwitchExpressionException class (Microsoft Learn)
- The history of C# (switch expressions in C# 8, relational patterns in C# 9) (Microsoft Learn)
- Java language guide: switch expressions and statements (Oracle)
Report a problem with this lesson
Kept only in this browser. Your Learn progress