switch语句中带有when子句的执行顺序是怎样的?

4

考虑以下示例:

public static double ComputeArea_Version3(object shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
            return 0;

        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

when子句case Square s when s.Side == 0:是否保证在更通用的case Square s:之前执行? 是什么决定了这个顺序,是在switch中的位置吗? 如果我改为以下方式是否正确:

public static double ComputeArea_Version3(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;

        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
            return 0;

        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}
< p >这些< code >when条件从未被评估吗?< /p >

你已经调用了 return 行,因此它不会到达后续的 case 语句。 - Kristianne Nerona
2
我认为他在问当条件不被满足时,它们是否会按顺序进行。他想知道条件是否按顺序评估。答案是是的,情况是按提供的顺序考虑的。 - DetectivePikachu
@DetectivePikachu 你说得对。 - Drise
2个回答

4

根据代码所示的顺序,案例子句将被评估。

一个switch语句不过是一系列if-then-else if-then-else等语句的简洁且更易读的表述方式。

因此,如果您放置break、return、throw或goto语句,则它会在第一个true条件处中断解析,否则它们也会依次检查。

例如,您可以编写:

case 0:
case 1:
  DoSomething();
  break;

但你不能这样写:

case 0:
  DoSomething();
  // break or return or throw or goto needed here
case 1:
  DoAnotherSomething();
  break;

您可以使用一些示例进行逐行调试以进行检查。

您的第二个示例无法编译,因为条件必须按照特定到一般的顺序放置。

例如:

object instance = new Form();
switch ( instance )
{
  case Form f when f.Text == "Test":
    return;
  case Form f:
    return;
  case Label l:
    return;
}

生成的IL代码:

// object obj = new Form();
IL_0001: newobj instance void [System.Windows.Forms]System.Windows.Forms.Form::.ctor()
IL_0006: stloc.0
// object obj2 = obj;
IL_0007: ldloc.0
IL_0008: stloc.s 7
// object obj3 = obj2;
IL_000a: ldloc.s 7
IL_000c: stloc.1
// if (obj3 == null)
IL_000d: ldloc.1
// (no C# code)
IL_000e: brtrue.s IL_0012

// }
IL_0010: br.s IL_002c

// if ((form = (obj3 as Form)) == null)
IL_0012: ldloc.1
IL_0013: isinst [System.Windows.Forms]System.Windows.Forms.Form
// (no C# code)
IL_0018: dup
IL_0019: stloc.2
IL_001a: brfalse.s IL_0020

IL_001c: br.s IL_002e

IL_001e: br.s IL_0048

// if ((label = (obj3 as Label)) != null)
IL_0020: ldloc.1
IL_0021: isinst [System.Windows.Forms]System.Windows.Forms.Label
// (no C# code)
IL_0026: dup
IL_0027: stloc.3
IL_0028: brfalse.s IL_002c

IL_002a: br.s IL_004f

IL_002c: br.s IL_0056

// Form form2 = form;
IL_002e: ldloc.2
IL_002f: stloc.s 4
// if (!(form2.Text == "Test"))
IL_0031: ldloc.s 4
IL_0033: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
IL_0038: ldstr "Test"
IL_003d: call bool [mscorlib]System.String::op_Equality(string, string)
// (no C# code)
IL_0042: brtrue.s IL_0046

IL_0044: br.s IL_001e

IL_0046: br.s IL_0056

// Form form3 = form;
IL_0048: ldloc.2
IL_0049: stloc.s 5
// (no C# code)
IL_004b: br.s IL_004d

IL_004d: br.s IL_0056

// Label label2 = label;
IL_004f: ldloc.3
IL_0050: stloc.s 6
// (no C# code)
IL_0052: br.s IL_0054

IL_0054: br.s IL_0056

IL_0056: ret

您可以看到,这段代码是一系列测试和分支。

https://learn.microsoft.com/dotnet/api/system.reflection.emit.opcodes


1
我认为你的回答并不完全正确。据我所知,C# 可以为常量值 switch 语句生成哈希表,这应该使得顺序变得无关紧要。 - Drise
它只会为大于7的情况生成一个字典。无论如何排序都是“无意义”的。 - DetectivePikachu
1
我不知道编译器会在第二个例子中产生错误“该switch case已经被前一个case处理过了。”。说得好。 - Drise

1

文档确认代码中的顺序是按照其编写顺序进行评估的:

switch表达式按文本顺序进行评估。执行将转移到与switch表达式匹配的第一个switch标签。

虽然@OlivierRogier在他的答案中解释了你的第二个示例无法编译的原因,但顺序仍然很重要。考虑以下代码片段:

object o = 42;

switch (o)
{
   case int i when i > 10:
       Console.WriteLine("Greater 10");
       break;
   case int j when j > 20:
       Console.WriteLine("Greater 20");
       break;
}

这将打印出Greater 10。但是如果你交换(没有双关语)这两种情况,则会……
switch (o)
{
   case int j when j > 20:
       Console.WriteLine("Greater 20");
       break;
   case int i when i > 10:
       Console.WriteLine("Greater 10");
       break;
}

它仍然可以编译,但会打印出 Greater 20

1
重要的是要解释这个编译错误何时出现:“switch case已经被前一个case处理过了。” - Fabjan
1
有趣的事实是,您可以重复相同的 case when,编译器不会介意,因为它只检查是否已经有一个 case 涵盖了 case when,但实际上并不检查 when 部分。 - juharr

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