Thursday, July 3, 2014

Так ли прост оператор switch

В этой статье мы рассмотрим, так ли прост оператор switch в языке C#. Если вы знаете всю подноготную работы оператора, то, вероятно, ничего нового из этой статьи не почерпнете. Но постараюсь удивить некоторыми нюансами, с которыми вы могли раньше не сталкиваться. 
Немного теоретической базы. Оператору switch, специфике его работы посвящена отдельная статья в MSDN. Но эта статья рассказывает только о внешнем представлении данного оператора, упуская из виду то, как он представлен на низком уровне. 
Оператор switch ­­– это оператор управления, который выбирает из списка возможных вариантов секцию переключения (switch section) для выполнения содержащегося в нем кода. Оператор switch включает один или несколько разделов switch, и каждый из этих разделов содержит одну или несколько веток case. Каждая ветка case содержит значение-константу. Оператор switch передает управление тому разделу, метка case которого совпадает со значением выражения switch. Если ни одна из меток case не содержит совпадающего значения, управление передаётся в раздел default (значение по умолчанию), если таковой имеется. Если же раздела default нет, никакие действия не предпринимаются и управление передаётся за пределы оператора switch.
Оператор switch может содержать любое количество разделов switch, а каждый раздел может иметь одну или несколько меток case (как показано в этом примере ниже). Однако две метки case не могут содержать одно и то же постоянное значение.
Выполнение списка операторов в выбранном разделе switch начинается с первого оператора и продолжается по списку, обычно до достижения оператора перехода, такого как break, goto case, return или throw. В этой точке управление передаётся за пределы оператора switch или к другой метке case.
Давайте создадим пример SwitchOperatorSample, в котором рассмотрим всю эту логику.
Небольшое замечание. В отличие от языка C++, в C# не допускается такая ситуация, чтобы выполнение началось в одном разделе и продолжалось во втором. Например, приведенный ниже код является невалидным для языка C#.
static void Main(string[] args)
{
    int caseSwitch = 1;
    switch (caseSwitch)
    {
        // The following switch section causes an error.
        case 1:
            Console.WriteLine("Case 1...");
        // Add a break or other jump statement here.
        case 2:
            Console.WriteLine("... and/or Case 2");
            break;
    }
}
Среда разработки сама подсветит ошибку, что у вас не хватает оператора break. Единственное исключение – это использование оператора goto, который позволяет изменять переход в программе. Например, следующий код вызовет выполнение двух блоков case.
static void Main(string[] args)
{
    int caseSwitch = 1;
    switch (caseSwitch)
    {
        // The following switch section causes an error.
        case 1:
            Console.WriteLine("Case 1...");
        // Add a break or other jump statement here.
            goto label;
            break;
        case 2:
    label:
            Console.WriteLine("... and/or Case 2");
            break;
    }
    Console.ReadLine();
}
Старайтесь избегать использования оператора перехода goto, так как он запутывает программу; в большинстве случаев использование данного оператора говорит о непродуманной архитектуре программы. Давайте рассмотрим простой пример использования оператора switch, а вы попробуете рассказать о том, как происходит сравнение в этом операторе. Имеем простой пример, который выведет на экран строку "555".
static void Main(string[] args)
{
    string search = "555";
    switch (search)
    {
        case "111":
            Console.WriteLine("111");
            break;
        case "222":
            Console.WriteLine("222");
            break;
        case "333":
            Console.WriteLine("333");
            break;
        case "444":
            Console.WriteLine("444");
            break;
        case "555":
            Console.WriteLine("555");
            break;
        case "666":
            Console.WriteLine("666");
            break;
    }
    Console.ReadLine();
}
Как видите, логика проще некуда. Вопрос: как происходит поиск и нужное сравнение в операторе switch? Если вы знаете, что такое таблица переходов и как она относится вообще к оператору switch, – вам сразу плюс в карму. А если вы еще знаете, почему в приведенном выше примере эта таблица не будет создана, тогда вам двойной плюс в карму. Если же для вас таблица переходов, которую часто называют jump table незнакомое понятие, а второй вопрос ни о чем не говорит, тогда добро пожаловать в мир удивительного оператора switch.
Давайте посмотрим, что магического есть в приведенном коде. Во-первых, наш код выше интерпретируется в IL-код следующего вида:
.method private hidebysig static
        void Main (
            string[] args
        ) cil managed
    {
        // Method begins at RVA 0x2050
        // Code size 180 (0xb4)
        .maxstack 2
        .entrypoint
        .locals init (
            [0] string search,
            [1] string CS$4$0000
        )

        IL_0000: nop
        IL_0001: ldstr "555"
        IL_0006: stloc.0
        IL_0007: ldloc.0
        IL_0008: stloc.1
        IL_0009: ldloc.1
        IL_000a: brfalse IL_00ad

        IL_000f: ldloc.1
        IL_0010: ldstr "111"
        IL_0015: call bool [mscorlib]System.String::op_Equality(stringstring)
        IL_001a: brtrue.s IL_005f

        IL_001c: ldloc.1
        IL_001d: ldstr "222"
        IL_0022: call bool [mscorlib]System.String::op_Equality(stringstring)
        IL_0027: brtrue.s IL_006c

        IL_0029: ldloc.1
        IL_002a: ldstr "333"
        IL_002f: call bool [mscorlib]System.String::op_Equality(stringstring)
        IL_0034: brtrue.s IL_0079

        IL_0036: ldloc.1
        IL_0037: ldstr "444"
        IL_003c: call bool [mscorlib]System.String::op_Equality(stringstring)
        IL_0041: brtrue.s IL_0086

        IL_0043: ldloc.1
        IL_0044: ldstr "555"
        IL_0049: call bool [mscorlib]System.String::op_Equality(stringstring)
        IL_004e: brtrue.s IL_0093

        IL_0050: ldloc.1
        IL_0051: ldstr "666"
        IL_0056: call bool [mscorlib]System.String::op_Equality(stringstring)
        IL_005b: brtrue.s IL_00a0

        IL_005d: br.s IL_00ad

        IL_005f: ldstr "111"
        IL_0064: call void [mscorlib]System.Console::WriteLine(string)
        IL_0069: nop
        IL_006a: br.s IL_00ad

        IL_006c: ldstr "222"
        IL_0071: call void [mscorlib]System.Console::WriteLine(string)
        IL_0076: nop
        IL_0077: br.s IL_00ad

        IL_0079: ldstr "333"
        IL_007e: call void [mscorlib]System.Console::WriteLine(string)
        IL_0083: nop
        IL_0084: br.s IL_00ad

        IL_0086: ldstr "444"
        IL_008b: call void [mscorlib]System.Console::WriteLine(string)
        IL_0090: nop
        IL_0091: br.s IL_00ad

        IL_0093: ldstr "555"
        IL_0098: call void [mscorlib]System.Console::WriteLine(string)
        IL_009d: nop
        IL_009e: br.s IL_00ad

        IL_00a0: ldstr "666"
        IL_00a5: call void [mscorlib]System.Console::WriteLine(string)
        IL_00aa: nop
        IL_00ab: br.s IL_00ad

        IL_00ad: call string [mscorlib]System.Console::ReadLine()
        IL_00b2: pop
        IL_00b3: ret
    } // end of method Program::Main
