switch语句中有多种情况


581

有没有办法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;
}

我是从其他语言考虑使用这种语法吗?还是我错过了某些内容?


您是否有理由不仅仅使用IF语句(如果您要检查整数范围)?
埃里克·舒诺弗

2
是的,charlse,第一种方法运作良好,我已经在很多地方使用过它。它比我想要的要脏,但它很有用。我只是以这些整数为例。实际数据更加多样化。if(1 || 2 || 3){...} else if(4 || 5 || 6){...}也可以使用,但是很难阅读。
theo

5
为什么您认为后者比前者更脏。后者为,C语言增加了另一种含义,并且不与任何其他c样式语言共享。在我看来,这似乎更加肮脏。
乔恩·汉纳

1
您可能已经从Ruby那里学习了2nd的语法。这就是它在该语言中的工作方式(尽管切换成了大小写,而大小写变成了何时等)
juanpaco 2012年

4
重要说明。从C#v7开始的转换案例中支持范围-请参阅史蒂夫·G的回答
RBT

Answers:


313

您提到的第二种方法在C ++和C#中都没有语法。

您的第一种方法没有错。但是,如果范围很大,则只需使用一系列if语句。


5
另外,我想添加一个指向MSDN上C#语言规范的链接,该链接位于msdn.microsoft.com/en-us/vcsharp/aa336809.aspx
Richard McGuire

用户可以使用一些if(或表查找)将输入缩减为一组枚举并打开该枚举。
哈维2013年

5
可能是从VB.net选的
乔治·比比里斯

1
VB.net更好地适用于各种情况下的语句...除非它们不会像C#那样掉线。一点点,一点点。
Brain2000 '18

我相信这不再正确。请参阅stackoverflow.com/questions/20147879/…。同样在这个问题上,还有一个答案stackoverflow.com/a/44848705/1073157
Dan Rayson

699

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

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

3
对于C#,“ switch”应该为小写吗?
奥斯汀·哈里斯

3
折叠后的代码延长到问题的第一个示例。也可以按照问题的方式进行操作。
MetalPhoenix 2014年

10
何必呢?无论如何,Visual Studio 2013中的自动压头都会将其还原为原始问题中的格式。
古斯塔夫

14
@T_D得到了支持,因为它实际上回答了问题。OP说,我要丢失什么吗?Carlos回答了他丢失的内容。对我来说似乎很漂亮,而且干了。不要讨厌他有422个投票。
Mike Devenney '16

8
@MikeDevenney然后,您对问题的解释有所不同,据我所知,正确的答案是“不,C#没有任何语法”。如果有人问“是否可以倒倒我拿着的杯子里的液体?” 答案应该是“否”,而不是“如果倒过来看看并使用想像力,您可以倒入液体”,因为这个答案是关于想像力的全部。如果您使用常规语法但格式很差,则看起来与其他语法一样,有些想像力。希望你能
理解

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#中使用此语法。相反,您必须使用第一个示例中的语法。


49
这是我想念* Basic的几件事之一。
尼克

10
在这里,我们可以看到为数不多的几款显示器,这些显示器的视觉基础并不那么丑陋,并且比c#更通用。这是一个很有价值的例子!
bgmCoder

这是相当不错的。我想知道为什么C#8.0没有添加它。会很好。
Sigex

69

在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:

3
当您可以使用C#7.x或更高版本时,这种(模式匹配)通常应该是最佳实践,因为它比其他答案更清晰。
UndyingJellyfish

有没有一种方法可以通过枚举列表来实现?枚举在哪里映射到int?
Sigex

32

您可以省略换行符,该换行符为您提供:

case 1: case 2: case 3:
   break;

但我认为这种风格不好。


20

.NET Framework 3.5具有以下范围:

来自MSDN的Enumerable.Range

您可以将其与“ contains”和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);

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

在C#3.0中,没有简单的Partial Application来封装case参数相同的事实,但是您创建了一个小辅助方法(有点冗长,但是)。

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

这是一个示例,说明新的函数式导入语句如何比旧的命令式语句更强大,更优雅。


3
好的选择。不过要注意一件事-Enumerable.Range具有参数int startint count。您的示例将无法正确编写。您将其写为好像第二个参数是int end。例如- Enumerable.Range(11,20)将导致以11开头20个数字,而不是数字从11到20
加布里埃尔麦克亚当斯

