关于C#:switch语句中有多个case

Multiple cases in switch statement

有没有一种方法可以在不重复说明case value:的情况下通过多个案例陈述?

我知道这是可行的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

但我想这样做:

1
2
3
4
5
6
7
8
9
10
11
12
switch (value)
{
   case 1,2,3:
      //Do Something
      break;
   case 4,5,6:
      //Do Something
      break;
   default:
      //Do the Default
      break;
}

这是我从另一种语言想到的语法,还是我遗漏了什么?

  • 是否有理由不使用if语句(如果您检查的是一系列int)?
  • 是的,查理,第一种方法很好用,我在很多地方都用过。它比我想要的脏,但它很有用。我只是用这些整数作为例子。真实的数据更加多样化。如果(1 2 3)…否则如果(4 5 6)…也会工作,但更难阅读。
  • 你为什么认为后者比前者更脏?后者为,添加了另一个含义,并且不与任何其他C风格语言共享。那对我来说会更脏。
  • 您可能已经从Ruby学习了第二个语法。这就是它在该语言中的工作方式(尽管开关变为case,case变为when,等等)。
  • vb的select case语句在这方面有更大的威力。
  • 您可能会想到delphi/pascal,它允许使用case i when 1...3: begin end; 4, 5, 7: begin end; 6, 8..10: begin end; else // handle default end;类型的语法。
  • 重要提示。从C V7开始的开关箱支持范围-请参阅Steve G.的答案。


我想这已经被回答了。但是,我认为您仍然可以通过执行以下操作以更好的语法方式混合这两个选项:

1
2
3
4
5
6
7
8
9
10
11
12
switch (value)
{
case 1: case 2: case 3:          
    // Do Something
    break;
case 4: case 5: case 6:
    // Do Something
    break;
default:
    // Do Something
    break;
}

  • "switch"应该是小写的c?
  • 折叠的代码会被加长到问题中的第一个示例。不妨按问题的方式来做。
  • 何苦?Visual Studio 2013中的自动缩进器无论如何都会将其还原为原始问题中的格式。
  • @杰里米希尔德也许是因为这个答案只是一个伪装的问题。难得的一个时刻我投了反对票。真的,这是怎么得到这么多支持的?
  • @它得到了支持,因为它实际上回答了这个问题。手术室说,我是不是错过了什么…卡洛斯回答说他错过了什么。在我看来,它看起来很干枯。别恨他有422张赞成票。
  • @Mikedevenney那么你对这个问题的解释就不同了,我认为正确的答案是"不,C没有任何语法"。如果有人问"能不能把液体倒在我拿着的杯子里?"答案应该是"不",而不是"如果你把液体倒过来,用你的想象力",因为这个答案是关于运用想象力的。如果您使用常规语法,但格式不好,那么它看起来像其他语法,并且具有一定的想象力。希望你明白我的意思…P
  • "Visual Studio 2013中的自动缩进器仍会将此格式还原为原始问题中的格式。"是否已更改?在Visual Studio 2010中,格式不会还原。
  • @Matsolof删除最后一个花括号并重新键入。
  • 你什么意思,nu evest?我的答案中没有花括号。
  • @例如,我投了米凯德文尼的反对票,因为这和艾伦·温德两年前给出的答案是一样的!加上一些不必要的绒毛…哦,我赞成阿拉斯的回答;)
  • 如果您使用的是Enums,这也会得到长的行,对于switch case语句来说,这主要是如此……


对于你提到的第二种方法,C++中没有语法,也没有C语言。

你的第一种方法没什么问题。但是,如果范围很大,只需使用一系列if语句。

  • 作为一个补充,我想添加一个到c语言规范的链接,该语言规范在msdn.microsoft.com/en-us/vcsharp/aa336809.aspx上提供。
  • 用户可以使用一些if(或表查找)将输入减少到一组枚举并打开枚举。
  • 可能是从vb.net上选的
  • vb.net更适合各种case语句…但他们不像C那样通过。吃一点,给一点。


此语法来自Visual Basic Select…Case语句:

