В каком порядке переключаются операторы когда оцениваются предложения? - PullRequest
2 голосов
/ 08 ноября 2019

Рассмотрим следующий пример:

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

Не были бы оценены предложения when?

Ответы [ 2 ]

1 голос
/ 08 ноября 2019

Предложения Cases оцениваются в порядке, указанном в коде.

Переключение - это не более, чем последовательность, если затем, если еще, тогда и еще, и так далее, в кратком и более читаемом виде.

Таким образом, он прерывает синтаксический анализ при первом истинном условии, если вы ставите оператор break или return, или throw, или оператор goto, иначе они также проверяются последовательно.

Например, вы можете написать:

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://docs.microsoft.com/dotnet/api/system.reflection.emit.opcodes

0 голосов
/ 08 ноября 2019

Документация подтверждает, что порядок такой, как он написан в коде:

Выражения переключения вычисляются в текстовом порядке. Выполнение переходит к первой метке переключателя, которая соответствует выражению переключателя.

Хотя верно, что ваш второй пример не скомпилируется, как объяснил @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.

...