Определите, выполняется ли в блоке finally из-за исключения - PullRequest
33 голосов
/ 21 июля 2010

Можно ли определить, выполняется ли код в настоящее время в контексте обработчика 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.


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

Ответы [ 8 ]

17 голосов
/ 21 июля 2010

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

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

Таким образом, ваш объект области видимости может отслеживать, удаляется ли он из-за ошибки или успешной операции. Таков подход, например, TransactionScope (см. TransactionScope.Complete ).

13 голосов
/ 21 июля 2010

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

7 голосов
/ 07 октября 2010

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

5 голосов
/ 21 июля 2010

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

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

Конечно, scope.Cancel() удостоверится, что ничего не произойдет в Dispose ()

3 голосов
/ 21 июля 2010

Следующий шаблон устраняет проблему с неправильным использованием 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;
        }
    }
}   
1 голос
/ 05 июня 2013

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

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

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

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

, что позволяет использовать код вроде:

return scopeGuard.Success(thingThatMightThrow());

вместо

var result = thingThatMightThrow();
scopeGuard.Success();
return result;
1 голос
/ 21 июля 2010

Я думаю, что лучший способ - использовать предложение write 1001 * вручную.Изучите предмет из первой книги «Эффективный c #». Хороший хакер C # должен точно знать, что использует расширение до. Он немного изменился со времени .Net 1.1 - теперь вы можете иметь несколько, используя один под другим. Итак, используйте рефлектор иизучите не подслащенный код.

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

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

LAZY WAY :

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();
    }
}
0 голосов
/ 21 июля 2010

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

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

...