1
2
3
4
5
6
7
8
9
10
11
12
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中使用此语法。相反,您必须使用第一个示例中的语法。

  • 这是我为数不多的关于*基本的事情之一。
  • 在这里,我们有一个少数的显示器,VisualBasic没有那么难看,而且比C更通用。这是一个很有价值的例子!
  • 这相当不错。我想知道为什么没有添加C 8.0。会很不错的。


对于最初的问题来说有点晚了,但是我发布这个答案是希望使用较新版本(C 7——默认情况下在Visual Studio 2017/.NET Framework 4.6.2中提供)的人会发现它很有用。

在C 7中,使用switch语句现在可以进行基于范围的切换,这将有助于解决OP的问题。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 7.x或更高版本时(模式匹配)通常是最佳实践,因为它比其他答案更清晰。
  • 有没有一种方法可以通过枚举列表来实现这一点?枚举映射到int的位置?


您可以省去换行符,这样可以:

1
2
case 1: case 2: case 3:
   break;

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


.NET Framework 3.5具有以下范围:

从msdn开始的可枚举范围

您可以将它与"contains"和if语句一起使用,因为就像有人说的那样,switch语句使用"=="运算符。

这里有一个例子:

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

但我认为我们可以有更多的乐趣:因为您不需要返回值,而且这个动作不带参数,所以您可以很容易地使用动作!

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

这个新方法的旧示例:

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

因为您传递的是动作而不是值,所以应该省略括号,这非常重要。如果需要带参数的函数,只需将Action的类型更改为Action。如果需要返回值,请使用Func

在C 3.0中,没有容易的部分应用程序来封装case参数相同的事实,但是您创建了一个小的helper方法(有点冗长,tho)。

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

