Updated on

C# gives several values the same result in two ways. In a switch statement we stack the case labels above one statement list and end it with a single break. In a switch expression we join the values into one arm with the or pattern: 20 or 22 or 24 => "It is a pleasant day".

The or form is the default choice. It fits on one line and needs nothing but the values, and the compiler reports any arm that can never be reached.

To download the source code for the video, visit our Patreon page (YouTube Patron tier).

The other techniques on this page exist for the cases or cannot cover: a range, a condition that has to run code, and a set of values that is only known at run time. Our article on switch-case statements covers the basics of switch itself.


VIDEO: Write Better Switch Statements in C#.


How Do We Give Multiple Switch Cases the Same Result?

In a switch statement we stack the case labels above a single statement list. Every label in the stack runs the same body, and one break ends the group.

C# makes this safe in a way C and JavaScript do not. Control cannot fall through from one switch section to the next, and every section has to end with a statement such as break, goto or return, so forgetting a break is a compiler error.

Only empty labels stack. As soon as a section contains code, the next case starts a new section, so a shared body is always deliberate. Microsoft calls it a deliberate design choice for “concisely handling multiple cases that share the same or interdependent logic”.

Reach for the statement form when the cases do work, such as writing to a log or changing state. When every case produces a value, the switch expression in the next section says the same thing in one line.

The quote comes from Microsoft’s selection statements reference. In the sample, where every example is a method of one static class, three temperatures share one result:

public static string SubMultipleCaseResults(int switchTemp)
{
    var result = string.Empty;

    switch (switchTemp)
    {
        case 20:
        case 22:
        case 24:
            result = "It is a pleasant day";
            break;
        case 30:
            result = "It is hot today";
            break;
        case 35:
            result = "It is very hot today";
            break;
        default:
            result = "No weather report";
            break;
    }

    return result;
}

The result string "It is a pleasant day" applies to multiple cases (20, 22, 24).

How Do We Write an Inline Switch in C#?

An inline switch is the switch expression, added in C# 8.0. It usually goes on the right-hand side of an assignment or a return and produces a value directly, so there is no variable to declare first and no break to write.

Each arm is a pattern, then =>, then the result. To give several values one result we join their patterns with or, and the whole group becomes a single arm.

Arms are evaluated in text order. The compiler reports an error when an arm lower down can never be chosen because an arm above it already matches everything that arm would, which catches a list already covered higher up before it runs.

The _ arm is the discard pattern. It matches anything and makes the expression exhaustive. Leave it out and an unmatched input throws a SwitchExpressionException at run time, though the compiler warns first in most cases.

Our article on pattern matching in C# goes through the other patterns an arm can use. The weather example, written as a switch expression, joins the three pleasant temperatures with or:

public static string SubMultipleCaseWithOrPattern(int tempValue) => tempValue switch
{
    20 or 22 or 24 => "It is a pleasant day",
    30 => "It is hot today",
    35 => "It is very hot today",
    > 35 => "Heat wave condition",
    _ => "No weather report."
};

Here, we are using just the disjunctive or pattern, but of course, if we need it, we can use the conjunctive and pattern as well.

How Do We Match Multiple Conditions in a Switch Case?

C# has two tools for this: patterns and case guards.

A range needs no guard. Relational patterns compare against a constant with <, >, <= or >=, and the and combinator joins two of them, so >= 50 and <= 150 is one arm with nothing extra around it. The right-hand side of a relational pattern has to be a constant expression, so a range whose bounds live in variables needs a guard.

A case guard covers everything a pattern cannot express. It is the when keyword after the pattern, followed by any boolean expression: a method call, a modulo test, a comparison against another variable. The arm is chosen only when the pattern matches and the guard is true.

So if the condition compares against a literal, write it as a pattern and let the compiler reason about coverage. If it has to run code, write it as a guard. Both forms work in a switch statement and a switch expression.

Here is a range check written with relational patterns:

public static string SubMultipleCaseWithRangePattern(int value)
{
    switch (value)
    {
        case >= 50 and <= 150:
            return "The value is between 50 and 150";
        case > 150 and <= 200:
            return "The value is between 150 and 200";
        default:
            return "The number is not within the given range.";
    }
}

In this case, each case label holds a whole range, written as two relational patterns joined with and. The second range starts at > 150, so 150 belongs to the first range only. The output depends on the value variable.

When the test cannot be written as a pattern, such as checking whether a number is even, we add a case guard with when:

public static string SubMultipleCaseWithGuard(int value)
{
    switch (value)
    {
        case >= 50 and <= 150 when value % 2 == 0:
            return "An even value between 50 and 150";
        case >= 50 and <= 150:
            return "An odd value between 50 and 150";
        default:
            return "The number is not within the given range.";
    }
}

The guard runs only after the pattern matches, so 100 returns "An even value between 50 and 150" and 101 moves on to the second label.

How Do We Match a Case Against a List of Values?

The or pattern needs compile-time constants. When the values are only known at run time, for example loaded from configuration or a database, no pattern can match them, and a case guard calling Contains is the right answer.

The two examples below do the same thing in two ways: a generic extension method when we want the syntax to read like SQL’s IN, and a plain collection when we already know the type.

For the first one, we can create our own extension method, and then use it in the switch-case expression. The method has to live in a static class, and in the sample it sits on the same static class as the examples:

public static bool In<T>(this T val, params T[] vals) => vals.Contains(val);

public static string SubMultipleCaseWithExtension(int tempValue)
{
    var result = tempValue switch
    {
        var x when x.In(20, 22, 24) => "It is a pleasant day",
        30 => "It is hot today",
        35 => "It is very hot today",
        _ => "No weather report.",
    };

    return result;
}

As we can see, we can use our custom In method for different types. Since C# 13, its parameter can also be params ReadOnlySpan<T>, which avoids allocating an array on every call.

We can also use an array or a list instead of the extension method if we already know the type we are going to work with:

private static readonly List<int> pleasantTemperatures = [20, 22, 24];

public static string SubMultipleCaseWithListValues(int tempValue)
{
    var newResult = tempValue switch
    {
        var x when pleasantTemperatures.Contains(x) => "It is a pleasant day",
        30 => "It is hot today",
        35 => "It is very hot today",
        _ => "No weather report",
    };

    return newResult;
}

The list is a static readonly field, so it is built once instead of on every call.

Which Multiple-Case Syntax Should We Use?

This table maps each kind of match to the syntax that handles it and the C# version it needs:

What we are matchingSyntaxFormSince
Several constant values, one resultcase 20: case 22: case 24: above one statement listswitch statementthe original switch
Several constant values, one result20 or 22 or 24 => …eitherC# 9.0
A range>= 50 and <= 150 => …eitherC# 9.0
Everything above or below a value> 35 => …eitherC# 9.0
Several types, one resultCar or Truck => …eitherC# 9.0
A condition no pattern can expressvar n when n % 2 == 0 => …eitherC# 7.0 in a statement, C# 8.0 as an arm
Values known only at run timevar x when values.Contains(x) => …eitherC# 7.0 in a statement, C# 8.0 as an arm
Anything not matched above_ => …switch expression onlyC# 8.0

Where a pattern and a guard can both express the same test, the pattern is the better choice, because the compiler reports a pattern arm that can never be reached and cannot see inside a when guard.

Conclusion

In this article, we have seen several simple techniques that we can use when we want to create switch statements with multiple cases that return the same result.

For the performance side of the choice, see how switch compares with if-else on performance.

Tested with .NET 10.0.10.