В этой статье мы рассмотрим, так ли прост оператор switch в языке C#. Если вы знаете всю подноготную работы оператора, то, вероятно, ничего нового из этой статьи не почерпнете. Но постараюсь удивить некоторыми нюансами, с которыми вы могли раньше не
сталкиваться.
Немного теоретической базы. Оператору switch, специфике его работы посвящена отдельная статья в MSDN. Но эта статья рассказывает только о внешнем представлении данного оператора, упуская из виду то, как он представлен на низком уровне.
Оператор switch – это оператор управления, который выбирает из списка возможных вариантов секцию переключения (switch section) для выполнения содержащегося в нем кода. Оператор switch включает один или несколько разделов switch, и каждый из этих разделов содержит одну или несколько веток case. Каждая ветка case содержит значение-константу. Оператор switch передает управление тому разделу, метка case которого совпадает со значением выражения switch. Если ни одна из меток case не содержит совпадающего значения, управление передаётся в раздел default (значение по умолчанию), если таковой имеется. Если же раздела default нет, никакие действия не предпринимаются и управление передаётся за пределы оператора switch.
Немного теоретической базы. Оператору 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(string, string)
IL_001a: brtrue.s IL_005f
IL_001c: ldloc.1
IL_001d: ldstr "222"
IL_0022: call bool [mscorlib]System.String::op_Equality(string, string)
IL_0027: brtrue.s IL_006c
IL_0029: ldloc.1
IL_002a: ldstr "333"
IL_002f: call bool [mscorlib]System.String::op_Equality(string, string)
IL_0034: brtrue.s IL_0079
IL_0036: ldloc.1
IL_0037: ldstr "444"
IL_003c: call bool [mscorlib]System.String::op_Equality(string, string)
IL_0041: brtrue.s IL_0086
IL_0043: ldloc.1
IL_0044: ldstr "555"
IL_0049: call bool [mscorlib]System.String::op_Equality(string, string)
IL_004e: brtrue.s IL_0093
IL_0050: ldloc.1
IL_0051: ldstr "666"
IL_0056: call bool [mscorlib]System.String::op_Equality(string, string)
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
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(string, string)
IL_001a: brtrue.s IL_005f
IL_001c: ldloc.1
IL_001d: ldstr "222"
IL_0022: call bool [mscorlib]System.String::op_Equality(string, string)
IL_0027: brtrue.s IL_006c
IL_0029: ldloc.1
IL_002a: ldstr "333"
IL_002f: call bool [mscorlib]System.String::op_Equality(string, string)
IL_0034: brtrue.s IL_0079
IL_0036: ldloc.1
IL_0037: ldstr "444"
IL_003c: call bool [mscorlib]System.String::op_Equality(string, string)
IL_0041: brtrue.s IL_0086
IL_0043: ldloc.1
IL_0044: ldstr "555"
IL_0049: call bool [mscorlib]System.String::op_Equality(string, string)
IL_004e: brtrue.s IL_0093
IL_0050: ldloc.1
IL_0051: ldstr "666"
IL_0056: call bool [mscorlib]System.String::op_Equality(string, string)
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(string, string)
В итоге, для нашего примера будет произведено 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<string, int32> '<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<string, int32>::.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<string, int32>::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<string, int32>::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<string, int32>::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<string, int32>::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<string, int32>::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<string, int32>::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<string, int32>::Add(!0, !1)
IL_0072: volatile.
IL_0074: stsfld class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32> '<PrivateImplementationDetails>{25C81403-A6B7-49D8-8E60-800241F73CA5}'::'$$method0x6000001-1'
IL_0079: volatile.
IL_007b: ldsfld class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32> '<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<string, int32>::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
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<string, int32> '<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<string, int32>::.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<string, int32>::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<string, int32>::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<string, int32>::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<string, int32>::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<string, int32>::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<string, int32>::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<string, int32>::Add(!0, !1)
IL_0072: volatile.
IL_0074: stsfld class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32> '<PrivateImplementationDetails>{25C81403-A6B7-49D8-8E60-800241F73CA5}'::'$$method0x6000001-1'
IL_0079: volatile.
IL_007b: ldsfld class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32> '<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<string, int32>::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<string, int32>::.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
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
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, то получим вот такой список:
В нем восемь значений создано для таблицы переходов, хотя у нас операторов 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
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
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
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
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