Advanced Control Flow with the C# Switch Statement
Switch is a multi-way selection statement that resolves an answer matching one or more values of a supplied variable. Its roots are a fundamental programming flow control statement.
The switch statement is often used as an alternative to an if-else construct. Switch becomes a logical choice when a single expression is tested against three or more conditions.
The statement uses a combination of different keywords: switch, case, default, break and when.
The switch structure starts with the 'switch; keyword. It looks like a function as the expression or match expression is wrapped in ():
switch( expression )
The statements are place within {}, so the statement looks like a function.
switch( expression ){ statement 1 statement 2 statement 3 statement n }
The case keyword defines each statement. It is followed by the value to match and a colon. It is also known as a label.
switch( expression ){ case 1: //do something case 2: //do something case 3: //do something case n: //do something }
break is another keyword used by the switch statement. It causes immediate exit from the switch.
If break is not included the value continues to be evaluated against the remaining statements. If it also matches any of those values they will also trigger execution flow.
This allows you to define a scenario where a value could potentially trigger multiple actions. This may be what you need, but be careful to use the switch statement without statement breaks.
I think this is one of the more confusing aspects of the switch statement. Other control flow constructs don't have this concept.
I have been bitten by not including break statements more than once in my days!
switch( expression ){ case 1: //do something break; case 2: //do something break; case 3: //do something break; case n: //do something break; }
Place break statements after your action or code that needs to execute.
Statements can be stacked, so more than one value can trigger an action.
switch( expression ){ case 1: case a: //do something break; case 2: case b: //do something break; case 3: case c: //do something break; case n: //do something break; }
Switch with Default Section
The following example shows a simple switch statement that has three switch sections. First two sections start with case label followed by constant value. If a value passed to the switch statement matches any case label constant the specified switch section is executed, otherwise the default section is executed. One switch section can contain more than one statements.
int i = 1; switch (i) { case 1: Console.WriteLine("One"); break; case 2: Console.WriteLine("Two"); break; default: Console.WriteLine("Other"); break; }
Output: "One"
Switch Without Default Section
If switch doesn't contain default section and no case label matches the value, no code is executed and control is transferred outside the switch statement.
int i = 3; switch (i) { case 1: Console.WriteLine("One"); break; case 2: Console.WriteLine("Two"); break; }
Nothing is performed since there are no cases matching the value.
Switch with Multiple Case Labels
Before each switch section can be more than one case labels. Such switch section is executed if any of the case labels matches the value.
int i = 1; switch (i) { case 1: case 2: Console.WriteLine("One or Two"); break; default: Console.WriteLine("Other"); break; }
Output: 'One or Two'
Switch with Enum
An enum is a special type that helps translate a related set of values, typically integer codes, into natural language. They not only make code easier for humans to read enums are useful for code editors to guide your coding.
The switch statement can use enum properties as values to match in expressions.
State state = State.Active; switch (state) { case State.Active: Console.WriteLine("Active"); break; case State.Inactive: Console.WriteLine("Inactive"); break; default: throw new Exception(String.Format("Unknown state: {0}", state)); }
Switch can be also used with enum values. Mostly it's good practice to include also default section and throw an exception for unexpected values.
Output: "Active"
Switch with String
Switch can also use string literals to determine which switch section should be executed.
string commandName = "start"; switch (commandName) { case "start": Console.WriteLine("Starting service..."); StartService(); break; case "stop": Console.WriteLine("Stopping service..."); StopService(); break; default: Console.WriteLine(String.Format("Unknown command: {0}", commandName)); break; }
Output: 'Starting service...'
Matching More Than Just Integers
Traditionally values were integers, but in C# you can also match other value types.
- a char
- a string
- a bool
- an integral value, such as an int or a long
- an enum value
In C# 6 and below case statements define mutually exclusive values. This means the statement order is not important.
The values must be constant values, like integers are specific strings.
This changed in C# 7. Pattern matching was added to the switch statement. This means order matters.
For example if a statement matches a pattern of an int and you define a case of 0 before the pattern a compiler exception is thrown.
switch( expression ){
case 1: //do something case int val: //do something case IEnumerable<object coll> subList when subList.Any(): //do something case IEnumerable<object coll> subList: //do something case null: //do something case n: //do something }
Type Pattern
The type pattern enables concise type evaluation and conversion. C# uses type patterns to test whether an expression can be converted to a specified type. If the expression can be converted C# casts it to a variable of that type.
Its syntax is:
case type varname
The rules to determine if a type pattern is matched are:
expr is an instance of the same type as type.
expr is an instance of a type that derives from type. In other words, the result of expr can be upcast to an instance of type.
expr has a compile-time type that is a base class of type, and expr has a runtime type that is type or is derived from type. The compile-time type of a variable is the variable's type as defined in its type declaration. The runtime type of a variable is the type of the instance that is assigned to that variable.
expr is an instance of a type that implements the type interface.
The default keyword defines a statement that matches when none of the values are matched. It is optional, but I use it often to ensure something is triggered.
Null does not match a type. You just need to match the null value:
case null:
Refining Pattern Matching with the when Statement
Because patterns can match many scenarios you may want to refine the case statement by adding a when clause. This works very similar to how the when statement works in SQL.
It follows this syntax:
case [type expression] when (conditional clause)
Adding when statements to a switch statement using patterns:
switch( expression ){ case int val when val < 5: //do something case int val when val >= 5 && val < 10: //do something default: //do something }
Summary
The switch statement is a fundamental control flow tool in almost every programming language. C# adds some helpful spins on the statement, making it more powerful.
You can perform traditional value matching to execute code or leverage pattern matching, filtering potential matches with the when keyword.