switch
switch
is a selection statement that chooses a single switch section to execute from a list of candidates based on a pattern match with the match expression.
using System;
public class Example
{
public static void Main()
{
int caseSwitch = 1;
switch (caseSwitch)
{
case 1:
Console.WriteLine("Case 1");
break;
case 2:
Console.WriteLine("Case 2");
break;
default:
Console.WriteLine("Default case");
break;
}
}
}
// The example displays the following output:
// Case 1
The switch
statement is often used as an alternative to an if-else construct if a single expression is tested against three or more conditions. For example, the following switch
statement determines whether a variable of type Color
has one of three values:
using System;
public enum Color { Red, Green, Blue }
public class Example
{
public static void Main()
{
Color c = (Color) (new Random()).Next(0, 3);
switch (c)
{
case Color.Red:
Console.WriteLine("The color is red");
break;
case Color.Green:
Console.WriteLine("The color is green");
break;
case Color.Blue:
Console.WriteLine("The color is blue");
break;
default:
Console.WriteLine("The color is unknown.");
break;
}
}
}
It is equivalent to the following example that uses an if
-else
construct.
using System;
public enum Color { Red, Green, Blue }
public class Example
{
public static void Main()
{
Color c = (Color) (new Random()).Next(0, 3);
if (c == Color.Red)
Console.WriteLine("The color is red");
else if (c == Color.Green)
Console.WriteLine("The color is green");
else if (c == Color.Blue)
Console.WriteLine("The color is blue");
else
Console.WriteLine("The color is unknown.");
}
}
// The example displays the following output:
// The color is red
The match expression
The match expression provides the value to match against the patterns in case
labels. Its syntax is:
switch (expr)
In C# 6, the match expression must be an expression that returns a value of the following types:
- a char.
- a string.
- a bool.
- an integral value, such as an int or a long.
- an enum value.
Starting with C# 7.0, the match expression can be any non-null expression.
The switch section
A switch
statement includes one or more switch sections. Each switch section contains one or more case labels (either a case or default label) followed by one or more statements. The switch
statement may include at most one default label placed in any switch section. The following example shows a simple switch
statement that has three switch sections, each containing two statements. The second switch section contains the case 2:
and case 3:
labels.
A switch
statement can include any number of switch sections, and each section can have one or more case labels, as shown in the following example. However, no two case labels may contain the same expression.
using System;
public class Example
{
public static void Main()
{
Random rnd = new Random();
int caseSwitch = rnd.Next(1,4);
switch (caseSwitch)
{
case 1:
Console.WriteLine("Case 1");
break;
case 2:
case 3:
Console.WriteLine($"Case {caseSwitch}");
break;
default:
Console.WriteLine($"An unexpected value ({caseSwitch})");
break;
}
}
}
// The example displays output like the following:
// Case 1
Only one switch section in a switch statement executes. C# does not allow execution to continue from one switch section to the next. Because of this, the following code generates a compiler error, CS0163: "Control cannot fall through from one case label (
switch (caseSwitch)
{
// The following switch section causes an error.
case 1:
Console.WriteLine("Case 1...");
// Add a break or other jump statement here.
case 2:
Console.WriteLine("... and/or Case 2");
break;
}
This requirement is usually met by explicitly exiting the switch section by using a break, goto, or return statement. However, the following code is also valid, because it ensures that program control cannot fall through to the default
switch section.
switch (caseSwitch)
{
// The following switch section causes an error.
case 1:
Console.WriteLine("Case 1...");
break;
case 2:
case 3:
Console.WriteLine("... and/or Case 2");
break;
case 4:
while (true)
Console.WriteLine("Endless looping. . . .");
default:
Console.WriteLine("Default value...");
break;
}
Execution of the statement list in the switch section with a case label that matches the match expression begins with the first statement and proceeds through the statement list, typically until a jump statement, such as a break
, goto case
, goto label
, return
, or throw
, is reached. At that point, control is transferred outside the switch
statement or to another case label. A goto
statement, if it is used, must transfer control to a constant label. This restriction is necessary, since attempting to transfer control to a non-constant label can have undesirable side-effects, such transferring control to an unintended location in code or creating an endless loop.
Case labels
Each case label specifies a pattern to compare to the match expression (the caseSwitch
variable in the previous examples). If they match, control is transferred to the switch section that contains the first matching case label. If no case label pattern matches the match expression, control is transferred to the section with the default
case label, if there is one. If there is no default
case, no statements in any switch section are executed, and control is transferred outside the switch
statement.
For information on the switch
statement and pattern matching, see the Pattern matching with the switch
statement section.
Because C# 6 supports only the constant pattern and does not allow the repetition of constant values, case labels define mutually exclusive values, and only one pattern can match the match expression. As a result, the order in which case
statements appear is unimportant.
In C# 7.0, however, because other patterns are supported, case labels need not define mutually exclusive values, and multiple patterns can match the match expression. Because only the statements in the switch section that contains the first matching pattern are executed, the order in which case
statements appear is now important. If C# detects a switch section whose case statement or statements are equivalent to or are subsets of previous statements, it generates a compiler error, CS8120, "The switch case has already been handled by a previous case."
The following example illustrates a switch
statement that uses a variety of non-mutually exclusive patterns. If you move the case 0:
switch section so that it is no longer the first section in the switch
statement, C# generates a compiler error because an integer whose value is zero is a subset of all integers, which is the pattern defined by the case int val
statement.
using System;
using System.Collections.Generic;
using System.Linq;
public class Example
{
public static void Main()
{
var values = new List<object>();
for (int ctr = 0; ctr <= 7; ctr++) {
if (ctr == 2)
values.Add(DiceLibrary.Roll2());
else if (ctr == 4)
values.Add(DiceLibrary.Pass());
else
values.Add(DiceLibrary.Roll());
}
Console.WriteLine($"The sum of { values.Count } die is { DiceLibrary.DiceSum(values) }");
}
}
public static class DiceLibrary
{
// Random number generator to simulate dice rolls.
static Random rnd = new Random();
// Roll a single die.
public static int Roll()
{
return rnd.Next(1, 7);
}
// Roll two dice.
public static List<object> Roll2()
{
var rolls = new List<object>();
rolls.Add(Roll());
rolls.Add(Roll());
return rolls;
}
// Calculate the sum of n dice rolls.
public static int DiceSum(IEnumerable<object> values)
{
var sum = 0;
foreach (var item in values)
{
switch (item)
{
// A single zero value.
case 0:
break;
// A single value.
case int val:
sum += val;
break;
// A non-empty collection.
case IEnumerable<object> subList when subList.Any():
sum += DiceSum(subList);
break;
// An empty collection.
case IEnumerable<object> subList:
break;
// A null reference.
case null:
break;
// A value that is neither an integer nor a collection.
default:
throw new InvalidOperationException("unknown item type");
}
}
return sum;
}
public static object Pass()
{
if (rnd.Next(0, 2) == 0)
return null;
else
return new List<object>();
}
}
You can correct this issue and eliminate the compiler warning in one of two ways:
-
By changing the order of the switch sections.
-
By using a when clause in the
case
label.
The default
case
The default
case specifies the switch section to execute if the match expression does not match any other case
label. If a default
case is not present and the match expression does not match any other case
label, program flow falls through the switch
statement.
The default
case can appear in any order in the switch
statement. Regardless of its order in the source code, it is always evaluated last, after all case
labels have been evaluated.
Pattern matching with the switch
statement
Each case
statement defines a pattern that, if it matches the match expression, causes its containing switch section to be executed. All versions of C# support the constant pattern. The remaining patterns are supported beginning with C# 7.0.
Constant pattern
The constant pattern tests whether the match expression equals a specified constant. Its syntax is:
case constant:
where constant is the value to test for. constant can be any of the following constant expressions:
-
A bool literal, either
true
orfalse
. - Any integral constant, such as an int, a long, or a byte.
-
The name of a declared
const
variable. - An enumeration constant.
- A char literal.
- A string literal.
The constant expression is evaluated as follows:
-
If expr and constant are integral types, the C# equality operator determines whether the expression returns
true
(that is, whetherexpr == constant
). -
Otherwise, the value of the expression is determined by a call to the static Object.Equals(expr, constant) method.
The following example uses the constant pattern to determine whether a particular date is a weekend, the first day of the work week, the last day of the work week, or the middle of the work week. It evaluates the DateTime.DayOfWeek property of the current day against the members of the DayOfWeek enumeration.
using System;
class Program
{
static void Main()
{
switch (DateTime.Now.DayOfWeek)
{
case DayOfWeek.Sunday:
case DayOfWeek.Saturday:
Console.WriteLine("The weekend");
break;
case DayOfWeek.Monday:
Console.WriteLine("The first day of the work week.");
break;
case DayOfWeek.Friday:
Console.WriteLine("The last day of the work week.");
break;
default:
Console.WriteLine("The middle of the work week.");
break;
}
}
}
// The example displays output like the following:
// The middle of the work week.
The following example uses the constant pattern to handle user input in a console application that simulates an automatic coffee machine.
using System;
class Example
{
static void Main()
{
Console.WriteLine("Coffee sizes: 1=small 2=medium 3=large");
Console.Write("Please enter your selection: ");
string str = Console.ReadLine();
int cost = 0;
// Because of the goto statements in cases 2 and 3, the base cost of 25
// cents is added to the additional cost for the medium and large sizes.
switch (str)
{
case "1":
case "small":
cost += 25;
break;
case "2":
case "medium":
cost += 25;
goto case "1";
case "3":
case "large":
cost += 50;
goto case "1";
default:
Console.WriteLine("Invalid selection. Please select 1, 2, or 3.");
break;
}
if (cost != 0)
{
Console.WriteLine("Please insert {0} cents.", cost);
}
Console.WriteLine("Thank you for your business.");
}
}
// The example displays output like the following:
// Coffee sizes: 1=small 2=medium 3=large
// Please enter your selection: 2
// Please insert 50 cents.
// Thank you for your business.
Type pattern
The type pattern enables concise type evaluation and conversion. When used with the switch
statement to perform pattern matching, it tests whether an expression can be converted to a specified type and, if it can be, casts it to a variable of that type. Its syntax is:
case type varname
where type is the name of the type to which the result of expr is to be converted, and varname is the object to which the result of expr is converted if the match succeeds.
The case
expression is true
if any of the following is true:
-
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.
If the case expression is true, varname is definitely assigned and has local scope within the switch section only.
Note that null
does not match a type. To match a null
, you use the following case
label:
case null:
The following example uses the type pattern to provide information about various kinds of collection types.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
class Example
{
static void Main(string[] args)
{
int[] values = { 2, 4, 6, 8, 10 };
ShowCollectionInformation(values);
var names = new List<string>();
names.AddRange( new string[] { "Adam", "Abigail", "Bertrand", "Bridgette" } );
ShowCollectionInformation(names);
List<int> numbers = null;
ShowCollectionInformation(numbers);
}
private static void ShowCollectionInformation(object coll)
{
switch (coll)
{
case Array arr:
Console.WriteLine($"An array with {arr.Length} elements.");
break;
case IEnumerable<int> ieInt:
Console.WriteLine($"Average: {ieInt.Average(s => s)}");
break;
case IList list:
Console.WriteLine($"{list.Count} items");
break;
case IEnumerable ie:
string result = "";
foreach (var item in ie)
result += "${e} ";
Console.WriteLine(result);
break;
case null:
// Do nothing for a null.
break;
default:
Console.WriteLine($"A instance of type {coll.GetType().Name}");
break;
}
}
}
// The example displays the following output:
// An array with 5 elements.
// 4 items
Without pattern matching, this code might be written as follows. The use of type pattern matching produces more compact, readable code by eliminating the need to test whether the result of a conversion is a null
or to perform repeated casts.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
class Example
{
static void Main(string[] args)
{
int[] values = { 2, 4, 6, 8, 10 };
ShowCollectionInformation(values);
var names = new List<string>();
names.AddRange( new string[] { "Adam", "Abigail", "Bertrand", "Bridgette" } );
ShowCollectionInformation(names);
List<int> numbers = null;
ShowCollectionInformation(numbers);
}
private static void ShowCollectionInformation(object coll)
{
if (coll is Array) {
Array arr = (Array) coll;
Console.WriteLine($"An array with {arr.Length} elements.");
}
else if (coll is IEnumerable<int>) {
IEnumerable<int> ieInt = (IEnumerable<int>) coll;
Console.WriteLine($"Average: {ieInt.Average(s => s)}");
}
else if (coll is IList) {
IList list = (IList) coll;
Console.WriteLine($"{list.Count} items");
}
else if (coll is IEnumerable) {
IEnumerable ie = (IEnumerable) coll;
string result = "";
foreach (var item in ie)
result += "${e} ";
Console.WriteLine(result);
}
else if (coll == null) {
// Do nothing.
}
else {
Console.WriteLine($"An instance of type {coll.GetType().Name}");
}
}
}
// The example displays the following output:
// An array with 5 elements.
// 4 items
The case
statement and the when
clause
Starting with C# 7.0, because case statements need not be mutually exclusive, you can add a when
clause to specify an additional condition that must be satisfied for the case statement to evaluate to true. The when
clause can be any expression that returns a Boolean value.
The following example defines a base Shape
class, a Rectangle
class that derives from Shape
, and a Square
class that derives from Rectangle
. It uses the when
clause to ensure that the ShowShapeInfo
treats a Rectangle
object that has been assigned equal lengths and widths as a Square
even if it has not been instantiated as a Square
object. The method does not attempt to display information either about an object that is null
or a shape whose area is zero.
using System;
public abstract class Shape
{
public abstract double Area { get; }
public abstract double Circumference { get; }
}
public class Rectangle : Shape
{
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}
public double Length { get; set; }
public double Width { get; set; }
public override double Area
{
get { return Math.Round(Length * Width,2); }
}
public override double Circumference
{
get { return (Length + Width) * 2; }
}
}
public class Square : Rectangle
{
public Square(double side) : base(side, side)
{
Side = side;
}
public double Side { get; set; }
}
public class Circle : Shape
{
public Circle(double radius)
{
Radius = radius;
}
public double Radius { get; set; }
public override double Circumference
{
get { return 2 * Math.PI * Radius; }
}
public override double Area
{
get { return Math.PI * Math.Pow(Radius, 2); }
}
}
public class Example
{
public static void Main()
{
Shape sh = null;
Shape[] shapes = { new Square(10), new Rectangle(5, 7),
sh, new Square(0), new Rectangle(8, 8),
new Circle(3) };
foreach (var shape in shapes)
ShowShapeInfo(shape);
}
private static void ShowShapeInfo(Shape sh)
{
switch (sh)
{
// Note that this code never evaluates to true.
case Shape shape when shape == null:
Console.WriteLine($"An uninitialized shape (shape == null)");
break;
case null:
Console.WriteLine($"An uninitialized shape");
break;
case Shape shape when sh.Area == 0:
Console.WriteLine($"The shape: {sh.GetType().Name} with no dimensions");
break;
case Square sq when sh.Area > 0:
Console.WriteLine("Information about square:");
Console.WriteLine($" Length of a side: {sq.Side}");
Console.WriteLine($" Area: {sq.Area}");
break;
case Rectangle r when r.Length == r.Width && r.Area > 0:
Console.WriteLine("Information about square rectangle:");
Console.WriteLine($" Length of a side: {r.Length}");
Console.WriteLine($" Area: {r.Area}");
break;
case Rectangle r when sh.Area > 0:
Console.WriteLine("Information about rectangle:");
Console.WriteLine($" Dimensions: {r.Length} x {r.Width}");
Console.WriteLine($" Area: {r.Area}");
break;
case Shape shape when sh != null:
Console.WriteLine($"A {sh.GetType().Name} shape");
break;
default:
Console.WriteLine($"The {nameof(sh)} variable does not represent a Shape.");
break;
}
}
}
// The example displays the following output:
// Information about square:
// Length of a side: 10
// Area: 100
// Information about rectangle:
// Dimensions: 5 x 7
// Area: 35
// An uninitialized shape
// The shape: Square with no dimensions
// Information about square rectangle:
// Length of a side: 8
// Area: 64
// A Circle shape
Note that the
when
clause in the example that attempts to test whether aShape
object isnull
does not execute. The correct type pattern to test for anull
iscase null:
.