Определите, выполняется ли в блоке finally из-за генерируемого исключения

StackOverflow https://stackoverflow.com/questions/3301507

Вопрос

Можно ли определить, выполняется ли код в данный момент в контексте finally обработчик в результате генерируемого исключения?Мне довольно нравится использовать IDisposable шаблон для реализации функциональности определения области входа / выхода, но одна проблема с этим шаблоном заключается в том, что вы, возможно, не обязательно хотите, чтобы поведение конца области возникало, если в теле using.Я бы искал что-то подобное этому:

public static class MyClass
{
    public static void MyMethod()
    {
        using (var scope = MyScopedBehavior.Begin())
        {
            //Do stuff with scope here
        }
    }
}

public sealed class MyScopedBehavior : IDisposable
{
    private MyScopedBehavior()
    {
        //Start of scope behavior
    }

    public void Dispose()
    {
        //I only want to execute the following if we're not unwinding
        //through finally due to an exception:
        //...End of scope behavior    
    }

    public static MyScopedBehavior Begin()
    {
        return new MyScopedBehavior();
    }
}

Есть другие способы, которыми я могу выполнить это (передать делегат функции, которая окружает вызов определенным поведением), но мне любопытно, возможно ли это сделать с помощью IDisposable шаблон.


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

Это было полезно?

Решение

Способы достижения этого, которые я видел, требуют дополнительного метода:

public static void MyMethod()
{
    using (var scope = MyScopedBehavior.Begin())
    {
        //Do stuff with scope here
        scope.Complete(); // Tells the scope that it's good
    }
}

Делая это, ваш объект scope может отслеживать, удален ли он из-за ошибки или успешной операции.Это подход, принятый Транзакционный обзор, например (см. Обзор транзакций.Завершено).

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

В качестве боковой точки IL позволяет вам указать SEH fault блоки , которые похожи на finally но вводятся Только когда генерируется исключение - вы можете увидеть пример здесь, примерно на 2/3 страницы ниже по странице.К сожалению, C # не предоставляет эту функциональность.

Я искал что-то похожее для модульного тестирования - у меня есть вспомогательный класс, который я использую для очистки объектов после тестового запуска, и я хочу сохранить приятный, чистый синтаксис "using".Я также хотел иметь возможность не выполнять очистку в случае неудачи теста.То, что я придумал, - это позвонить Маршал.GetExceptionCode().Я не знаю, подходит ли это для всех случаев, но для тестового кода это, кажется, работает нормально.

Лучшее, что я могу придумать, было бы:

using (var scope = MyScopedBehavior.Begin())
{
  try
  {
    //Do stuff with scope here
  }
  catch(Exception)
  {
    scope.Cancel();
    throw;
  }
}

Конечно, scope.Cancel() убедился бы, что в Dispose() ничего не происходит

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

Редактировать

Еще более прямолинейно после комментария Дэна:

public class Bling
{
    public static void DoBling()
    {
        MyScopedBehavior.Begin(() =>
        {
            //Do something.
        }) ;
    }   
}

public static class MyScopedBehavior
{
    public static void Begin(Action action)
    {
        try
        {
            action();

            //Do additonal scoped stuff as there is no exception.
        }
        catch (Exception ex)
        {
            //Clean up...
            throw;
        }
    }
}   

Я думаю, что лучший способ - это использовать write out try/catch/finally предложение вручную.Изучите пункт из первой книги "Эффективный c #".Хороший хакер C # должен точно знать, к чему приводит использование.Это немного изменилось со времен .Net 1.1 - теперь у вас может быть несколько, использующих одно под другим.Итак, используйте reflector и изучите код без сахара.

Затем, когда вы напишете свой собственный код - либо используйте using или напиши свой собственный материал.Это не так уж трудно, и знать это полезно.

Вы могли бы пофантазировать с другими трюками, но они кажутся слишком тяжелыми и даже неэффективными.Позвольте мне включить пример кода.

ЛЕНИВЫЙ СПОСОБ:

using (SqlConnection cn = new SqlConnection(connectionString))
using (SqlCommand cm = new SqlCommand(commandString, cn))
{
    cn.Open();
    cm.ExecuteNonQuery();
}

РУЧНОЙ СПОСОБ:

bool sawMyEx = false;
SqlConnection cn =  null;
SqlCommand cm = null;

try
{
    cn = new SqlConnection(connectionString);
    cm = new SqlCommand(commandString, cn);
    cn.Open();
    cm.ExecuteNonQuery();
}
catch (MyException myEx)
{
    sawMyEx = true; // I better not tell my wife.
    // Do some stuff here maybe?
}
finally
{
    if (sawMyEx)
    {
        // Piss my pants.
    }

    if (null != cm);
    {
        cm.Dispose();
    }
    if (null != cn)
    {
        cn.Dispose();
    }
}

Было бы (ИМХО очень) полезно, если бы существовал вариант IDisposable чей Dispose метод принял параметр, указывающий, какое исключение, если таковое имелось, находилось в ожидании при его запуске.Среди прочего, в том случае, если Dispose не в состоянии выполнить ожидаемую очистку, он был бы в состоянии выдать исключение, которое включает информацию о более раннем исключении.Это также позволило бы Dispose метод для генерирования исключения, если код "забывает" сделать что-то, что он должен был сделать в using блокируйте, но не перезаписывайте любое другое исключение, которое может привести к преждевременному завершению использования блока.К сожалению, на данный момент такой функции не существует.

Существует множество статей, в которых предлагаются способы использования функций API для определения того, существует ли ожидающее исключение.Одна из основных проблем, связанных с такими подходами, заключается в том, что возможно, что код может выполняться в finally блок для try который успешно завершен, но который может быть вложен в finally блок, чей try вышел преждевременно.Даже если a Dispose метод мог бы определить, что такая ситуация существует, у него не было бы способа узнать, какая try заблокируйте, к которому он "принадлежал".Можно было бы сформулировать примеры, где применима любая ситуация.

Как бы то ни было, лучший подход, вероятно, состоит в том, чтобы иметь явный метод "success" и предполагать сбой, если он не вызывается, и считать, что последствия забывания вызвать метод "success" должны быть очевидны, даже если исключение не генерируется.Одна вещь, которая может быть полезна в качестве простого служебного метода, была бы чем-то вроде

T Success<T>(T returnValue)
{
  Success();
  return T;
}

таким образом, позволяя использовать такой код, как:

return scopeGuard.Success(thingThatMightThrow());

вместо того , чтобы

var result = thingThatMightThrow();
scopeGuard.Success();
return result;

Почему бы просто не избавиться изнутри try { } блокировать в самом конце и вообще не использовать finally?Похоже, это именно то поведение, которое вы ищете.

Это также кажется более реалистичным с точки зрения того, как другие могут использовать ваш класс.Вы уверены, что все, кто когда-либо использует его, никогда не захотят утилизировать в случае исключения?Или это поведение должно обрабатываться потребителем класса?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top