使用switch语句处理多个情况

745

有没有一种方法可以在不重复声明 case value: 的情况下穿透多个 case 语句?

我知道这样是可行的:

switch (value)
{
   case 1:
   case 2:
   case 3:
      // Do some stuff
      break;
   case 4:
   case 5:
   case 6:
      // Do some different stuff
      break;
   default:
       // Default stuff
      break;
}

但我想做类似这样的事情:

switch (value)
{
   case 1,2,3:
      // Do something
      break;
   case 4,5,6:
      // Do something
      break;
   default:
      // Do the Default
      break;
}

我想到的这种语法是来自于其他语言吗,还是我有所遗漏?


1
你不直接使用 IF 语句(如果你正在检查一系列整数),有什么原因吗? - Eric Schoonover
2
是的,Charlse,第一种方法很好用,我在许多地方都使用过它。虽然比我想象中要麻烦,但确实很有用。我只是用那些整数作为例子。真正的数据更加复杂多样。if (1 || 2 || 3) {...} else if (4 || 5 || 6) {...} 也可以工作,但更难读懂。 - theo
6
为什么您认为后者比前者更脏?后者给逗号“,”添加了另一层含义,这是其他任何C语言都没有的,这使得后者看起来更加混乱杂乱。 - Jon Hanna
你可能在想Delphi/Pascal,它允许使用case i when 1...3: begin end; 4, 5, 7: begin end; 6, 8..10: begin end; else // handle default end;这种语法类型。 - Ken White
5
重要提示:自 C# v7 开始,支持在 switch case 中使用范围。请参阅 Steve G. 的答案 - RBT
这里的顶部示例实际上比所有“这是如何在今天使用花哨的基于范围或可枚举范围的方式来完成”的答案都要短得多,更易于阅读,并且适用于我的超宽屏幕。我的意思是,你看着它就清楚它在做什么。 - robsn
26个回答

869

我猜这个问题已经有人回答过了。不过,我认为你还可以通过以下方式更好地组合这两个选项:

switch (value)
{
    case 1: case 2: case 3:          
        // Do Something
        break;
    case 4: case 5: case 6: 
        // Do Something
        break;
    default:
        // Do Something
        break;
}

4
代码已经展开到问题中第一个示例的长度。最好按照问题中的方式进行操作。 - user4593252
14
为什么要这样做?Visual Studio 2013中的自动缩进功能会将其还原为原始问题中的格式。 - Gustav
5
也许是因为这个回答只是把问题掩盖起来的抄袭。这是我很少会下投反对票的时刻之一。真的,这个回答怎么会得到这么多支持呢? - T_D
20
@T_D之所以得到支持,是因为他确实回答了问题。原帖提到“我有什么遗漏吗……”,Carlos回答了他所遗漏的内容。对我来说,这似乎很清晰明了。不要因为他得到了422个赞而憎恨他。 - Mike Devenney
11
@MikeDevenney,据我所见,您对问题的解释有所不同,正确的答案应该是“不,C#没有任何语法来实现这个”。 如果有人问“我能把液体倒在我倒过来拿着的玻璃里吗?” 答案应该是“不能”,而不是“如果你倒过来看并运用想象力,你可以往上倒液体”,因为这个答案完全是关于使用想象力的。 如果您使用常规语法但格式不当,它看起来就像其他语法,需要一些想象力。 希望您明白我的意思...:P - T_D
显示剩余3条评论

369

C++和C#中均没有第二种方法的语法。

使用第一种方法没有问题。但是如果你有非常大的范围,可以使用一系列if语句。


5
作为补充,我想添加一个链接到MSDN上可用的C#语言规范,链接为http://msdn.microsoft.com/en-us/vcsharp/aa336809.aspx。 - Richard McGuire
1
用户可以使用一些if语句(或表格查找)将输入减少到一组枚举值,并在枚举值上进行switch操作。 - Harvey
4
我认为这个说法不再正确。请参考https://dev59.com/9WIj5IYBdhLWcg3wilkI。此外,在这篇问题的答案https://dev59.com/6nVD5IYBdhLWcg3wKYL-#44848705中也有相关信息。 - Dan Rayson
使用那么多的“if”语句真的很麻烦。请参考下面Misha的答案,有更好的方法。 - Christian Gibbs