下面是一个新的函数导入语句如何比旧的命令式语句更强大和优雅的例子。

  • 不错的选择。有一点需要注意,尽管-Enumerable.Range有参数int startint count。你的例子不会像写的那样正确。你把它写得好像第二个论点是int end。例如-Enumerable.Range(11,20)将产生20个以11开头的数字,而不是11到20之间的数字。
  • 但是,如果使用枚举,为什么不使用类似的东西?if(enumerable.range(myenum.a,myenum.m)dothing();else if(enumerable.range(myenum.n,myenum.z)doanotherthing();
  • 注意,Enumerable.Range(11,20).Contains(c)相当于for(int i = 11; i < 21; ++i){ if (i == c) return true; } return false;,如果范围大,需要很长的时间,而仅仅使用><是快速和恒定的时间。
  • 改进:在这种情况下,MySwitchWithEnumerable返回void是弱设计。原因:您已经将一个if-else转换为一系列独立的语句-隐藏了意图,也就是说它们是互斥的-只执行一个action。相反,返回bool,身体为if (..) { action(); return true; } else return false;,主叫站点显示意图:if (MySwitchWithEnumerable(..)) else (MySwitchWithEnumerable(..));。最好是这样。然而,对于这个简单的例子,它也不再是对原始版本的显著改进。


@詹妮弗·欧文斯:你说得对,下面的代码是行不通的:

1
2
case 1 | 3 | 5:
//not working do something

唯一的方法是:

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

您正在寻找的代码在VisualBasic上工作,您可以轻松地将范围…在没有选择开关或如果其他块方便,我建议,在非常极端的点,使.dll与VisualBasic和导入回您的C项目。

注意:VisualBasic中的等效开关是Select案例。


另一种选择是使用一个例程。如果案例1-3都执行相同的逻辑,那么将该逻辑包装在一个例程中,并为每个案例调用它。我知道这实际上并不能摆脱case语句,但它确实实现了良好的风格并将维护保持在最低限度。

[编辑]添加了替代实现以匹配原始问题…[/edit]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
switch (x)
{
   case 1:
      DoSomething();
      break;
   case 2:
      DoSomething();
      break;
   case 3:
      DoSomething();
      break;
   ...
}

private void DoSomething()
{
   ...
}

中高音

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch (x)
{
   case 1:
   case 2:
   case 3:
      DoSomething();
      break;
   ...
}

private void DoSomething()
{
   ...
}

GCC实现了对C语言的扩展,以支持连续范围:

1
2
3
4
5
6
7
8
9
10
11
12
switch (value)
{
   case 1...3:
      //Do Something
      break;
   case 4...6:
      //Do Something
      break;
   default:
      //Do the Default
      break;
}

编辑:刚注意到问题的C标签,所以大概GCC的答案没有帮助。


C中开关的另一个鲜为人知的方面是它依赖于operator=并且由于它可以被覆盖,所以您可以有如下内容:

1
2
3
4
5
6
string s = foo();

switch (s) {
  case"abc": /*...*/ break;
  case"def": /*...*/ break;
}

  • 这可能会成为其他人试图阅读代码的一大难题。


这是完整的C 7解决方案…

1
2
3
4
5
6
7
8
9
10
11
12
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;
}

也适用于字符串…

1
2
3
4
5
6
7
switch (mystring)
{
   case var s when new[] {"Alpha","Beta","Gamma" }.Contains(s):
      //Do Something
      break;
...
}


实际上,我也不喜欢goto命令,但它在官方的MS材料中,这里都允许使用语法。

如果可以到达switch节的语句列表的端点,则会发生编译时错误。这就是所谓的"不摔倒"规则。实例

1
2
3
4
5
6
7
8
9
10
11
switch (i) {
case 0:
   CaseZero();
   break;
case 1:
   CaseOne();
   break;
default:
   CaseOthers();
   break;
}

有效,因为没有开关节具有可到达的终结点。与C和C++不同,开关部分的执行不允许"下一步"切换到下一个开关部分,并举例说明

1
2
3
4
5
6
7
8
switch (i) {
case 0:
   CaseZero();
case 1:
   CaseZeroOrOne();
default:
   CaseAny();
}

导致编译时错误。当一个switch节的执行后面跟着另一个switch节的执行时,必须使用一个显式的goto case或goto default语句:

1
2
3
4
5
6
7
8
9
10
11
switch (i) {
case 0:
   CaseZero();
   goto case 1;
case 1:
   CaseZeroOrOne();
   goto default;
default:
   CaseAny();
   break;
}

开关部分允许有多个标签。实例

1
2
3
4
5
6
7
8
9
10
11
12
switch (i) {
case 0:
   CaseZero();
   break;
case 1:
   CaseOne();
   break;
case 2:
default:
   CaseTwo();
   break;
}

我相信在这个特殊的情况下,goto可以被使用,它实际上是唯一的解决方法。

来源:http://msdn.microsoft.com/en-us/library/aa664749%28v=vs.71%29.aspx

  • 请注意,在实践中,几乎总是可以避免goto(尽管我不认为这是"可怕的"—它正在扮演一个特定的、结构化的角色)。在您的示例中,因为您已经用函数包装了案例主体(一件好事),所以案例0可以成为CaseZero(); CaseZeroOrOne(); break;。不需要goto


许多工作似乎都花在了寻找一种方法,使C最不常用的语法中的一个看起来更好或工作更好。我个人认为switch语句很少值得使用。我强烈建议您分析正在测试的数据以及您想要的最终结果。

例如,我们想快速测试已知范围内的值,看看它们是否是素数。你想避免让你的代码进行浪费性的计算,你可以在网上找到你想要的范围内的素数列表。您可以使用大量的switch语句将每个值与已知素数进行比较。

或者你可以创建素数的数组图,并立即得到结果:

1
2
3
4
5
6
7
8
9
10
11
    bool[] Primes = new bool[] {
        false, false, true, true, false, true, false,    
        true, false, false, false, true, false, true,
        false,false,false,true,false,true,false};
    private void button1_Click(object sender, EventArgs e) {
        int Value = Convert.ToInt32(textBox1.Text);
        if ((Value >= 0) && (Value < Primes.Length)) {
            bool IsPrime = Primes[Value];
            textBox2.Text = IsPrime.ToString();
        }
    }

也许您想看看字符串中的字符是否是十六进制的。您可以使用一个笨拙且稍大的switch语句。

或者,可以使用正则表达式来测试字符,或者使用indexof函数来搜索已知十六进制字母字符串中的字符:

1
2
3
4
5
6
        private void textBox2_TextChanged(object sender, EventArgs e) {
        try {
            textBox1.Text = ("0123456789ABCDEFGabcdefg".IndexOf(textBox2.Text[0]) >= 0).ToString();
        } catch {
        }
    }

假设您希望根据1到24之间的值执行三种不同操作中的一种。我建议使用一组if语句。如果这变得太复杂(或者数字更大,比如5个不同的动作,取决于1到90之间的值),那么使用枚举来定义动作并创建枚举的数组映射。然后,该值将用于索引到数组映射中,并获取所需操作的枚举。然后使用一小组if语句或一个非常简单的switch语句来处理产生的枚举值。

此外,数组映射将一系列值转换为动作的好处在于它可以很容易地被代码更改。使用硬接线代码,您不可能在运行时轻松更改行为,但使用数组映射,这很容易。

  • 还可以映射到lambda表达式或委托
  • 好点。一条小注释:我通常发现维护与给定情况匹配的值列表比维护数组映射更容易。数组映射的问题是很容易出错。例如,与真/假素数数组映射不同,只需拥有素数列表,并将其加载到哈希集中以获得查找性能。即使有两种以上的情况,通常只有一种情况是一个小列表,所以从其他情况的列表中构建一个枚举哈希集(如果稀疏)或数组映射(在代码中)。


如果您有大量的字符串(或任何其他类型)大小写都在执行相同的操作,我建议使用与string.contains属性组合的字符串列表。

所以,如果你有这样一个大开关语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch (stringValue)
{
    case"cat":
    case"dog":
    case"string3":
    ...
    case"+1000 more string": //Too many string to write a case for all!
        //Do something;
    case"a lonely case"
        //Do something else;
    .
    .
    .
}

您可能希望用这样的if语句替换它:

1
2
3
4
5
6
7
8
9
10
11
//Define all the similar"case" string in a List
List<string> listString = new List<string>(){"cat","dog","string3","+1000 more string"};
//Use string.Contains to find what you are looking for
if (listString.Contains(stringValue))
{
    //Do something;
}
else
{
    //Then go back to a switch statement inside the else for the remaining cases if you really need to
}

这个比例适用于任何数量的字符串大小写。


为此,您将使用goto语句。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    switch(value){
    case 1:
        goto case 3;
    case 2:
        goto case 3;
    case 3:
        DoCase123();
    //This would work too, but I'm not sure if it's slower
    case 4:
        goto case 5;
    case 5:
        goto case 6;
    case 6:
        goto case 7;
    case 7:
        DoCase4567();
    }

  • SKON-GOTO打破了程序设计的一个基本原理(C++和C语言仍然是根植于其中的,它们不是纯OO语言(感谢上帝))。过程编程有一个由语言构造和方法调用约定(运行时堆栈如何增长和收缩)确定的定义良好的逻辑流。goto语句基本上通过允许任意跳转来绕过此流。
  • 佩西,我并不是说这是一种好的风格,但它确实符合最初问题的要求。
  • 不,它不会"按照最初的问题要求做"。原始问题的代码按原样工作。他们不需要修理。即使他们这样做了,这也是一个可怕的建议。它不够简洁,使用了goto。更糟糕的是,它完全不必要地使用goto,因为op所声明的原始语法确实有效。问题是是否有一种更简洁的方法来提供备用案例。正如人们多年前回答的那样,是的,如果你愿意把几个案例放在一行case 1: case 2:上,并且如果编辑器的自动风格允许的话。
  • Goto被认为是坏的唯一原因是有些人发现很难遵循逻辑流程。.NET msil(汇编对象代码)使用goto是因为它速度很快,但是如果.NET代码可以被编写,并且没有goto也能发挥同样的性能,那么最好不要使用goto,这样你就不会被@toolmakersteve屈尊的回复这样的人激怒。
  • @请仔细阅读我的回复。我的抱怨不仅仅是关于goto的使用。我之所以反对,是因为问题显示了已经正常工作的代码,而这个答案a)采用了工作代码,使其更冗长、结构更不完善,毫无益处;b)没有回答问题。