void 方法的 C# 8 开关表达式

Posted

技术标签:

【中文标题】void 方法的 C# 8 开关表达式【英文标题】:C# 8 switch expression for void methods 【发布时间】:2020-09-13 21:42:56 【问题描述】:

我知道返回值的方法或属性匹配的 C# 8 switch expression 语法。但是如果我们只需要打开一个字符串值并执行一个什么都不返回的方法(没有返回类型/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

这可能吗?

【问题讨论】:

【参考方案1】:

TL;DR

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

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

这种方法也可以用于expression bodied methods。这里是demo。


说明

这可能吗?

C# 8 这是不可能的。 C# specification 中描述了此限制。

让我们参考C# specification。从页面Recursive Pattern Matching - Switch ExpressionStatements 我们可以了解到:

switch_expression 不允许作为expression_statement

expression_statement 计算给定的表达式。表达式计算的值(如果有)将被丢弃。

从这两条语句我们可以得出结论,switch_expression 不能在expression_statement 的上下文中使用,其结果值不能被丢弃。必须使用结果值,例如,它必须分配给变量、作为参数传递给方法或作为方法的结果返回。因此编译器抱怨switch expression 不能用作语句。


如果我们只需要打开一个字符串值并执行一个方法 什么都不返回(没有返回类型/void),我们该怎么做呢?我是 正在考虑某种形式的 Func,但不确定确切的语法。

我们可以使用下一种方法:编写一个switch expression,它返回一个delegate,然后立即调用它。例如:

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

这个方法也可以用来声明expression bodied members

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

这里是complete sample。

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

感谢l33t的评论。他指出,使用这种方法会导致每个case expression遭受非常昂贵的delegate allocation(在.NET 5中验证)。所以还有一个理由不使用这种方法,而是更喜欢使用switch-caseif-else


可能会在C#的未来版本中放宽此限制(see this link):

switch_expression 不允许作为expression_statement

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

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

【讨论】:

不幸的是,这种解决方法是性能杀手。不要使用这个!每个case 表达式都会受到非常昂贵的delegate allocation 的影响(在.NET 5 中验证)。 @l33t 谢谢。好点子。根据sharplab,委托分配只会在执行适当的切换分支时发生一次。但无论如何,我也不喜欢使用这种方法。我的回答更倾向于说明为什么 switch-expression 不允许返回 void。 是的,它会分配一次每次 switch-branch 被执行。所以如果你把它放在热路径上就会有麻烦:P @l33t 是的,这是真的 :)【参考方案2】:

正如伊利亚尔告诉你的那样,这是不可能的。我有时使用的一种解决方法是添加一个始终返回 true 的本地函数,并且我在 switch 分配中使用了一个丢弃字符。我发现这种方法比使用委托的方法更清晰。

例如,您可以在切换之前将其添加到代码中:

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

然后你会这样称呼它:

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

我认为这种方法有一些用例。例如,如果您想在 switch 中使用元组模式,它将比许多 annidated ifs 更具可读性。这是我正在使用的代码,我认为这种方法比 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),
            ;

【讨论】:

【参考方案3】:

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

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

您试图错误地使用 switch 表达式。

【讨论】:

以上是关于void 方法的 C# 8 开关表达式的主要内容,如果未能解决你的问题,请参考以下文章

新的 C# 8.0 开关表达式的运算符优先级是啥?

c# 8 switch 表达式不够“聪明”

c# 8.0 switch 表达式返回类型和空值

有没有办法让 switch 使用 C# 8 switch 表达式返回字符串值?

C#控制台基础 返回类型为void的 int 类型参数的委托与拉姆塔表达式

《C#本质论》读书笔记(15)使用查询表达式的LINQ