223

How can a switch expression be written to support multiple cases returning the same result?

With C# prior to version 8, a switch may be written like so:

var switchValue = 3;
var resultText = string.Empty;
switch (switchValue)
{
    case 1:
    case 2:
    case 3:
        resultText = "one to three";
        break;
    case 4:
        resultText = "four";
        break;
    case 5:
        resultText = "five";
        break;
    default:
        resultText = "unkown";
        break;
}

When I am using the C# version 8, with the expression syntax, it's like so:

var switchValue = 3;
var resultText = switchValue switch
{
    1 => "one to three",
    2 => "one to three",
    3 => "one to three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

So my question is: How to turn the cases 1, 2 and 3 to just one switch-case-arm so the value doesn't need to be repeated?

Update per suggestion from "Rufus L":

For my given example, this works.

var switchValue = 3;
var resultText = switchValue switch
{
    var x when (x >= 1 && x <= 3) => "one to three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

But it's not exactly what I want to accomplish. This is still only one case (with a filter condition), not multiple cases yielding to the same right-hand result.

Pang
  • 9,564
  • 146
  • 81
  • 122
huzle
  • 2,249
  • 2
  • 9
  • 8
  • What *do* you want to accomplish? Switch expressions aren't switch statements and fall through is explicitly forbidden. `when` is far more powerfull than fall-through anyway. You can use `Contains` with an array of values if you want. – Panagiotis Kanavos Jun 20 '19 at 10:10
  • Instead of using 3 or 4 or 11 case statements you can use a *single* `var x when listOfValues.Contains(x)` and handle as many cases as you want – Panagiotis Kanavos Jun 20 '19 at 10:13
  • 2
    I am not trying to fall-through over cases here. Like shown in the first code-block of my question case 1,2 and 3 execute the exact same right-arm code. And for the purpose of this question I have taken a very very simple example here. Imagine case 1,2,3 would evaluate very different and complex stuff like pattern matching with 'when' etc.. – huzle Jun 20 '19 at 10:25
  • the first code block *falls through* cases. That's what `case 1: case 2:` do - they are cases with empty blocks that fall through to the next – Panagiotis Kanavos Jun 20 '19 at 10:31
  • As for complex stuff, `when` can handle far more complex cases than simple fall through. The simple fall through can only handle equality checks against a hard-coded value. Pattern matching and `when` are the same in switch expressions. In fact, it's switch *statements* that cause problems with pattern matching. If you check functional languages [like F#](https://fsharpforfunandprofit.com/posts/match-expression/) you'll see that the primary use case (often the *only* one) is pattern matching expressions. – Panagiotis Kanavos Jun 20 '19 at 10:32
  • 1
    With fall through I have meant none-empty bodies on the cases. – huzle Jun 20 '19 at 10:45
  • So I think the proper way to go for my question is to use a expression in the 'when' clause. – huzle Jun 20 '19 at 10:46
  • Note that complex pattern matching can still be separated using functions, such as `CaseOne(x) => x is 1` (where `1` could be a more complex pattern match), then tested together in a single switch expression case like `var x when CaseOne(x) || CaseTwo(x) || CaseThree(x) => "one to three"` – nmclean Jun 22 '23 at 17:52

6 Answers6

245

C# 9 supports the following:

var switchValue = 3;
var resultText = switchValue switch
{
    1 or 2 or 3 => "one, two, or three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

Alternatively:

var switchValue = 3;
var resultText = switchValue switch
{
    >= 1 and <= 3 => "one, two, or three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

Source


For older versions of C#, I use the following extension method:

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

like this:

var switchValue = 3;
var resultText = switchValue switch
{
    var x when x.In(1, 2, 3) => "one, two, or three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

It's a little more concise than when x == 1 || x == 2 || x == 3 and has a more natural ordering than when new [] {1, 2, 3}.Contains(x).

Zev Spitz
  • 13,950
  • 6
  • 64
  • 136
  • 7
    It also allocates a new temporary Int Array which will cause GC pressure if this is inside a loop, and is about 100x slower than the 1 || 2 - but this probably doesn't matter for most code – Orion Edwards Jul 27 '20 at 23:10
151

I got around to installing it, but I have not found a way to specify multiple, separate case labels for a single switch section with the new syntax.

However, you can create a new variable that captures the value and then use a condition to represent the cases that should have the same result:

var resultText = switchValue switch
{
    var x when
        x == 1 ||
        x == 2 ||
        x == 3 => "one to three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

This is actually more concise if you have many cases to test, because you can test a range of values in one line:

var resultText = switchValue switch
{
    var x when x > 0 && x < 4 => "one to three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};
Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • 2
    Fall through is explicitly forbidden. The `when` clause is far more powerful than fall through anyway. Instead of writing 3 or 10 `case` statements you can use a single `var x when listOfValues.Contains(x)` – Panagiotis Kanavos Jun 20 '19 at 10:12
  • So I think the proper way to go for my question is to use a expression in the 'when' clause. So I am marking your answer as the correct cause it fits the requirement. Especially the first code block is formatted to have kind of one case per line. – huzle Jun 20 '19 at 10:51
  • 2
    @PanagiotisKanavos I just learned that while Fall Through is forbidden, this is not Fall Through. This is multiple matching, wich is allowed and compiles: https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA+ABATARgLABQGAzAATakDCpA3oaQ+WRjgAykDKAlgF4wAUwAJ4AXGKWABKeozoFGC0gGcA7lxFgAFqUHT5ihnIMGwAQyXjWIGccZmLpHNf22DGAOykAREoC2pgBsArwBuG1cAExgAM1MAVwCRZ1djD29gLgBzUPCFAF9wgpdSQjygA= – Christopher Dec 13 '19 at 21:40
  • @Christopher what you link to is fall-through in switch *statements*. The question asked how to do the same with switch expressions, which isn't allowed. – Panagiotis Kanavos Dec 16 '19 at 08:18
  • 3
    @PanagiotisKanavos Except it is **not** fallthrough, wich was my point. "Only one switch section in a switch statement executes. C# doesn't 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 () to another."" - https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch | This was multi-matching by multiple labels. Rather then a comma seperate list like in Python. – Christopher Dec 16 '19 at 15:19
  • 1
    @PanagiotisKanavos What might confuse you is that a lot of Langauge *implemented* Multiple Mathcing via Fall Through. But in C# those are as different as delegates and integers. Or interfaces and classes. – Christopher Dec 16 '19 at 15:20
  • @Christopher Are you arguing that I should re-phrase "fall through" to something else? I referred to the use of multiple `case` statements in a `switch` section as "fall through" (as is @PanagiotisKanavos ), but would be happy to use a different term if that's confusing. – Rufus L Dec 16 '19 at 16:51
  • 1
    @RufusL I am hoenestly not sure waht the proper term even is. All I do know for certain is that it is not fallthrough. I actually thought that myself, as I had memorized it being implemented via Falltrhough in my early Native C++ days. But the guy that corrected me was right: It is not fallthrough. It can not be fallthrough. It is something else. And I am unsure if the when clause is just a subset for pattern matching or a 3rd way altogether. – Christopher Dec 16 '19 at 18:04
  • Fair enough. I removed the term and just replaced it with multiple words. :) – Rufus L Dec 16 '19 at 18:12
  • Looks like they'll be tidying this up a bit with C# 9's [improved pattern matching](https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/#logical-patterns). – Ryan Sparks Jun 12 '20 at 08:30
  • The compiled result is very similar regardless of the used syntax https://shorturl.at/gzCO5 – Sz. Moncz Dec 18 '20 at 14:42
22

Sadly this appears to be a shortcoming in the switch-expression syntax in C# 8, relative to the switch-statement syntax. As other posters have suggested, if you are stuck with C# 8 then the rather clumsy var syntax is your only real option.

So you might have been hoping you could write:

switchValue switch {
    Type1 t1:
    Type2 t2:
    Type3 t3 => ResultA, // where the ResultX variables are placeholders for expressions.
    Type4 t4 => ResultB,
    Type5 t5 => ResultC
};

Instead you will need to write the rather awkward code below, with typename sprayed about:

switchValue switch {
    var x when x is Type1 || x is Type2 || x is Type 3 => ResultA,
    Type4 t4 => ResultB,
    Type5 t5 => ResultC
};

In such a simple example, you can probably live with this awkwardness. But more complicated example are much less liveable with. In fact my examples are actually a simplification of an example drawn from our own code base, where I was hoping to convert a switch-statement, with roughly six outcomes but over a dozen type-cases, into a switch-expression. And the result was clearly less readable than the switch-statement.

My view is that if the switch-expression needs shared outcomes and is more than a few lines long, then you are better off sticking to a switch-statement. Boo! It's more verbose but probably a kindness to your teammates.

ResultType tmp;
switch (switchValue) {
    case Type1 t1:
    case Type2 t2:
    case Type3 t3:
        tmp = ResultA;
        break;
    case Type4 t4:
        tmp = ResultB;
        break;
    case Type5 t5:
        tmp = ResultC;
        break;
};
return tmp;

UPDATE: However, as pointed out by others, if you can move to C# 9 or later, then you can make use of the or pattern syntax. Then you could write:

return switchValue switch {
    Type1 t1 or Type t2 or Type t3 => ResultA,
    Type4 t4 => ResultB,
    Type5 t5 => ResultC
    _ => default
};
sfkleach
  • 707
  • 6
  • 9
  • 4
    I know it's only an example, but it could be cleaned up a bit by directly returning from the cases rather than setting `tmp`. That way you can omit the `break`s too. – Ben Nov 13 '19 at 09:00
  • 2
    @Ben's comment is fair and, all things being equal, my own style too. But I have elected not to change my response because there was actually no return in the original post & I wanted to have the computation and the return cleanly separated, so the refactoring was clearer. – sfkleach Jan 07 '20 at 12:01
  • Since you are not using the matched variables, I'd recommend updating your switch statement example to use the discard pattern for the cases: `case Type1 _:`, `case Type2 _:` etc. – julealgon Jan 29 '20 at 00:59
  • @julealgon yes using discards is better style. In this case I was using the variable names because ResultA, ResultB, ResultC are intended to be placeholders for expressions that might reference t1, t2 ... t5. I have added a comment to make that clearer. – sfkleach Jan 30 '20 at 08:59
3

If you're still using C# 8 and so can't use the C# 9 1 or 2 or 3 => method as in the above answer, you can also write it in a way that avoids creating the extra variable x with the same value as switchValue as in the accepted answer (which is completely correct of course, I'm just suggesting an alternative as I don't like that extra x).

var list = new List<int> { 3, 4, 5 };

var resultText = switchValue switch
{
    1 => "one",
    2 => "two",
    _ when list.Contains(switchValue) => "three to five",
    _ => "unknown",
};

Obviously you can replace the condition after the _ when with whatever, like switchValue == 3 || switchValue == 4 || switchValue == 5; the point of the answer is to show the _ when bit.

I just think of _ when => meaning "else when" and _ => meaning "else".

jsabrooke
  • 405
  • 7
  • 12
1

I have done in this way :

public string GetValue(string name)
            {
                return name switch
                {
                    var x when name is "test1" || name is "test2" => "finch",
                    "test2" => somevalue,
                    _ => name
                };
            }
Nic
  • 439
  • 4
  • 14
0

If your switch type is a flag enum

[System.Flags]
public enum Values 
{
    One = 1, 
    Two = 2, 
    Three = 4,
    Four = 8,
    OneToThree = One | Two | Three
}

var resultText = switchValue switch
{
    var x when Values.OneToThree.HasFlag(x) => "one to three",
    Values.Four => "4",
    _ => "unknown",
};
pcchan
  • 21
  • 4