Почему «переключатель возврата x» генерирует «if (1 == 0)» в ILSpy?

Из любопытства я использовал 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; можешь выложить ИЛ? если я запускаю его через Sharplab, я этого не вижу (ни в IL, ни в декомпилированном C#) - также укажите, находитесь ли вы в отладочной или релизной сборке (это важно)

Marc Gravell 21.06.2024 17:40

Вы должны включить фактически сгенерированный IL, о котором вы говорите.

Mark Benningfield 21.06.2024 17:40

Это не ИЛ. Похоже, вы взяли исходный код, скомпилировали его, а затем декомпилировали обратно в C#.

phuzi 21.06.2024 17:56

@phuzi Плохо, я новичок в использовании ILSpy и не видел, чтобы в нем было меню для выбора между IL и C#. Мне было интересно, почему IL похож на C#...

Arkane 21.06.2024 18:32

@Arkane, как выглядит классический оператор переключения?

Ivan Petrov 21.06.2024 19:14

@IvanPetrov: Похоже на switch (name) { case "General Kenobi": return "Hello there"; } и так далее. В результирующем IL нет двух пустых операторов.

Arkane 23.06.2024 22:55

@Arkane Я отредактировал свой ответ, надеюсь, он достаточно конкретен, чтобы ты его принял

Ivan Petrov 24.06.2024 00:34

Если вы выполните пошаговый код, через какие строки пройдет текущая инструкция....? Отладочные сборки могут включать дополнительный код только для выделения строк исходного кода.

Jeremy Lakeman 24.06.2024 02:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
114
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Скорее всего, для целей отладки, созданных компилятором 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 другому, это другой вопрос, на который не могут ответить многие люди, не входящие в команду компилятора.

Фиктивный код для точек останова имеет смысл. Спасибо!

Arkane 24.06.2024 11:23

Другие вопросы по теме