【问题标题】:C# 8 switch expression with multiple cases with same resultC# 8 switch 表达式具有多个具有相同结果的情况
【发布时间】:2019-11-02 16:48:54
【问题描述】:

如何编写 switch 表达式来支持返回相同结果的多种情况?

对于版本 8 之前的 C#,开关可以这样编写:

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

当我使用 C# 版本 8 时,使用表达式语法时,是这样的:

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

所以我的问题是:如何将案例 1、2 和 3 变成只有一个 switch-case-arm,这样该值就不需要重复了?

根据“Rufus L”的建议更新:

对于我给出的示例,这是可行的。

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

但这并不是我想要完成的。这仍然只是一种情况(带有过滤条件),而不是多种情况产生相同的右手结果。

【问题讨论】:

  • 您想要完成什么? switch 表达式不是 switch 语句,并且明确禁止失败。 when 无论如何都比失败更强大。如果需要,可以将 Contains 与值数组一起使用。
  • 您可以使用 single var x when listOfValues.Contains(x) 来代替使用 3 或 4 或 11 个 case 语句,并根据需要处理尽可能多的 case
  • 我不想在这里解决案例。如我的问题案例 1,2 和 3 的第一个代码块所示,执行完全相同的右臂代码。出于这个问题的目的,我在这里举了一个非常简单的例子。想象一下案例 1,2,3 将评估非常不同和复杂的东西,例如与“何时”匹配的模式等。
  • 第一个代码块落入个案例。这就是case 1: case 2: 所做的 - 它们是带有空块的情况,会掉到下一个
  • 跌倒是指箱子上没有空的尸体。

标签: c# c#-8.0 switch-expression


【解决方案1】:

我开始安装它,但我还没有找到一种方法来使用新语法为单个开关部分指定多个单独的大小写标签。

但是,您可以创建一个新变量来捕获该值,然后使用条件来表示应该具有相同结果的情况:

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

如果您有很多案例要测试,这实际上更简洁,因为您可以在一行中测试一系列值:

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

【讨论】:

  • 明确禁止掉线。 when 子句无论如何都比失败要强大得多。您可以使用单个 var x when listOfValues.Contains(x) 而不是编写 3 或 10 个 case 语句
  • @PanagiotisKanavos 我刚刚了解到,虽然禁止掉线,但这不是掉线。这是多重匹配,允许并编译:sharplab.io/…
  • @PanagiotisKanavos 除了它不是失败,这就是我的观点。 “只执行 switch 语句中的一个 switch 部分。C# 不允许从一个 switch 部分继续执行到下一个。因此,以下代码会生成编译器错误 CS0163:“Control cannot fall through from one case label () 到另一个。"" - docs.microsoft.com/en-us/dotnet/csharp/language-reference/… |这是多个标签的多重匹配。而不是像 Python 中的逗号分隔列表。
  • @PanagiotisKanavos 您可能会感到困惑的是,许多语言实现多重数学通过 Fall Through。但在 C# 中,它们与委托和整数一样不同。或者接口和类。
  • @RufusL 我真的不确定正确的术语是什么。我只知道它不是失败的。实际上,我自己也这么认为,因为我记得它是在我早期的 Native C++ 时代通过 Falltrhough 实现的。但纠正我的人是对的:这不是失败。它不能失败。这是另外一回事。而且我不确定 when 子句只是模式匹配的一个子集还是第三种方式。
【解决方案2】:

C# 9 支持以下内容:

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

或者:

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

Source


对于旧版本的 C#,我使用以下扩展方法:

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

像这样:

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

它比when x == 1 || x == 2 || x == 3 更简洁一些,并且比when new [] {1, 2, 3}.Contains(x) 具有更自然的顺序。

【讨论】:

  • 它还分配了一个新的临时 Int 数组,如果它在循环内会导致 GC 压力,并且比 1 || 慢大约 100 倍。 2 - 但这对于大多数代码来说可能无关紧要
【解决方案3】:

遗憾的是,相对于 switch-statement 语法,这似乎是 switch-expression 语法的一个缺点。正如其他海报所建议的那样,相当笨拙的 var 语法是您唯一真正的选择。

所以你可能一直希望你能写:

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

相反,您需要在下面编写相当笨拙的代码,并喷上 typename:

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

在这样一个简单的例子中,你可能可以忍受这种尴尬。但是更复杂的例子就不那么适合了。事实上,我的示例实际上是从我们自己的代码库中提取的示例的简化,我希望在其中将具有大约六个结果但有十几个类型案例的 switch 语句转换为 switch 表达式。结果显然不如 switch 语句可读。

我的观点是,如果 switch 表达式需要共享结果并且长度超过几行,那么你最好坚持使用 switch 语句。嘘!它更冗长,但可能是对你的队友的善意。

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;

【讨论】:

  • 我知道这只是一个例子,但可以通过直接从案例中返回而不是设置tmp来清理一下。这样你也可以省略breaks。
  • @Ben 的评论很中肯,而且在所有条件相同的情况下,我也有自己的风格。但我选择不更改我的回复,因为原始帖子中实际上没有返回,我希望将计算和返回完全分开,因此重构更加清晰。
  • 由于您没有使用匹配的变量,我建议您更新您的 switch 语句示例,以便在以下情况下使用丢弃模式:case Type1 _:case Type2 _: 等。
  • @julealgon 是的,使用丢弃是更好的风格。在这种情况下,我使用了变量名称,因为 ResultA、ResultB、ResultC 旨在作为可能引用 t1、t2 ... t5 的表达式的占位符。我添加了一条评论以使其更清楚。
【解决方案4】:

C#9 解决了这个多案例问题:

    var result = foo switch
    {
        1 => ...,
        2 or 3 => ...,
        _ => throw ...
    };

【讨论】:

    【解决方案5】:

    如果你的开关类型是标志枚举

    [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",
    };
    

    【讨论】:

      【解决方案6】:

      如果您仍在使用 C# 8,因此无法使用上述答案中的 C# 9 1 or 2 or 3 =&gt; 方法,您也可以以避免创建额外变量 x 的方式编写它值作为switchValue 和接受的答案一样(当然这是完全正确的,我只是建议一个替代方案,因为我不喜欢那个额外的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",
      };
      

      显然,您可以将_ when 之后的条件替换为任何内容,例如switchValue == 3 || switchValue == 4 || switchValue == 5;答案的重点是显示_ when 位。

      我只是想到_ when =&gt; 的意思是“其他时候”,_ =&gt; 的意思是“其他时候”。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-30
        • 1970-01-01
        • 1970-01-01
        • 2020-05-10
        • 1970-01-01
        相关资源
        最近更新 更多