27

Switch expressions were introduced in C# 8. There's plenty of places in codebases, which may be rewritten in this new style.

For example, I have some code, which is used for parsing packets from a stream of bytes:

switch (command)
{
    case Command.C1:
        return new P1();
    case Command.C2:
        return new P2();
    default:
        stream.Position++;
        return null;
}

The problem is - it can't be converted to a switch expression like

return command switch
{
    Command.C1 => new P1(),
    Command.C3 => new P2(),
    _ => { stream.Position++; return null; }
};

The first thing that got in my mind was to use a Func<>, which compiles:

return command switch
{
    Command.C1 => new P1(),
    Command.C3 => new P2(),
    _ => new Func<AbstractPacket>(() => { stream.Position++; return null; })()
};

F# already allows code with multiple statements in each branch:

match command with
| Command.C1 -> Some(P1() :> AbstractPacket)
| Command.C2 -> Some(P2() :> AbstractPacket)
| _ ->
    stream.Position <- stream.Position + 1
    None

Now I'm stuck using switch-statements, but is there any option to write this as a switch-expression without any weird hacks?

Kit
  • 20,354
  • 4
  • 60
  • 103
JL0PD
  • 3,698
  • 2
  • 15
  • 23
  • 1
    Nitpick: "expression" doesn't have that many S's in it. – David Conrad Jan 24 '20 at 04:32
  • 2
    so suggest an edit? – John Lord Jan 24 '20 at 05:12
  • Personally, I would convert to a `switch` statement in such cases. That said, C# LDM is discussing some options to allow statements inside `switch` expressions and possibly more generally ("expression blocks"). Here's the proposal we most recently reviewed: https://github.com/dotnet/csharplang/issues/3086 – Julien Couvreur Jan 24 '20 at 05:13
  • 2
    The switch expression **is not meant to** replace the switch statement. It serves a different purpose. It will not handle multiple statements, and it shouldn't, because you're just switching on a value to get the right **single** expression evaluated. So you're entirely right, getting the switch expression to handle multiple statements is a pain, in pretty much the same way as using a hammer to split a plank in two is. – Lasse V. Karlsen Jan 24 '20 at 08:57
  • My comment above aside, what are you actually asking about here? Do you have a usecase where you have to use a switch expression but must have statements? Or is this just an academic question, like "how can I subvert a switch expression to take the place of a switch statement"? Basically, which problem are you trying to solve? – Lasse V. Karlsen Jan 24 '20 at 08:58
  • @LasseV.Karlsen, I'm just refactoring code and searching for solution with best readability – JL0PD Jan 24 '20 at 09:32
  • @JL0PD it's expression, singular. You can't have multiple expressions in an `if` or assignment expression. David Conrad's comment is actually the answer – Panagiotis Kanavos Jan 24 '20 at 10:04
  • @JohnLord David Conrad's comment is actually the answer - an expression is *one* thing, not multiple. It can be one function call, or one logical expression or a composite of other expressions etc. – Panagiotis Kanavos Jan 24 '20 at 10:05
  • @JL0PD switch *expressions* aren't a new version of switch statements, or a way to make them prettier. They are a unique new construct. In fact, F# *doesn't allow multiple expressions* in each branch either - those are individual *functions* – Panagiotis Kanavos Jan 24 '20 at 10:07
  • @JL0PD you can use local functions to combine multiple statements into one function that can be used in any expression, including switch expressions – Panagiotis Kanavos Jan 24 '20 at 10:14
  • @JL0PD BTW the ternary operator, `?:` works the same way too. You can't put multiple statements there either. – Panagiotis Kanavos Jan 24 '20 at 10:15
  • As a C# dev, my expectation was that multi-line statements/side-effects would be supported. We do it all day with LINQ, where we can flexibly write Select(MethodName), Select( a => b), or Select( c => { Console.WriteLine("d"); return d; }); – lightw8 Mar 26 '21 at 00:46

2 Answers2

29

Your only supported choice is the func like you did. See [this article][1] for more information. His example:

var result = operation switch
{
"+" => ((Func<int>)(() => {
    Log("addition");
    return a + b;
}))(),
"-" => ((Func<int>)(() => {
    Log("subtraction");
    return a - b;
}))(),
"/" => ((Func<int>)(() => {
    Log("division");
    return a / b;
}))(),
_ => throw new NotSupportedException()
};

Just because switch expressions are new doesn't mean they are the best for all use cases. They are not designed to contain multiple commands.

Edit: I suppose you could also simply call external functions instead of making anonymous ones. [1]: https://alexatnet.com/cs8-switch-statement/

John Lord
  • 1,941
  • 12
  • 27
4

With:

TRes Call<TRes>(Func<TRes> f) => f();

its looks like:

return command switch {
  Command.C1 => new P1(),
  Command.C3 => new P2(),
  _ => Call(() => { stream.Position++; return null; }),
};

or:

var result = operation switch {
  "+" => Call(() => {
    Log("addition");
    return a + b;
  }),
  "-" => Call(() => {
    Log("subtraction");
    return a - b;
  }),
  "/" => Call(() => {
    Log("division");
    return a / b;
  }),
  _ => throw new NotSupportedException(),
};
  • 2
    Generally, answers are much more helpful if they include an explanation of what the code is intended to do, and why that solves the problem without introducing others. – DCCoder Sep 17 '20 at 22:53
  • 2
    it's also basically ripping off my answer – John Lord Aug 23 '21 at 18:19