C# 8中用于void方法的switch表达式

51
我知道如何使用 C# 8 中的 switch 表达式 语法来匹配方法返回值或属性,但如果我们只需要根据字符串值进行切换,并执行不返回任何值(无返回类型/void)的方法,该怎么做呢?我在想一些形式的 Func,但不确定确切的语法。我知道我们可以用普通的 case 语句来实现,但是我想看看是否可以使用更新的语法实现相同的效果。
以下是问题的示例
switch (stringvalue)
{
    case "Add": Add(); break;
    case "Subtract": Subtract(); break;
}

可以是这样的:

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

这可能吗?

3个回答

53

简述

不可能的。C# 8 中,switch expression 不能返回 void。 它必须返回一个值,并且这个值必须被使用(赋给变量、作为方法参数传递、作为方法的返回结果等)。但是有一种解决方法。我们可以编写一个返回 delegateswitch expression(例如类型为 Action),然后立即调用它:

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

这种方法也适用于表达式主体的方法。这是演示


解释

这可行吗?

C# 8中不可以。这个限制在C#规范中有描述。

让我们参考C#规范。从页面递归模式匹配 - 开关表达式语句,我们可以了解到:

  • switch_expression 不允许作为 expression_statement

  • expression_statement 评估给定的表达式。如果有,表达式计算的值将被丢弃。

从这两个语句我们可以得出结论:在 expression_statement 的上下文中不能使用 switch_expression,其结果值不能被丢弃。结果值必须被使用,例如,它必须被赋值给一个变量,作为方法的参数传递或作为方法的返回值返回。因此编译器会报错指出无法将 switch expression 用作语句。


如果我们只需要在字符串值上进行切换并执行不返回任何内容(没有返回类型/void)的方法,应该如何处理?我想到了某种形式的 Func,但不确定具体的语法。

我们可以使用下面的方法:编写一个返回委托switch expression并立即调用它。例如:

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

这种方法也可以用来声明 表达式体成员:

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

这里是完整的示例

在我看来,这种解决方法看起来很丑,个人更喜欢使用switch-caseif-else而不是这样的构造。

感谢l33t的评论。他指出,使用这种方法会导致每个case表达式遭受非常昂贵的委托分配(在.NET 5中验证)。因此,这是不使用此方法并更喜欢使用switch-caseif-else的另一个原因。


也许在将来的C#版本中,这种限制将被放宽(请参见此链接):

switch_expression不允许作为expression_statement

我们正在考虑在将来的修订版中放宽这一点。

但是我没有在csharplang repo中找到合适的提案。


6
很遗憾,这种解决方法会极大地影响性能。请使用此方法!每个case表达式都将受到非常昂贵的委托分配(在.NET 5中已经验证)。 - l33t
1
@l33t 谢谢。好观点。根据 sharplab,当执行适当的 switch-branch 时,委托分配只会发生一次。但无论如何,我也更喜欢不使用这种方法。我的答案更多地是为了说明为什么 switch-expression 不允许返回 void。 - Iliar Turdushev
1
是的,每次执行 switch 分支时都会分配一次内存。因此,如果将其放在热路径上,就会出现问题 :P - l33t
@l33t 是的,没错 :) - Iliar Turdushev
1
请查看这个开放提案:https://github.com/dotnet/csharplang/issues/3038 - undefined

7

就像Iliar告诉你的那样,这是做不到的。我有时会使用一种解决方法,即添加一个始终返回true的本地函数,并在switch分配中使用一个丢弃字符。与使用委托的解决方法相比,我发现这种方法更清晰。

例如,在switch之前,您可以将以下内容添加到您的代码中:

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

然后你可以像这样调用它:

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

我认为这种方法有一些用例。例如,如果您想在开关语句中使用元组模式,那么它比许多嵌套的if语句更易读。以下是我正在使用的代码,在其中我认为这种方法比if语句更好:

           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),
            };

0

你真的想在你的用例中使用switch表达式吗?传统的方式更好。

所有提出的解决方案,如委托、丢弃都是变通方法。

你正在错误地使用switch表达式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接