147

C# 7的原始答案

C# 7 中(默认可用于Visual Studio 2017 / .NET Framework 4.6.2),使用switch语句现在可以进行基于范围的切换,这将有助于解决OP的问题。

示例:

int i = 5;

switch (i)
{
    case int n when (n >= 7):
        Console.WriteLine($"I am 7 or above: {n}");
        break;

    case int n when (n >= 4 && n <= 6 ):
        Console.WriteLine($"I am between 4 and 6: {n}");
        break;

    case int n when (n <= 3):
        Console.WriteLine($"I am 3 or less: {n}");
        break;
}

// Output: I am between 4 and 6: 5

注意:

  • when 条件中的括号 () 不是必需的,但在本示例中使用括号以突出比较。
  • 也可以使用 var 替代 int。例如:case var n when n >= 7:

C# 9 的更新示例

switch(myValue)
{
    case <= 0:
        Console.WriteLine("Less than or equal to 0");
        break;
    case > 0 and <= 10:
        Console.WriteLine("More than 0 but less than or equal to 10");
        break;
    default:
        Console.WriteLine("More than 10");
        break;
}

或者

var message = myValue switch
{
    <= 0 => "Less than or equal to 0",
    > 0 and <= 10 => "More than 0 but less than or equal to 10",
    _ => "More than 10"
};
Console.WriteLine(message);

4
当您可以使用C# 7.x或更高版本时,模式匹配应该是最佳实践,因为它比其他答案更清晰明了。 - UndyingJellyfish
有没有一种方法可以使用枚举列表来实现这个?其中枚举映射到int? - Sigex

74

这个语法来自于Visual Basic的Select...Case语句

Dim number As Integer = 8
Select Case number
    Case 1 To 5
        Debug.WriteLine("Between 1 and 5, inclusive")
        ' The following is the only Case clause that evaluates to True.
    Case 6, 7, 8
        Debug.WriteLine("Between 6 and 8, inclusive")
    Case Is < 1
        Debug.WriteLine("Equal to 9 or 10")
    Case Else
        Debug.WriteLine("Not between 1 and 10, inclusive")
End Select

您不能在C#中使用这种语法。相反,您必须使用第一个示例中的语法。


14
这怎么算是一个回答?“你不能使用以下代码。” - Muizz Mahdy

59

C#9引入了关系模式匹配。这使我们能够执行以下操作:

switch (value)
{
    case 1 or 2 or 3:
      // Do stuff
      break;
    case 4 or 5 or 6:
      // Do stuff
      break;
    default:
        // Do stuff
        break;
}

深入教程:C#9中的关系模式匹配

C# 9.0的模式匹配变化

关系模式允许程序员表达输入值必须满足与常量进行比较时的关系约束。


40

你可以省略换行符,这样就可以得到:

case 1: case 2: case 3:
   break;

但我认为那是不好的风格。


1
糟糕的风格是主观的。我更喜欢这种方式,因为它清晰地显示了意图。 - Thomas Phaneuf

22

.NET Framework 3.5具有范围:

来自MSDN的Enumerable.Range

您可以结合“包含”和IF语句使用它,因为像某人所说,SWITCH语句使用“==”运算符。

以下是一个例子:

int c = 2;
if(Enumerable.Range(0,10).Contains(c))
    DoThing();
else if(Enumerable.Range(11,20).Contains(c))
    DoAnotherThing();

但我认为我们可以更有趣:既然您不需要返回值,而且此操作不需要参数,您可以轻松地使用动作!

public static void MySwitchWithEnumerable(int switchcase, int startNumber, int endNumber, Action action)
{
    if(Enumerable.Range(startNumber, endNumber).Contains(switchcase))
        action();
}

这个新方法的旧例子:

MySwitchWithEnumerable(c, 0, 10, DoThing);
MySwitchWithEnumerable(c, 10, 20, DoAnotherThing);