但是,如果使用枚举,为什么不这样做呢?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;如果您使用的范围较大,则仅使用它会花费较长的时间,>并且<会保持恒定的时间。
乔恩·汉纳

一项改进:对于这种情况,MySwitchWithEnumerable退货void是薄弱的设计。原因:您已将转换if-else为一系列独立的语句-隐藏了它们的意图(即它们是互斥的)的意图-仅action执行一个。取而代之的是,返回bool带有正文if (..) { action(); return true; } else return false;的主叫站点,然后显示意图:if (MySwitchWithEnumerable(..)) else (MySwitchWithEnumerable(..));。这是优选的。但是,对于这种简单情况,它也不再是对原始版本的重大改进。
ToolmakerSteve

15

这是完整的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;
...
}

这意味着您将在每个switch语句中分配数组,对吗?如果我们将它们作为常量变量会更好吗?
MaLiN2223 '19

很优雅,但是知道编译器是否优化了这种情况确实是一件好事,这样反复调用不会每次都引起数组构造的开销。提前定义数组是一种选择,但是却失去了很多优雅之处。
mklement0

11

以下代码无法正常工作:

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

唯一的方法是:

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

您要查找的代码在Visual Basic中有效,您可以在其中轻松放置范围... noneswitch语句或if else块选项中比较方便,我建议在极端情况下使用Visual Basic制作.dll并重新导入到您的C#项目。

注意:Visual Basic中的等效开关为Select Case


7

另一种选择是使用例程。如果情况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()
{
   ...
}

5

在C#中,switch的一个鲜为人知的方面是它依赖于operator =,并且由于可以覆盖它,因此您可能会遇到以下情况:


string s = foo();

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

4
对于其他尝试读取代码的人来说,这可能会成为一个大难题
安德鲁·哈里

5

gcc实现了对C语言的扩展,以支持顺序范围:

switch (value)
{
   case 1...3:
      //Do Something
      break;
   case 4...6:
      //Do Something
      break;
   default:
      //Do the Default
      break;
}

编辑:刚刚注意到问题上的C#标记,所以大概一个gcc答案无济于事。


4

在C#7中,我们现在有了模式匹配,因此您可以执行以下操作:

switch (age)
{
  case 50:
    ageBlock = "the big five-oh";
    break;
  case var testAge when (new List<int>()
      { 80, 81, 82, 83, 84, 85, 86, 87, 88, 89 }).Contains(testAge):
    ageBlock = "octogenarian";
    break;
  case var testAge when ((testAge >= 90) & (testAge <= 99)):
    ageBlock = "nonagenarian";
    break;
  case var testAge when (testAge >= 100):
    ageBlock = "centenarian";
    break;
  default:
    ageBlock = "just old";
    break;
}

3

其实我也不喜欢GOTO命令,但是它在Microsoft官方资料中,并且所有允许的语法都在这里。

如果开关部分的语句列表的端点可访问,则发生编译时错误。这称为“不掉线”规则。这个例子

switch (i) {
case 0:
   CaseZero();
   break;
case 1:
   CaseOne();
   break;
default:
   CaseOthers();
   break;
}

之所以有效,是因为没有任何开关部分具有可到达的端点。与C和C ++不同,不允许执行切换部分以“掉入”到下一个切换部分,该示例

switch (i) {
case 0:
   CaseZero();
case 1:
   CaseZeroOrOne();
default:
   CaseAny();
}

导致编译时错误。当执行一个切换部分之后又执行另一个切换部分时,必须使用显式的goto情况或goto默认语句:

switch (i) {
case 0:
   CaseZero();
   goto case 1;
case 1:
   CaseZeroOrOne();
   goto default;
default:
   CaseAny();
   break;
}

开关部分允许使用多个标签。这个例子

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几乎可以避免这种情况(尽管我在这里不认为它“糟糕”,它正在扮演一种特定的,结构化的角色)。在您的示例中,由于已将case主体包装在函数中(一件好事),因此case 0可以变为CaseZero(); CaseZeroOrOne(); break;。没有goto必需的。
ToolmakerSteve

链接是半断开的(重定向,“ Visual Studio 2003退休技术文档”)。
Peter Mortensen

2

似乎已经进行了大量工作,以寻找使C#最少使用的语法之一看起来更好或工作更好的方法。我个人认为switch语句很少值得使用。我强烈建议您分析要测试的数据以及所需的最终结果。

