none
Почему возврат из функции по return нелзя выполнить в блоке finally? RRS feed

  • Вопрос

  • Почему возврат из функции по return нелзя выполнить в блоке finally?

    /// <summary>
    /// Объект для реализации блокировки доступа к коду в критической секции.
    /// </summary>
    private static object lockOn = new object();
    
    . . . . . . . . . . . . . . . . . . . . . . . .
    
    int MyFunc()
    {
         lock (lockOn)
         {
              int errorCode = 0;
              try
              {
                   // Некоторые действия
                   . . . . . . . . . . . . . 
                   errorCode = 1;
              }
              catch(Exception e)
              {
                   // Обработка исключения
                   . . . . . . . . . . . . .
                   errorCode = -99;
              }
              finally
              {
                   return errorCode; // Здесь ошибка: "Управление не может быть передано из тела конструкции finally".
              }
         }
    { 
     

    Почему управление не может быть передано из тела конструкции finally ?
    3 декабря 2012 г. 15:36

Ответы

  • Чтобы не было недоразумений, вот такой код будет иметь логику аналогичную вашей:

    int MyFunc()
    {
        lock (lockOn)
        {
            int errorCode = 0;
            try
            {
                // Некоторые действия
                errorCode = 1;
            }
            catch (Exception e)
            {
                // Обработка исключения
                errorCode = -99;
            }
            return errorCode; 
        }
    }

    Но будет без ошибок. И, да, для отсутствия ошибок, как правило, используют не один, а ноль.
    • Помечено в качестве ответа TownSparrow 4 декабря 2012 г. 13:43
    4 декабря 2012 г. 6:46
    Отвечающий

Все ответы

  • Потому что код в блоке finally должен выполниться всегда! А если добавить после return какой-то код, то он уже не выполнится (из блока-то вышли!).
    • Предложено в качестве ответа YatajgaModerator 4 декабря 2012 г. 17:50
    3 декабря 2012 г. 22:23
  • Потому что код в блоке finally должен выполниться всегда! А если добавить после return какой-то код, то он уже не выполнится (из блока-то вышли!).

    Я думаю дело тут в другом. В блок finally попадаем уже когда возвращаемое значение заполнено и поэтому если ещё раз вызвать return в finally, то собственно это значение всегда и будет возвращаться.

    А по Вашей логике получается, что такой код тоже недопустим:

    finally { while (true) { break; SomeFunction(); // Никогда не выполнится

    } }




    • Изменено tulosba 4 декабря 2012 г. 19:44
    4 декабря 2012 г. 6:31
  • Чтобы не было недоразумений, вот такой код будет иметь логику аналогичную вашей:

    int MyFunc()
    {
        lock (lockOn)
        {
            int errorCode = 0;
            try
            {
                // Некоторые действия
                errorCode = 1;
            }
            catch (Exception e)
            {
                // Обработка исключения
                errorCode = -99;
            }
            return errorCode; 
        }
    }

    Но будет без ошибок. И, да, для отсутствия ошибок, как правило, используют не один, а ноль.
    • Помечено в качестве ответа TownSparrow 4 декабря 2012 г. 13:43
    4 декабря 2012 г. 6:46
    Отвечающий
  • Спасибо, Алексей. И остальным ребятам тоже спасибо. Я, в итоге, и сделал так, как вы, Алексей, посоветовали. А возвращение единички, в случае успеха, - я написал просто для наглядности (надо было конечно - ноль). В программе у меня, в этом случае, - ноль. Ещё раз спасибо всем большое.

    4 декабря 2012 г. 13:42
  • "Я думаю дело тут в другом. В блок finally попадаем уже когда возвращаемое значение заполнено и поэтому если ещё раз вызвать return в finally, то собственно это значение всегда и будет возвращаться." - что значит "возвращаемое значение уже заполнено"? Оператор return передаёт управление вызывающей инструкции, и следовательно после него никакой код не может выполниться. Что противоречит концепции C#, гласящей, что код в блоке finally должен обязательно выполниться.

    "А по Вашей логике получается, что такой код тоже недопустим: " - break и return совершенно разные инструкции. Первая передаёт выполнение на следующий блок, а вторая вне, уровнем высше. Последнее гарантирует, что последующий код не будет выполнен.

    4 декабря 2012 г. 18:03
    Модератор
  • > что значит "возвращаемое значение уже заполнено"?

    Mea culpa. Я предполагал, например такой код, если бы это было возможно конечно:

    try
    {
       return 1;
    }
    finally
    {
       return 2;
    }

    Т.е. по сути всегда бы возвращалась двойка.

    >Оператор return передаёт управление вызывающей инструкции, и следовательно после него никакой код не может выполниться. Что противоречит концепции C#, гласящей, что код в блоке finally должен обязательно выполниться.

    Чем, с точки зрения выполняемых инструкций отличается код:

    finally
    {
       while (true)
       {
          break;
          SomeFunction(); // Никогда не выполнится
       }
    }
    от
    finally
    {
       return;
       SomeFunction(); // Никогда не выполнится
    }
    ? Опять таки, если бы это было возможно скомпилировать.

    4 декабря 2012 г. 19:43
  • "Чем, с точки зрения выполняемых инструкций отличается код:"

    finally
    {
       while (true)
       {
          break;
       }
       SomeFunction(); // Выполнится
    }

    finally
    {
       while (true)
       {
          return;
       }
       SomeFunction(); // Никогда не выполнится
    }

    4 декабря 2012 г. 19:53
    Модератор
  • Вы, видимо, всё пытаетесь меня убедить в том, что break и return разные сущности. Я этого не отрицаю, поэтому привел вполне конкретный код для сравнения (Ваш код при этом не равнозначен с точки зрения выполняемых полезных действий). Для меня не ясна Ваша фраза "код в блоке finally должен обязательно выполниться." применимо к моим примерам кода. return там или break, в блок finally всё равно же попадаем, а дальше пусть работает логика внутри блока finally. Предполагаю, что дело кроется в сложности реализации выхода из блока finally для компилятора. Т.е. на вопрос топикстартера "Почему возврат из функции по return нелзя выполнить в блоке finally?" я пока не вижу однозначного ответа.
    5 декабря 2012 г. 6:07
  • Предполагаю, что дело кроется в сложности реализации выхода из блока finally для компилятора.

    Отчасти да, причина в этом.

    Но сама суть finally в том, что этот блок кода должен выполняться всегда! Тут уже вопрос не синтаксиса (разрешить использование return внутри него), а семантики.

    Между прочим, компилятор выдаёт предупреждение Unreachable code detected на такой код:

    while (true)
    {
       break;
       SomeFunction(); // Никогда не выполнится
    }

    Просто в случае finally предупреждение заменено на ошибку, чтобы сильнее бить по рукам разработчиков.

    5 декабря 2012 г. 8:27

  • Просто в случае finally предупреждение заменено на ошибку, чтобы сильнее бить по рукам разработчиков.

    Но зачем? Таким образом можно было бы все предупреждения в ошибки переделать. В Java например можно делать return из finally.

    Возвращаясь к C# может быть ещё интересный вариант :

    try
    {
       System.Environment.Exit( 0 );
    }
    finally
    {
       SomeFunction(); // Никогда не выполнится
    }

    Так что finally может быть и должен выполняться всегда, но не всегда выполняется.


    • Изменено tulosba 5 декабря 2012 г. 8:50
    5 декабря 2012 г. 8:46
  • Таким образом можно было бы все предупреждения в ошибки переделать.

    В серьёзных разработках именно так и делают: включают в опциях билда Treat warnings as errors - All.

    В Java например можно делать return из finally.

    Это их их проблемы. Разные языки - разные возможности.

    Возвращаясь к C# может быть ещё интересный вариант 

    [скипнул]

    Приложение завершено, ничего больше выполнять не надо. Синтаксис не расходится с семантикой. Что не так?

    5 декабря 2012 г. 9:15
  • В серьёзных разработках именно так и делают: включают в опциях билда Treat warnings as errors - All.

    Это всё частности. Не понятно почему MS решили сделать это ошибкой. Какую благую цель преследовали?



    Приложение завершено, ничего больше выполнять не надо. Синтаксис не расходится с семантикой. Что не так?

    "Но сама суть finally в том, что этот блок кода должен выполняться всегда!"

    Вы действительно не видите что не так? Путем недолгого погугливания можно найти, что и в случае исключений типа StackOverflowException, OutOfMemoryException, ThreadAbortException блок finally может не выполняться.
    5 декабря 2012 г. 10:08
  • "Вы действительно не видите что не так? Путем недолгого погугливания можно найти, что и в случае исключений типа StackOverflowException, OutOfMemoryException, ThreadAbortException блок finally может не выполняться." - Вы уже далеко уходите от темы. Вопрос был - "почему нельзя", потому что последующий код не будет выполняться, что по определению должно гарантированно выполниться. А сам блок finally выполнится или нет, это уже другая тема.
    5 декабря 2012 г. 11:07
    Модератор
  • Это всё частности.

    Нет, это не частности. Хоть один оставшийся при компиляции варнинг - потенциальный источник ошибок. Считаешь что в коде всё правильно - подави предупреждение прагмой. Или исправь код. Но предупреждения быть не должно!

     

    Не понятно почему MS решили сделать это ошибкой. Какую благую цель преследовали?

    Конечно, я не знаю, чем именно руководствовались разрабы MS. Я могу высказывать лишь предположения.

    В некоторых языках программирования может быть лишь одна точка выхода из метода. По мнения теоретиков программирования, это упрощает читабельность и поддержку такого кода. Частично я с этим согласен (хотя иногда несколько return'ов всё же удобней). Так вот, одной из причин запрета return в finally может быть именно это. Ведь и так сложно проследить нить выполнения кода, если присутствует блок finally (а если несколько - вообще вешайся), а если ещё и прерывать его return'ом...

     

    Вы действительно не видите что не так? Путем недолгого погугливания можно найти, что и в случае исключений типа StackOverflowException, OutOfMemoryException, ThreadAbortException блок finally может не выполняться.

    Блок finally - специальная конструкция для выполнения критически важного кода. SO, OOM и TA - это ненормальное выполнение кода! finally - нормальное! Мы физически не можем правильно реагировать на эти исключения, так же как и на Environment.Exit. Запретили в MS return внутри finally - значит хотели уменьшить ещё одну возможность неправильного хода исполнения кода (моё мнение).

    Единственный способ борьбы с OOM - заранее предусмотреть резервирование памяти и т. п. (например, использовать MemoryFailPoint - хотя и это не даёт никаких гарнтий). А запрет использования return внутри finally - реальная гарантия, что весь критический код будет выполнен.

    ThreadAbort - плачевный случай. Постоянно везде талдычат, что нельзя абортить поток. Но - потоки регулярно молча убивают... В итоге эксепшены ловятся, софт валится, MS проклинается... Ибо таков нубизм разработчиков - если что-то можно сделать неправильно (хотя в документации чётко сказано, что это делать нельзя!), оно будет сделано неправильно.

    Я тут на стороне разрабов компилятора C#: считаю, что нужно максимально ограничивать всё, что может быть неправльным; считаю, что компилятор должен проверять как можно больше возможных ошибок. Считаю, что по умолчанию должны быть иммутабельные типы (хочу Немерле, хнык-хнык...), и масса других ограничений.

    5 декабря 2012 г. 11:20
  • > Вопрос был - "почему нельзя", потому что последующий код не будет выполняться, что по определению должно гарантированно выполниться.

    Я же приводил два варианта кода, один - c break, другой - с return. Можете мне объяснить, почему в первом случае компилируется, а во втором нет? И я не прошу дать ссылку на документацию по C#. Мне интересно почему разработчики компилятора так решили, что один код валидный, другой - нет.

    5 декабря 2012 г. 11:21
  • Коллеги, давайте, раз пошла такая пьянка, вернемся к исходному вопросу: "Почему возврат из функции по return нелзя выполнить в блоке finally?".

    Так вот, отвечаю. Это потому, что в синтаксическом графе, описывающем анализ конструкции try-catch-finally отсутствует ветка, разрешающая в блоке finally использовать ключевое слово return. Почему так было сделано? Сложно сказать. У меня такое ощущение, что это одна из самых сложных задач, сделать правильную обработку ошибок (ну это я говорю как человек несколько раз писавший свои скриптовые языки и преподававший построение компиляторов 5 лет в универе)...

    5 декабря 2012 г. 11:22
    Отвечающий
  • Я же приводил два варианта кода, один - c break, другой - с return. Можете мне объяснить, почему в первом случае компилируется, а во втором нет? И я не прошу дать ссылку на документацию по C#. Мне интересно почему разработчики компилятора так решили, что один код валидный, другой - нет.

    Это нужно спрашивать у самих разработчиков.

    Просто с давних времён программерам разрешено многое. Время идёт, приходит понимание, ужесточаются требования. Когда-то раньше в ООП не было приватных членов. Ныне, в современных языках по умолчанию все члены приватные.

    Сделав всего лишь предупреждение в случае break, создатели компилятора шли на поводу старых знаний и мнений. Полагаю, в будущем и тут требования будут расти, и такой код компилятор тоже будет запрещать.

    Я погуглил, но не найду внятного объяснения. Эрик Липперт, один из разрабов дотнета, тоже как-то уклончиво отвечает.

    Что я нашёл: одной из причин является (может являться) упрощения использования ключевого слова using: компилятору намного проще генерировать код.

    5 декабря 2012 г. 11:37
  • это я говорю как человек несколько раз писавший свои скриптовые языки и преподававший построение компиляторов 5 лет в универе

    Алексей, а можно вопрос, вернее, два вопроса:

    Теория компиляторов была основана на Книге Дракона?

    Как писались парсеры: вручную или с помощью генератора?

    5 декабря 2012 г. 11:39
  • В том числе и на этой книге, но она сложная, а у нас на все компиляторы был один семестр.

    Парсеры писались вручную, один раз под специфичную железку (писал на Delphi), второй раз была игра для студентов.

    5 декабря 2012 г. 11:51
    Отвечающий