由于您传递的是操作而不是值,因此应省略括号,这非常重要。如果需要带参数的函数,请将 Action 的类型更改为 Action<ParameterType>。如果需要返回值,请使用 Func<ParameterType, ReturnType>

在 C# 3.0 中,没有简单的偏函数应用来封装参数相同的情况,但您可以创建一个小的辅助方法(稍微有点冗长)。

public static void MySwitchWithEnumerable(int startNumber, int endNumber, Action action){ 
    MySwitchWithEnumerable(3, startNumber, endNumber, action); 
}

以下是新的功能性导入语句示例,我认为它们比旧的命令式语句更强大和优雅。


3
不错的选择。需要注意的是,Enumerable.Range 方法有两个参数:start 和 count。你的例子写法可能会有问题,因为你把第二个参数当成了 end。例如,Enumerable.Range(11,20) 会返回以 11 开始的 20 个数字,而不是从 11 到 20 的数字。请注意修改。 - Gabriel McAdams
虽然,如果使用枚举,为什么不这样做呢? if(Enumerable.Range(MyEnum.A, MyEnum.M){ DoThing(); } else if(Enumerable.Range(MyEnum.N, MyEnum.Z){ DoAnotherThing(); } - David Hollowell - MSFT
4
请注意,Enumerable.Range(11,20).Contains(c)等价于for(int i = 11; i < 21; ++i){ if (i == c) return true; } return false;。如果您的范围很大,使用此方法会花费很长时间,而只使用><则可以快速并且是常数时间的。 - Jon Hanna
一项改进:让 MySwitchWithEnumerable 返回 void 对于这种情况来说是一个弱设计。原因是你已经将 if-else 转换为一系列独立的语句,这隐藏了意图,即它们是互斥的 - 只有一个 action 被执行。相反,返回 bool,并使用 if (..) { action(); return true; } else return false; 作为函数体。调用站点则显示意图:if (MySwitchWithEnumerable(..)) else (MySwitchWithEnumerable(..));。这是更可取的。然而,对于这个简单的情况,它也不再是一个重大的改进。 - ToolmakerSteve

19

这里是完整的C# 7解决方案...

switch (value)
{
   case var s when new[] { 1,2,3 }.Contains(s):
      // Do something
      break;
   case var s when new[] { 4,5,6 }.Contains(s):
      // Do something
      break;
   default:
      // Do the default
      break;
}

它也适用于字符串...

switch (mystring)
{
   case var s when new[] { "Alpha","Beta","Gamma" }.Contains(s):
      // Do something
      break;
...
}

2
这意味着您会在每个 switch 语句中分配数组,对吗?如果我们将它们作为常量变量会不会更好? - MaLiN2223
2
优雅,但确实需要知道编译器是否优化了这种情况,以便重复调用不会每次都产生数组构造的开销;预先定义数组是一种选择,但会削弱很多优雅性。 - mklement0
创建一个数组并搜索它只是为了检查它是否为a或b或c非常低效! - S.Serpooshan

14

下面的代码会无法运行:

case 1 | 3 | 5:
// Not working do something

唯一的方法是:

case 1: case 2: case 3:
// Do something
break;

你要找的代码可以在Visual Basic中工作,你可以很容易地在switch语句的none选项或if else块中放入范围。为了方便起见,我建议在极端情况下使用Visual Basic制作.dll文件并将其导入到你的C#项目中。

注意:在Visual Basic中与switch等效的是Select Case


8
另一个选择是使用例程。如果情况1-3都执行相同的逻辑,则将该逻辑封装在例程中,并为每个情况调用它。我知道这并没有真正消除case语句,但它确实实现了良好的风格,并将维护保持到最低限度.....
[编辑]添加了替代实现以匹配原始问题...[/编辑]
switch (x)
{
   case 1:
      DoSomething();
      break;
   case 2:
      DoSomething();
      break;
   case 3:
      DoSomething();
      break;
   ...
}

private void DoSomething()
{
   ...
}

Alt

switch (x)
{
   case 1:
   case 2:
   case 3:
      DoSomething();
      break;
   ...
}

private void DoSomething()
{
   ...
}

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