例如,假设您要在已知范围内快速测试值以查看它们是否为质数。您希望避免让代码进行浪费的计算,并且可以在要在线的范围内找到素数列表。您可以使用大量的switch语句将每个值与已知质数进行比较。

或者,您可以创建素数的数组映射并立即获得结果:

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

也许您想查看字符串中的字符是否为十六进制。您可以使用冗长的开关声明。

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

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

假设您要执行3个不同的操作之一,具体取决于一个介于1到24之间的值。我建议使用一组IF语句。并且如果这变得太复杂(或者数字较大,例如5个不同的操作,具体取决于1到90范围内的值),则使用枚举定义操作并创建枚举的数组映射。然后,该值将用于索引数组映射并获取所需动作的枚举。然后,使用一小组IF语句或非常简单的switch语句来处理结果枚举值。

另外,关于将一系列值转换为动作的数组映射的好处是,可以通过代码轻松地对其进行更改。使用硬连接的代码,您无法在运行时轻松更改行为,但使用数组映射则很容易。


您还可以映射到lambda表达式或代表
Conrad Frix 2012年

好点。一个小评论:与数组映射相比,我通常更容易维护匹配给定情况的值列表。数组映射的问题是容易犯错误。例如,除了具有true / false的primes数组映射之外,还可以简单地列出一个质数,然后将它们加载到HashSet中以提高查找性能。即使存在两种以上的情况,通常,除了一种情况外,其他所有情况都是一个很小的列表,因此可以用代码从其他情况的列表中构建一个HashSet枚举(如果稀疏)或一个数组映射。
ToolmakerSteve

1

只是为了添加到对话中,使用.NET 4.6.2,我还可以执行以下操作。我测试了代码,它确实为我工作。

您还可以执行多个“ OR”语句,如下所示:

            switch (value)
            {
                case string a when a.Contains("text1"):
                    // Do Something
                    break;
                case string b when b.Contains("text3") || b.Contains("text4") || b.Contains("text5"):
                    // Do Something else
                    break;
                default:
                    // Or do this by default
                    break;
            }

您还可以检查它是否与数组中的值匹配:

            string[] statuses = { "text3", "text4", "text5"};

            switch (value)
            {
                case string a when a.Contains("text1"):
                    // Do Something
                    break;
                case string b when statuses.Contains(value):                        
                    // Do Something else
                    break;
                default:
                    // Or do this by default
                    break;
            }

这不依赖于C#版本,而不是.NET版本吗?
彼得·莫滕森

1

如果您有大量的字符串(或任何其他类型)都在做相同的事情,则建议将字符串列表与string.Contains属性结合使用。

因此,如果您有一个很大的switch语句,例如:

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语句替换它:

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

这对于任何数量的字符串情况都可以很好地扩展。



-5

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

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

7
@scone goto打破了过程编程的基本原理(其中c ++和c#仍然植根于其中;它们不是纯粹的OO语言(感谢上帝))。过程编程具有由语言构造和方法调用约定(运行时堆栈如何增长和收缩)确定的定义明确的逻辑流。goto语句基本上允许任意跳转来规避此流程。
samis 2012年

1
我说的并不是说这是一种好风格,但它确实满足了原始问题的要求。
烤饼

2
不,它不会“执行原始问题的要求”。原始问题的代码按原样工作。他们不需要修复它。即使他们这样做了,这也是一个可怕的建议。它不太简洁,并且使用goto。更糟糕的是,它完全没有必要使用goto,因为OP声明的原始语法确实有效。问题是是否有更简洁的方式来处理其他案件。就像人们早于您回答的那样,是的-如果您愿意将几种情况放在一行上case 1: case 2:,并且如果编辑器的自动样式允许的话。
ToolmakerSteve

确定goto的性能不好的唯一原因是因为某些人发现很难遵循逻辑流程。.Net MSIL(汇编对象代码)使用goto的原因是它很快,但是如果可以编写.Net代码并在没有它们的情况下表现出色,则最好不要使用它们,这样您就不会被@这样的人惹恼ToolmakerSteve的居高临下的答复。
dynamiclynk

@wchoward-请仔细阅读我的回复。我的抱怨不仅仅是关于goto的使用。我反对,因为该问题显示的代码已经按原样工作,并且此答案a)占用了该工作代码,并使它变得更加冗长,结构欠佳,无济于事,b)没有回答问题。
制造商史蒂夫
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.