Из любопытства я использовал ILSpy в своем коде и заметил, что вокруг if (1 == 0)
операторов были добавлены return x switch
пустые инструкции.
Вот пример такого поведения:
public static string TestReturnSwitch(string name)
{
return name switch
{
"General Kenobi" => "Hello there!",
"Inigo Montoya" => "My name is Inigo Montoya [...]",
"T800" => "Sarah Connor?",
_ => "???"
};
}
Приведенный выше код в ILSpy преобразуется в:
public static string TestReturnSwitch(string name)
{
if (1 == 0)
{
}
string result = name switch
{
"General Kenobi" => "Hello there!",
"Inigo Montoya" => "My name is Inigo Montoya [...]",
"T800" => "Sarah Connor?",
_ => "???",
};
if (1 == 0)
{
}
return result;
}
И часть ИЛ (для первого if (1 == 0)
):
// if (1 == 0)
IL_0001: ldc.i4.1
IL_0002: brtrue.s IL_0005
Однако, если я изменю оператор return switch
на классический оператор switch case
, в результирующем коде не будет странного if (1 == 0)
. Фактически, это становится точно таким же кодом, как и в исходном примере.
То же самое происходит с ILSpy v.8.2 (в котором используется .NET 6.0) и ILSpy v.9.0 Preview 2 (в котором используется .NET 8.0).
Обновлено: это происходит только при компиляции в Debug.
Почему эти инструкции добавлены для return switch
, а не для классической switch case
?
Вы должны включить фактически сгенерированный IL, о котором вы говорите.
Это не ИЛ. Похоже, вы взяли исходный код, скомпилировали его, а затем декомпилировали обратно в C#.
@phuzi Плохо, я новичок в использовании ILSpy и не видел, чтобы в нем было меню для выбора между IL и C#. Мне было интересно, почему IL похож на C#...
@Arkane, как выглядит классический оператор переключения?
@IvanPetrov: Похоже на switch (name) { case "General Kenobi": return "Hello there"; }
и так далее. В результирующем IL нет двух пустых операторов.
@Arkane Я отредактировал свой ответ, надеюсь, он достаточно конкретен, чтобы ты его принял
Если вы выполните пошаговый код, через какие строки пройдет текущая инструкция....? Отладочные сборки могут включать дополнительный код только для выделения строк исходного кода.
Скорее всего, для целей отладки, созданных компилятором C#.
На мой взгляд, лучший режим просмотра для ILSpy в таких ситуациях — IL с C#. Я воспроизвел сгенерированный код IL как:
IL_0000: nop
// if (1 == 0)
IL_0001: ldc.i4.1
IL_0002: brtrue.s IL_0005
// (no C# code)
IL_0004: nop
// string result = name switch
// {
// "General Kenobi" => "Hello there!",
// "Inigo Montoya" => "My name is Inigo Montoya [...]",
// "T800" => "Sarah Connor?",
// _ => "???",
// };
IL_0005: ldarg.0
IL_0006: ldstr "General Kenobi"
однако скомпилировано в режиме выпуска:
// {
IL_0000: ldarg.0
// (no C# code)
IL_0001: ldstr "General Kenobi"
IL_0006: call bool [System.Runtime]System.String::op_Equality(string, string)
// return name switch
// {
// "General Kenobi" => "Hello there!",
// "Inigo Montoya" => "My name is Inigo Montoya [...]",
// "T800" => "Sarah Connor?",
// _ => "???",
// };
IL_000b: brtrue.s IL_0029
Так же, как вы ожидаете.
Обычный оператор переключения по-прежнему создает "странный" код при отладочной компиляции, просто вы не заметили этого в обычном представлении только на C#.
Однако, если я изменю оператор возврата на классический переключатель case, в результирующем коде нет странного if (1 == 0). Фактически это становится точно таким же кодом, как и в исходном примере.
Это не совсем то же самое:
IL_0000: nop
// return name switch
// {
// "General Kenobi" => "Hello there!",
// "Inigo Montoya" => "My name is Inigo Montoya [...]",
// "T800" => "Sarah Connor?",
// _ => "???",
// };
IL_0001: ldarg.0
IL_0002: stloc.1
// (no C# code)
IL_0003: ldloc.1
IL_0004: stloc.0
IL_0005: ldloc.0
IL_0006: ldstr "General Kenobi"
Это примерно переводится как:
var localCompilerHelper1 = name;
var localCompilerHelper0 = localCompilerHelper1 ;
switch(localCompilerHelper0 )...
Нам не нужны эти фиктивные локальные переменные, мы можем просто использовать аргумент name
, как и в другом варианте переключателя.
Однако в обоих случаях мы начинаем настоящую работу с IL_0005.
IL_0005: ldarg.0 // this is "name" argument
IL_0006: ldstr "General Kenobi"
IL_0005: ldloc.0 // this is a local variable
IL_0006: ldstr "General Kenobi"
Итак, к вашему первоначальному вопросу
Почему эти инструкции добавлены для переключателя возврата, а не для переключателя возврата? классический корпус переключателя?
5 байтов фиктивного IL добавляются для обоих типов переключателей при компиляции в режиме отладки. Скорее всего, это позволит установить точку останова на {
, которая отмечает начало метода.
Почему компилятор предпочел один набор фиктивных IL другому, это другой вопрос, на который не могут ответить многие люди, не входящие в команду компилятора.
Фиктивный код для точек останова имеет смысл. Спасибо!
что здесь важно и необходимо для ответа на ваш вопрос, так это IL; можешь выложить ИЛ? если я запускаю его через Sharplab, я этого не вижу (ни в IL, ни в декомпилированном C#) - также укажите, находитесь ли вы в отладочной или релизной сборке (это важно)