C#8带有多个条件的开关表达式具有相同的结果


80

如何编写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变成仅一个开关案例臂,从而不需要重复该值?

来自“ 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值数组。
Panagiotis Kanavos

除了使用3或4或11条case语句,你可以使用一个单一的 var x when listOfValues.Contains(x),当你要处理,因为很多情况下
帕纳约蒂斯Kanavos

2
在这里,我不是要尝试解决问题。就像我的问题案例1,2和3的第一个代码块中所示,执行完全相同的右臂代码。出于这个问题的目的,我在这里举了一个非常非常简单的例子。想象一下案例1,2,3将评估非常不同和复杂的内容,例如模式匹配以及“何时”等
。– huzle

第一个代码块通过大小写。那是case 1: case 2:做什么的-它们是带有空块的情况下掉到下一个的情况
Panagiotis Kanavos

1
由于失败,我指的是案件中的非空组织。
huzle

Answers:


76

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

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

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条款远比完全失败要强大。不用编写3或10条case语句,您可以使用一个var x when listOfValues.Contains(x)
Panagiotis Kanavos

1
@PanagiotisKanavos我刚刚了解到,虽然禁止跌倒,但不是跌倒。这是多个匹配项,允许完全匹配并进行编译:sharplab.io/…–
Christopher,

2
@PanagiotisKanavos除了不是失败,这是我的观点。“仅执行switch语句中的一个switch节。C#不允许执行从一个switch节继续到下一个switch节。因此,以下代码生成了编译器错误CS0163:“控件不能从一个case标签掉落(<案例标签>)到另一个。”“- docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/… | 这是多个标签的多次匹配。而不是像在Python中那样用逗号分隔的列表。
Christopher

1
@PanagiotisKanavos可能使您感到困惑的是,许多Langauge通过Fall Through实现了Multiple Mathcing。但是在C#中,它们与委托和整数一样。或接口和类。
Christopher

1
@RufusL我什至不敢确定这个正确的词是。我确实知道,这不是失败。实际上,我记得自己在Native C ++早期就通过Falltrhough实现了它。但是纠正我的那个人是对的:这不是失败。它不能失败。这是另一回事。而且我不确定when子句只是模式匹配的子集还是第3种方式。
Christopher

19

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

资源


对于旧版本的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)


2
它还分配一个新的临时Int数组,如果这在循环内,它将导致GC压力,并且比1 ||慢约100倍。2-但这对大多数代码来说可能并不重要
Orion Edwards

15

遗憾的是,相对于开关语句语法,这似乎是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
};

在这样一个简单的示例中,您可能可以忍受这种尴尬。但是,更复杂的示例很难适应。实际上,我的示例实际上是从我们自己的代码库中提取的示例的简化,在该示例中,我希望将具有大约6个结果但有十几个类型用例的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;

4
我知道这只是一个例子,但是可以通过直接从案例中返回而不是set来对其进行清理tmp。这样,您也可以省略breaks。

2
@Ben的评论很公平,在所有条件平等的情况下,我的风格也是如此。但是我选择不更改响应,因为原始帖子中实际上没有退货,而且我希望将计算和退货完全分开,因此重构更加清晰。
sfkleach

由于不使用匹配的变量,我建议您更新switch语句例如使用丢弃模式的情况:case Type1 _:case Type2 _:等等
julealgon

@julealgon是,使用抛弃是更好的样式。在这种情况下,我使用变量名是因为ResultA,ResultB和ResultC打算用作可能引用t1,t2 ... t5的表达式的占位符。我添加了一条评论,以使之更清楚。
sfkleach '20

0

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

[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",
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.