48

I'm aware of the C# 8 switch expression syntax for methods that return a value or for property matching. But if we just need to switch on a string value and execute a method that returns nothing (no return type/void), then how do we go about it? I am thinking some form of Func but not sure of the exact syntax. I know we can do it the old way with regular case statements but was trying to see if we can achieve the same with the newer syntax.

Here's the example of the problem

switch (stringvalue)
{
    case "Add": Add(); break;
    case "Subtract": Subtract(); break;
}

could be like:

stringvalue switch
{
    "Add" => Add(),
    "Subtract" => Subtract()
}
// but this complains that there needs to be assignment on left side for this

Is this possible?

Iliar Turdushev
  • 4,935
  • 1
  • 10
  • 23
Shiva Naru
  • 925
  • 3
  • 10
  • 21

3 Answers3

50

TL;DR

It is not possible. In C# 8 switch expression cannot return void. It must return a value and this value must be consumed (assigned to a variable, passed as an argument to a method, returned as a result of a method, etc.). But there is a workaround. We can write a switch expression that returns a delegate (of type Action, for example) and then immediately invokes it:

(stringValue switch
 {
    "Add" => (Action) Add,
    "Subtract" => Subtract,
    _ => throw new ArgumentOutOfRangeException()
 })();

Such approach also can be used with expression bodied methods. Here is demo.


Explanation

Is this possible?

In C# 8 it is not possible. This limitation is described in C# specification.

Lets refer to C# specification. From pages Recursive Pattern Matching - Switch Expression and Statements we can learn that:

  • The switch_expression is not permitted as an expression_statement.

  • An expression_statement evaluates a given expression. The value computed by the expression, if any, is discarded.

From these two statements we can conclude that switch_expression cannot be used in the context of expression_statement, and its result value cannot be discarded. The result value must be used, for example, it must be assigned to a variable, passed to a method as an argument or returned as a result of a method. Therefore compiler complains that switch expression cannot be used as a statement.


if we just need to switch on a string value and execute a method that returns nothing (no return type/void), how do we go about it? I am thinking some form of Func but not sure of the exact syntax.

We can use the next approach: write a switch expression that returns a delegate and then immediately invokes it. For example:

(stringValue switch
 {
    "Add" => (Action) Add,
    "Subtract" => Subtract,
    _ => throw new ArgumentOutOfRangeException()
 })();

This approarch also can be used to declare expression bodied members:

private static void Demo(string str) =>
    (str switch
     {
         "Add" => (Action) Add,
         "Subtract" => Subtract,
         _ => throw new ArgumentOutOfRangeException()
     })();

Here is complete sample.

In my opinion such workaround looks ugly and personally I would prefer to use switch-case or if-else instead of such construction.

Thanks to l33t's comment. He pointed out that using such approach would cause each case expression suffer from very costly delegate allocation (verified in .NET 5). So it is one more reason not to use this approach and prefer to use switch-case or if-else.


May be in future version of C# this limitation will be relaxed (see this link):

The switch_expression is not permitted as an expression_statement.

We are looking at relaxing this in a future revision.

But I haven't found an appropriate proposal in csharplang repo.

Iliar Turdushev
  • 4,935
  • 1
  • 10
  • 23
  • 6
    Unfortunately, this workaround is a performance killer. Do **NOT** use this! Each `case` expression will suffer from very costly [delegate allocation](https://devblogs.microsoft.com/pfxteam/know-thine-implicit-allocations/) (verified in .NET 5). – l33t Dec 08 '21 at 08:47
  • 1
    @l33t Thanks. Good point. According to [sharplab](https://sharplab.io/#v2:CYLg1APgAgTAjAWAFBQMwAJboMLoN7LpGYZQAs6AsgBQCU+hxAvo0a+gA4BOAlgG4BDAC4BTTHABsmCgBERAWwD21KHAAM6AM5Cu9ALwA+dtW1ctAdx5CAxgAt2DJMWdEARAEFgwV+kPoVcDD0nsAANA7OrgDKAK4ARjoC1kI+frEJXElC4U4uxAD6vgboQrZciuboAHYile5cAOYx8iJVQgDyMR0AZgBKAlUNIgCiAB7WIhxCPIpVdA5MtHQA3MjsqlLk6CF0eCy5RBvS6OmJybv7TEA===) delegate allocation will occur only once when an appropriate switch-branch is executed. But anyway I also prefer not to use such approach. My answer was more oriented to show why switch-expression didn't allow to return void. – Iliar Turdushev Dec 08 '21 at 09:24
  • 1
    Yes, it will allocate once *each time* the switch-branch is executed. So if you put it on a hot path there will be trouble :P – l33t Dec 08 '21 at 10:00
6

As Iliar told you, it can't be done. A workaround that I use sometimes is to add a local function that always returns true and I use a discard char in the switch assignment. I find this walkaround a bit clearer than the one using delegates.

For example you would add this to your code before the switch:

bool Add() { [HEREYOURCODEToAdd]; return true;}
bool Substract() { [HEREYOURCODETOSubstract]; return true;}

And then you will call it, like this:

_ = stringvalue switch {
    "Add" => Add(),
    "Substract" => Substract(),
};

I think this approach has some use cases. For example if you want to use tuple patterns in the switch it would be much more readable than many annidated ifs. This is a code that I'm using in which I consider this approach better than ifs:

           bool agregar(int índiceInicial, int índiceFinal) { visitasJson.Add(json[índiceInicial..índiceFinal]); return true; };
            _ = (índiceInicial, índiceFinal) switch {
                (0, -1) => agregar(índiceInicial + 1, índiceFinal - 3),
                (_, -1) => agregar(índiceInicial, índiceFinal - 1),
                (0, _) => agregar(índiceInicial + 5, índiceFinal),
                (_, _) => agregar(índiceInicial, índiceFinal),
            };
Desmond
  • 406
  • 5
  • 7
-1

Do you really want to use switch expression in your use case, just traditional way is much better.

All proposed solution like delegates, discard are workarounds.

You are trying to wrongly use switch expression.