Если вы не понимаете принципа работы IL-кода, это нестрашно. В данном примере наша строка "555" поочередно сравнивается со строками, которые в операторе case проверяют строки на равенство.
IL_0022: call bool [mscorlib]System.String::op_Equality(stringstring)
В итоге, для нашего примера будет произведено 5 сравнений, что нас не очень устраивает. Оказывается, если в нашем операторе switch до 7 выборов (операторов case), компилятор языка C# не строит никакую таблицу переходов. Поэтому, по сути, никакой оптимизации не будет. Разницы с тем, если бы вы написали 6 операторов if не было бы, по сути. Но давайте в наш оператор добавим 7-й оператор case и посмотрим, что из этого получится.
static void Main(string[] args)
{
    string search = "555";
    switch (search)
    {
        case "111":
            Console.WriteLine("111");
            break;
        case "222":
            Console.WriteLine("222");
            break;
        case "333":
            Console.WriteLine("333");
            break;
        case "444":
            Console.WriteLine("444");
            break;
        case "555":
            Console.WriteLine("555");
            break;
        case "666":
            Console.WriteLine("666");
            break;
        case "777":
            Console.WriteLine("777");
            break;
    }
    Console.ReadLine();
}
Теперь интересно взглянуть, что же нам сгенерирует компилятор языка C# с учетом внесенных изменений.
.method private hidebysig static
    void Main (
        string[] args
    ) cil managed
{
    // Method begins at RVA 0x2050
    // Code size 272 (0x110)
    .maxstack 4
    .entrypoint
    .locals init (
        [0] string search,
        [1] string CS$4$0000,
        [2] int32 CS$0$0001
    )

    IL_0000: nop
    IL_0001: ldstr "555"
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: stloc.1
    IL_0009: ldloc.1
    IL_000a: brfalse IL_0109

    IL_000f: volatile.
    IL_0011: ldsfld class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32> '<PrivateImplementationDetails>{25C81403-A6B7-49D8-8E60-800241F73CA5}'::'$$method0x6000001-1'
    IL_0016: brtrue.s IL_0079

    IL_0018: ldc.i4.7
    IL_0019: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32>::.ctor(int32)
    IL_001e: dup
    IL_001f: ldstr "111"
    IL_0024: ldc.i4.0
    IL_0025: call instance void class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32>::Add(!0, !1)
    IL_002a: dup
    IL_002b: ldstr "222"
    IL_0030: ldc.i4.1
    IL_0031: call instance void class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32>::Add(!0, !1)
    IL_0036: dup
    IL_0037: ldstr "333"
    IL_003c: ldc.i4.2
    IL_003d: call instance void class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32>::Add(!0, !1)
    IL_0042: dup
    IL_0043: ldstr "444"
    IL_0048: ldc.i4.3
    IL_0049: call instance void class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32>::Add(!0, !1)
    IL_004e: dup
    IL_004f: ldstr "555"
    IL_0054: ldc.i4.4
    IL_0055: call instance void class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32>::Add(!0, !1)
    IL_005a: dup
    IL_005b: ldstr "666"
    IL_0060: ldc.i4.5
    IL_0061: call instance void class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32>::Add(!0, !1)
    IL_0066: dup
    IL_0067: ldstr "777"
    IL_006c: ldc.i4.6
    IL_006d: call instance void class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32>::Add(!0, !1)
    IL_0072: volatile.
    IL_0074: stsfld class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32> '<PrivateImplementationDetails>{25C81403-A6B7-49D8-8E60-800241F73CA5}'::'$$method0x6000001-1'

    IL_0079: volatile.
    IL_007b: ldsfld class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32> '<PrivateImplementationDetails>{25C81403-A6B7-49D8-8E60-800241F73CA5}'::'$$method0x6000001-1'
    IL_0080: ldloc.1
    IL_0081: ldloca.s CS$0$0001
    IL_0083: call instance bool class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32>::TryGetValue(!0, !1&)
    IL_0088: brfalse.s IL_0109

    IL_008a: ldloc.2
    IL_008b: switch (IL_00ae, IL_00bb, IL_00c8, IL_00d5, IL_00e2, IL_00ef, IL_00fc)

    IL_00ac: br.s IL_0109

    IL_00ae: ldstr "111"
    IL_00b3: call void [mscorlib]System.Console::WriteLine(string)
    IL_00b8: nop
    IL_00b9: br.s IL_0109

    IL_00bb: ldstr "222"
    IL_00c0: call void [mscorlib]System.Console::WriteLine(string)
    IL_00c5: nop
    IL_00c6: br.s IL_0109

    IL_00c8: ldstr "333"
    IL_00cd: call void [mscorlib]System.Console::WriteLine(string)
    IL_00d2: nop
    IL_00d3: br.s IL_0109

    IL_00d5: ldstr "444"
    IL_00da: call void [mscorlib]System.Console::WriteLine(string)
    IL_00df: nop
    IL_00e0: br.s IL_0109

    IL_00e2: ldstr "555"
    IL_00e7: call void [mscorlib]System.Console::WriteLine(string)
    IL_00ec: nop
    IL_00ed: br.s IL_0109

    IL_00ef: ldstr "666"
    IL_00f4: call void [mscorlib]System.Console::WriteLine(string)
    IL_00f9: nop
    IL_00fa: br.s IL_0109

    IL_00fc: ldstr "777"
    IL_0101: call void [mscorlib]System.Console::WriteLine(string)
    IL_0106: nop
    IL_0107: br.s IL_0109

    IL_0109: call string [mscorlib]System.Console::ReadLine()
    IL_010e: pop
    IL_010f: ret
// end of method Program::Main
Если посмотреть на код, то даже если вы ни когда не сталкивались с IL-кодом, вам как разработчику на языке C# должно быть знакомо создание словаря Dictionary, что мы можем увидеть в коде.
IL_0019: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<stringint32>::.ctor(int32)
Затем в этом мы для каждого значения создаем таблицу переходов.
На рисунке, добавленном выше, показано, как построена таблица переходов. (Для того чтобы посомотреть IL-код, я использовал бесплатный продукт ILSpy).
Если вы посмотрите код, который идет с самого начала, то увидите, что первым делом у нас сравнивается на ложность.
IL_0000: nop
    IL_0001: ldstr "555"
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: stloc.1
    IL_0009: ldloc.1
    IL_000a: brfalse IL_0109
Если у нас нет значения "555", то мы сразу переходим по метке IL_0109, то есть в конец нашей функции на метод Console.ReadLine().
Давайте теперь немного перепишем наш пример для работы с целыми числами.
static void Main(string[] args)
{
    int search = 5;
    switch (search)
    {
        case 1:
            Console.WriteLine("111");
            break;
        case 2:
            Console.WriteLine("222");
            break;
        case 3:
            Console.WriteLine("333");
            break;
        case 4:
            Console.WriteLine("444");
            break;
        case 5:
            Console.WriteLine("555");
            break;
        case 6:
            Console.WriteLine("666");
            break;
        case 8:
            Console.WriteLine("777");
            break;
    }
    Console.ReadLine();
}
По сути, внесены лишь небольшие изменения в наш предыдущий код. Но изменения в коде произошли серьезные. Поскольку мы работаем не со строками, нам не нужно высчитывать хеш для строк, поэтому словарь у нас создан не будет.
.method private hidebysig static
    void Main (
        string[] args
    ) cil managed
{
    // Method begins at RVA 0x2050
    // Code size 145 (0x91)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] int32 search,
        [1] int32 CS$4$0000
    )

    IL_0000: nop
    IL_0001: ldc.i4.5
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: stloc.1
    IL_0005: ldloc.1
    IL_0006: ldc.i4.1
    IL_0007: sub
    IL_0008: switch (IL_002f, IL_003c, IL_0049, IL_0056, IL_0063, IL_0070, IL_008a, IL_007d)

    IL_002d: br.s IL_008a

    IL_002f: ldstr "111"
    IL_0034: call void [mscorlib]System.Console::WriteLine(string)
    IL_0039: nop
    IL_003a: br.s IL_008a

    IL_003c: ldstr "222"
    IL_0041: call void [mscorlib]System.Console::WriteLine(string)
    IL_0046: nop
    IL_0047: br.s IL_008a

    IL_0049: ldstr "333"
    IL_004e: call void [mscorlib]System.Console::WriteLine(string)
    IL_0053: nop
    IL_0054: br.s IL_008a

    IL_0056: ldstr "444"
    IL_005b: call void [mscorlib]System.Console::WriteLine(string)
    IL_0060: nop
    IL_0061: br.s IL_008a

    IL_0063: ldstr "555"
    IL_0068: call void [mscorlib]System.Console::WriteLine(string)
    IL_006d: nop
    IL_006e: br.s IL_008a

    IL_0070: ldstr "666"
    IL_0075: call void [mscorlib]System.Console::WriteLine(string)
    IL_007a: nop
    IL_007b: br.s IL_008a

    IL_007d: ldstr "777"
    IL_0082: call void [mscorlib]System.Console::WriteLine(string)
    IL_0087: nop
    IL_0088: br.s IL_008a

    IL_008a: call string [mscorlib]System.Console::ReadLine()
    IL_008f: pop
    IL_0090: ret
// end of method Program::Main
Теперь прошу обратить внимание на оператор switch в данном коде.
IL_0008: switch (IL_0033, IL_0040, IL_004d, IL_005a, IL_0067, IL_0074, IL_009b, IL_0081, IL_008e)
В нем восемь значений создано для таблицы переходов, хотя у нас операторов case всего лишь 7. Все дело в том, что если мы посмотрим на последовательность в нашем операторе switch, то получим вот такой список:
Вот, по сути, мысленно представленная наша таблица переходов. Поскольку у нас только 7 значений, а между значениями 6 и 8 у нас находится число 7, которое у нас отсутствует, компилятор генерирует автоматически пустой переход в конец метода. Поэтому у нас сгенерировано в операторе switch 8 переходов: 7 переходов к конкретным значениям и один пустой заполнитель для перехода в конец метода Main. Внимательные разработчики сразу смекнут такую вещь. То есть, возникает вопрос: если мы вместо 8 поставим, например, 100,  то будет сгенерировано 93 заполнителя?
static void Main(string[] args)
{
    int search = 5;
    switch (search)
    {
        case 1:
            Console.WriteLine("111");
            break;
        case 2:
            Console.WriteLine("222");
            break;
        case 3:
            Console.WriteLine("333");
            break;
        case 4:
            Console.WriteLine("444");
            break;
        case 5:
            Console.WriteLine("555");
            break;
        case 6:
            Console.WriteLine("666");
            break;
        case 100:
            Console.WriteLine("777");
            break;
    }
    Console.ReadLine();
}
Сразу смею вас огорчить. Не будет сгенерировано 93 заполнителя. Компилятор представляет собой достаточно умную вещь, которая способна оптимизировать ваш код к такому виду:
.method private hidebysig static
    void Main (
        string[] args
    ) cil managed
{
    // Method begins at RVA 0x2050
    // Code size 142 (0x8e)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] int32 search,
        [1] int32 CS$4$0000
    )

    IL_0000: nop
    IL_0001: ldc.i4.5
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: stloc.1
    IL_0005: ldloc.1
    IL_0006: ldc.i4.1
    IL_0007: sub
    IL_0008: switch (IL_002c, IL_0039, IL_0046, IL_0053, IL_0060, IL_006d)

    IL_0025: ldloc.1
    IL_0026: ldc.i4.s 100
    IL_0028: beq.s IL_007a

    IL_002a: br.s IL_0087

    IL_002c: ldstr "111"
    IL_0031: call void [mscorlib]System.Console::WriteLine(string)
    IL_0036: nop
    IL_0037: br.s IL_0087

    IL_0039: ldstr "222"
    IL_003e: call void [mscorlib]System.Console::WriteLine(string)
    IL_0043: nop
    IL_0044: br.s IL_0087

    IL_0046: ldstr "333"
    IL_004b: call void [mscorlib]System.Console::WriteLine(string)
    IL_0050: nop
    IL_0051: br.s IL_0087

    IL_0053: ldstr "444"
    IL_0058: call void [mscorlib]System.Console::WriteLine(string)
    IL_005d: nop
    IL_005e: br.s IL_0087

    IL_0060: ldstr "555"
    IL_0065: call void [mscorlib]System.Console::WriteLine(string)
    IL_006a: nop
    IL_006b: br.s IL_0087

    IL_006d: ldstr "666"
    IL_0072: call void [mscorlib]System.Console::WriteLine(string)
    IL_0077: nop
    IL_0078: br.s IL_0087

    IL_007a: ldstr "777"
    IL_007f: call void [mscorlib]System.Console::WriteLine(string)
    IL_0084: nop
    IL_0085: br.s IL_0087

    IL_0087: call string [mscorlib]System.Console::ReadLine()
    IL_008c: pop
    IL_008d: ret
// end of method Program::Main
Наш компилятор сгенерировал нам таблицу переходов для 6 значений от 1 до 6, по порядку.
IL_0008: switch (IL_002c, IL_0039, IL_0046, IL_0053, IL_0060, IL_006d)
А для значения 100 сделал обычный оператор if.
IL_0025: ldloc.1
IL_0026: ldc.i4.s 100
IL_0028: beq.s IL_007a
Не знаю, как вам, но когда я впервые узнал об этом, то был просто ошеломлен. Если вы добавите, например, case со значением 200, то получите две проверки с помощью if. Интересный момент: если вы перепишете код следующим образом от 1 до 6 и с 100 до 101:
static void Main(string[] args)
{
    int search = 5;
    switch (search)
    {
        case 1:
            Console.WriteLine("111");
            break;
        case 2:
            Console.WriteLine("222");
            break;
        case 3:
            Console.WriteLine("333");
            break;
        case 4:
            Console.WriteLine("444");
            break;
        case 5:
            Console.WriteLine("555");
            break;
        case 6:
            Console.WriteLine("666");
            break;
        case 100:
            Console.WriteLine("777");
            break;
        case 101:
            Console.WriteLine("800");
            break;
    }
    Console.ReadLine();
}
У вас будет сгенерировано две таблицы переходов.
.method private hidebysig static
    void Main (
        string[] args
    ) cil managed
{
    // Method begins at RVA 0x2050
    // Code size 167 (0xa7)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] int32 search,
        [1] int32 CS$4$0000
    )

    IL_0000: nop
    IL_0001: ldc.i4.5
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: stloc.1
    IL_0005: ldloc.1
    IL_0006: ldc.i4.1
    IL_0007: sub
    IL_0008: switch (IL_0038, IL_0045, IL_0052, IL_005f, IL_006c, IL_0079)

    IL_0025: ldloc.1
    IL_0026: ldc.i4.s 100
    IL_0028: sub
    IL_0029: switch (IL_0086, IL_0093)

    IL_0036: br.s IL_00a0

    IL_0038: ldstr "111"
    IL_003d: call void [mscorlib]System.Console::WriteLine(string)
    IL_0042: nop
    IL_0043: br.s IL_00a0

    IL_0045: ldstr "222"
    IL_004a: call void [mscorlib]System.Console::WriteLine(string)
    IL_004f: nop
    IL_0050: br.s IL_00a0

    IL_0052: ldstr "333"
    IL_0057: call void [mscorlib]System.Console::WriteLine(string)
    IL_005c: nop
    IL_005d: br.s IL_00a0

    IL_005f: ldstr "444"
    IL_0064: call void [mscorlib]System.Console::WriteLine(string)
    IL_0069: nop
    IL_006a: br.s IL_00a0

    IL_006c: ldstr "555"
    IL_0071: call void [mscorlib]System.Console::WriteLine(string)
    IL_0076: nop
    IL_0077: br.s IL_00a0

    IL_0079: ldstr "666"
    IL_007e: call void [mscorlib]System.Console::WriteLine(string)
    IL_0083: nop
    IL_0084: br.s IL_00a0

    IL_0086: ldstr "777"
    IL_008b: call void [mscorlib]System.Console::WriteLine(string)
    IL_0090: nop
    IL_0091: br.s IL_00a0

    IL_0093: ldstr "800"
    IL_0098: call void [mscorlib]System.Console::WriteLine(string)
    IL_009d: nop
    IL_009e: br.s IL_00a0

    IL_00a0: call string [mscorlib]System.Console::ReadLine()
    IL_00a5: pop
    IL_00a6: ret
// end of method Program::Main
Теперь осталось рассмотреть самое простое: использование оператора default.
static void Main(string[] args)
{
    int search = 5;
    switch (search)
    {
        case 1:
            Console.WriteLine("111");
            break;
        case 2:
            Console.WriteLine("222");
            break;
        default:
            Console.WriteLine("default");
            break;
 
    }
    Console.ReadLine();
}
Если посмотреть на IL-код, который нам генерирует компилятор, то можно увидеть, что для оператора default генерируется отдельный переход.
.method private hidebysig static
    void Main (
        string[] args
    ) cil managed
{
    // Method begins at RVA 0x2050
    // Code size 69 (0x45)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] int32 search,
        [1] int32 CS$4$0000
    )

    IL_0000: nop
    IL_0001: ldc.i4.5
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: stloc.1
    IL_0005: ldloc.1
    IL_0006: ldc.i4.1
    IL_0007: sub
    IL_0008: switch (IL_0017, IL_0024)

    IL_0015: br.s IL_0031

    IL_0017: ldstr "111"
    IL_001c: call void [mscorlib]System.Console::WriteLine(string)
    IL_0021: nop
    IL_0022: br.s IL_003e

    IL_0024: ldstr "222"
    IL_0029: call void [mscorlib]System.Console::WriteLine(string)
    IL_002e: nop
    IL_002f: br.s IL_003e

    IL_0031: ldstr "default"
    IL_0036: call void [mscorlib]System.Console::WriteLine(string)
    IL_003b: nop
    IL_003c: br.s IL_003e

    IL_003e: call string [mscorlib]System.Console::ReadLine()
    IL_0043: pop
    IL_0044: ret
// end of method Program::Main
Это мы можем увидеть в строке
IL_0015: br.s IL_0031
Если вы поиграетесь со стоковыми типами, то получите следующий результат. Если у вас операторов в switch будет ≥ 7, то у вас первым аргументом будет сравниваться ваше значение, и если у вас такого значения нет у вашей таблице переходов, у вас сразу будет переход в секцию default. Если же значений в операторе switch будет меньше 6, то поведение будет подобно переходу в последнем примере.

Надеюсь, что небольшой экскурс в оператор switch не был столь скучным и вы узнали для себя что-то новое, как когда-то узнал об этом я. Если у вас возникли какие-то вопросы по теме, буду рад на них ответить. 

No comments:

Post a Comment