Подтвердить что ты не робот

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

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


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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

Edit

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

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;
        }
    }
}   

Ответ 6

Было бы (IMHO очень) полезно, если бы существовал вариант IDisposable, метод Dispose которого принимал параметр, чтобы указать, какое исключение, если оно было, было отложено, когда оно было запущено. Среди прочего, в случае, если Dispose не может выполнить ожидаемую очистку, он сможет выдать исключение, которое включает информацию о более раннем исключении. Это также позволит методу Dispose генерировать исключение, если код "забывает" делать что-то, что он должен был делать в блоке 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;

Ответ 7

Я думаю, что лучший способ - написать запись try/catch/finally вручную. Изучите элемент из первой книги "Эффективный С#". Хороший хакер С# должен точно знать, что использует расширение. Он немного изменился с момента .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();
    }
}

Ответ 8

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

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