如何确定是否正在处理.NET异常?

时间:2009-11-29 13:04:02

标签: c# .net exception-handling using-statement

我们正在调查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 个答案:

答案 0 :(得分:12)

http://www.codewrecks.com/blog/index.php/2008/07/25/detecting-if-finally-block-is-executing-for-an-manhandled-exception/描述了一个“hack”来检测你的代码是否在异常处理模式下执行。它使用Marshal.GetExceptionPointers来查看异常是否“有效”。

但请记住:

备注

GetExceptionPointers仅针对结构化异常处理(SEH)的编译器支持而公开。 NoteNote:

此方法使用SecurityAction.LinkDemand来防止从不受信任的代码调用它;只有直接调用者才需要具有SecurityPermissionAttribute.UnmanagedCode权限。如果可以从部分受信任的代码调用代码,则不要在没有验证的情况下将用户输入传递给Marshal类方法。有关使用LinkDemand成员的重要限制,请参阅Demand vs. LinkDemand。

答案 1 :(得分:7)

不是这个问题的答案,而只是说明我从未在实际代码中使用“已接受”的黑客,所以它仍然在很大程度上未经测试“在野外”。相反,我们选择了这样的事情:

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

关键是它与问题中的“使用”示例一样紧凑,并且不使用任何黑客。

缺点之一是性能损失(在我们的情况下完全可以忽略不计),而当我真正希望它直接进入DoThings时,F10会进入x.DoSomething()。两者都很小。

答案 2 :(得分:4)

您无法使用此信息。

我会使用类似于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)。不过,我认为这是唯一现实的解决方案。

答案 3 :(得分:2)

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

答案 4 :(得分:2)

这似乎不是一个坏主意;它只是在C#/。NET 中似乎不是理想的

在C ++中,有一个函数可以使代码检测是否由于异常而被调用。这在RAII析构函数中最为重要;根据控制流是正常还是异常,析构函数选择提交或中止是一件微不足道的事。我认为这是一种相当自然的方法,但缺乏内置支持(以及解决方法在道德上可疑的性质;它感觉相当依赖于实现)可能意味着应该采取更传统的方法。 / p>