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

Как определить, обрабатывается ли исключение .NET?

Мы изучаем шаблон кодирования в С#, в котором мы хотели бы использовать предложение "using" со специальным классом, метод Dispose() выполняет разные действия в зависимости от того, было ли нормально использовано тело "using" с исключением.

Насколько я понимаю, CLR отслеживает, что текущее исключение обрабатывается до тех пор, пока оно не будет использовано обработчиком "catch". Однако не совсем ясно, каким образом эта информация каким-либо образом предоставляется для доступа к коду. Знаете ли вы, и если да, как получить к нему доступ?

Например:

using (var x = new MyObject())
{
    x.DoSomething();
    x.DoMoreThings();
}

class MyObject : IDisposable
{
    public void Dispose()
    {
        if (ExceptionIsBeingHandled)
            Rollback();
        else
            Commit();
    }
}

Это выглядит почти как System.Transactions.TransactionScope, за исключением того, что успех/сбой не определяется вызовом x.Complete(), а скорее зависит от того, было ли тело using завершено нормально.

4b9b3361

Ответ 1

http://www.codewrecks.com/blog/index.php/2008/07/25/detecting-if-finally-block-is-executing-for-an-manhandled-exception/ описывает "взломать", чтобы определить, выполняется ли ваш код в режиме обработки исключений или нет. Он использует Marshal.GetExceptionPointers, чтобы узнать, является ли исключение "активным".

Но имейте в виду:

Примечания

GetExceptionPointers предоставляется только для поддержки компилятора структурированной обработки исключений (SEH). NoteNote:

Этот метод использует SecurityAction.LinkDemand для предотвращения его вызова из ненадежного кода; требуется только непосредственный вызывающий абонент, чтобы иметь разрешение SecurityPermissionAttribute.UnmanagedCode. Если ваш код может быть вызван из частично доверенного кода, не пропускайте ввод пользователя в методы класса Marshal без проверки. Для важных ограничений использования элемента LinkDemand см. Раздел "Спрос против LinkDemand".

Ответ 2

Не ответ на вопрос, а просто примечание о том, что я никогда не получал "принятого" взлома в реальном коде, поэтому он все еще в значительной степени не тестировался "в дикой природе". Вместо этого мы пошли на что-то вроде этого:

DoThings(x =>
{
    x.DoSomething();
    x.DoMoreThings();
});

где

public void DoThings(Action<MyObject> action)
{
    bool success = false;
    try
    {
        action(new MyObject());
        Commit();
        success = true;
    }
    finally
    {
        if (!success)
            Rollback();
    }
}

Ключевым моментом является то, что он столь же компактен, как пример использования в вопросе, и не использует хаки.

Среди недостатков - штраф за производительность (в нашем случае это совершенно ничтожно), а F10 переходит на DoThings, когда я действительно хочу, чтобы он просто шагнул прямо к x.DoSomething(). Оба очень незначительные.

Ответ 3

Эта информация недоступна для вас.

Я бы использовал шаблон, подобный тому, который используется классом DbTransaction: т.е. ваш класс IDisposable должен реализовать метод, аналогичный DbTransaction.Commit(). Затем ваш метод Dispose может выполнять различную логику в зависимости от того, была ли вызвана Commit (в случае DbTransaction транзакция будет откат, если она не была выполнена явно).

Пользователи вашего класса затем будут использовать следующий шаблон, аналогичный типичному DbTransaction:

using(MyDisposableClass instance = ...)
{
    ... do whatever ...

    instance.Commit();
} // Dispose logic depends on whether or not Commit was called.

EDIT. Я вижу, что вы отредактировали свой вопрос, чтобы показать, что вам известно об этом шаблоне (в вашем примере используется TransactionScope). Тем не менее я считаю это единственным реалистичным решением.

Ответ 4

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

try
{
    IDisposable x = new MyThing();
}
catch (Exception exception) // Use a more specific exception if possible.
{
    x.ErrorOccurred = true; // You could even pass a reference to the exception if you wish.
    throw;
}
finally
{
    x.Dispose();
}

Внутри MyThing вы можете сделать это, если хотите, например:

class MyThing : IDisposable
{
    public bool ErrorOccurred() { get; set; }

    public void Dispose()
    {
        if (ErrorOccurred) {
            RollBack();
        } else {
            Commit();
        }
    }
}

Примечание. Мне также нужно задаться вопросом, почему вы хотите это сделать. У этого есть некоторый запах кода. Метод Dispose предназначен для очистки неуправляемых ресурсов, а не для обработки исключений. Вероятно, вам лучше написать код обработки исключений в блоке catch, а не в распоряжаться, и если вам нужно использовать общий код, сделайте некоторые полезные вспомогательные функции, которые вы можете вызвать из обоих мест.

Вот лучший способ сделать то, что вы хотите:

using (IDisposable x = new MyThing())
{
    x.Foo();
    x.Bar();
    x.CommitChanges();
}

class MyThing : IDisposable
{
    public bool IsCommitted { get; private set; }

    public void CommitChanges()
    {
        // Do stuff needed to commit.
        IsCommitted = true;
    }

    public void Dispose()
    {
        if (!IsCommitted)
            RollBack();
    }
}

Ответ 5

Это, похоже, не такая уж плохая идея; он просто не идеален в С#/. NET.

В С++ есть функция, которая позволяет коду определять, вызвана ли она из-за исключения. Это наиболее важно для RAII-деструкторов; тривиальным делом для деструктора является выбор или фиксация в зависимости от того, является ли поток управления нормальным или исключительным. Я считаю, что это довольно естественный подход, но отсутствие встроенной поддержки (и морально сомнительный характер обходного пути, который, по моему мнению, зависит от меня, зависит от меня), вероятно, означает, что следует применять более традиционный подход.