Как определить, обрабатывается ли исключение .NET? - PullRequest
15 голосов
/ 29 ноября 2009

Мы исследуем шаблон кодирования в C #, в котором мы хотели бы использовать предложение «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 нормально завершено.

Ответы [ 5 ]

12 голосов
/ 29 ноября 2009

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.

7 голосов
/ 10 марта 2010

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

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(). Оба очень незначительные.

4 голосов
/ 29 ноября 2009

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

Я бы использовал шаблон, аналогичный тому, который используется классом 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.

РЕДАКТИРОВАТЬ Я вижу, что вы отредактировали свой вопрос, чтобы показать, что вы знаете об этом шаблоне (в вашем примере используется TransactionScope). Тем не менее, я думаю, что это единственное реалистичное решение.

2 голосов
/ 29 ноября 2009

Кажется, это не так уж и плохо; это просто не кажется идеальным в C # /. NET .

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

2 голосов
/ 29 ноября 2009

Оператор using - это просто синтаксический сахар для блока try finally. Вы можете получить то, что вы хотите, написав 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, а не в dispose, и если вам нужно поделиться кодом, создайте несколько полезных вспомогательных функций, которые вы можете вызывать из обоих мест.

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

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();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...