简体   繁体   English

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

[英]How to determine whether a .NET exception is being handled?

We're investigating a coding pattern in C# in which we'd like to use a "using" clause with a special class, whose Dispose() method does different things depending on whether the "using" body was exited normally or with an exception. 我们正在调查C#中的编码模式,其中我们想要使用带有特殊类的“using”子句,其Dispose()方法根据“using”主体是正常退出还是异常退出而执行不同的操作。

To the best of my understanding, the CLR keeps track of the current exception being handled until it's been consumed by a "catch" handler. 据我所知,CLR会跟踪当前正在处理的异常,直到它被“catch”处理程序占用为止。 However it's not entirely clear whether this information is exposed in any way for the code to access. 但是,这些信息是否以任何方式公开以供代码访问并不完全清楚。 Do you know whether it is, and if so, how to access it? 你知道它是否,如果是,如何访问它?

For example: 例如:

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

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

This looks almost like System.Transactions.TransactionScope , except that success/failure is not determined by a call to x.Complete() , but rather based on whether the using body was exited normally. 这看起来几乎像System.Transactions.TransactionScope ,除了成功/失败不是通过调用x.Complete()来确定,而是基于using正文是否正常退出。

http://www.codewrecks.com/blog/index.php/2008/07/25/detecting-if-finally-block-is-executing-for-an-manhandled-exception/ describes a "hack" to detect if your code is executed in exception handling mode or not. http://www.codewrecks.com/blog/index.php/2008/07/25/detecting-if-finally-block-is-executing-for-an-manhandled-exception/描述了一个“hack”来检测是否您的代码是否在异常处理模式下执行。 It uses Marshal.GetExceptionPointers to see if an exception is "active". 它使用Marshal.GetExceptionPointers来查看异常是否“活动”。

But keep in mind: 但请记住:

Remarks 备注

GetExceptionPointers is exposed for compiler support of structured exception handling (SEH) only. 仅为编译器支持结构化异常处理(SEH)公开GetExceptionPointers。 NoteNote: NoteNote:

This method uses SecurityAction.LinkDemand to prevent it from being called from untrusted code; 此方法使用SecurityAction.LinkDemand来防止从不受信任的代码调用它; only the immediate caller is required to have SecurityPermissionAttribute.UnmanagedCode permission. 只有直接调用者才需要具有SecurityPermissionAttribute.UnmanagedCode权限。 If your code can be called from partially trusted code, do not pass user input to Marshal class methods without validation. 如果可以从部分受信任的代码调用代码,则不要在没有验证的情况下将用户输入传递给Marshal类方法。 For important limitations on using the LinkDemand member, see Demand vs. LinkDemand. 有关使用LinkDemand成员的重要限制,请参阅Demand vs. LinkDemand。

Not an answer to the question, but just a note that I never ended up using the "accepted" hack in real code, so it's still largely untested "in the wild". 这个问题不是答案,而只是说明我从未在实际代码中使用“接受”的黑客攻击,所以它仍然在很大程度上未经测试“在野外”。 Instead we went for something like this: 相反,我们去了这样的事情:

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

where 哪里

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

The key point is that it's as compact as the "using" example in the question, and doesn't use any hacks. 关键点在于它与问题中的“使用”示例一样紧凑,并且不使用任何黑客。

Among the drawbacks is a performance penalty (completely negligible in our case), and F10 stepping into DoThings when I actually want it to just step straight to x.DoSomething() . 其中的缺点是性能损失(在我们的情况下完全可以忽略不计),而F10踩到DoThings时我实际上希望它直接进入x.DoSomething() Both very minor. 两者都很小。

This information isn't available to you. 您无法获得此信息。

I would use a pattern similar to that used by the DbTransaction class: that is, your IDisposable class should implement a method analagous to DbTransaction.Commit(). 我会使用类似于DbTransaction类使用的模式:也就是说,你的IDisposable类应该实现一个与DbTransaction.Commit()类似的方法。 Your Dispose method can then perform different logic depending on whether or not Commit was called (in the case of DbTransaction, the transaction will be rolled back if it wasn't explicity committed). 然后,您的Dispose方法可以执行不同的逻辑,具体取决于是否调用了Commit(在DbTransaction的情况下,如果事务没有明确提交,则事务将被回滚)。

Users of your class would then use the following pattern, similar to a typical DbTransaction: 然后,您的类的用户将使用以下模式,类似于典型的DbTransaction:

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

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

EDIT I see you've edited your question to show you're aware of this pattern (your example uses TransactionScope). 编辑我看到你编辑了你的问题,以表明你已经知道这个模式(你的例子使用TransactionScope)。 Nevertheless I think it's the only realistic solution. 不过,我认为这是唯一现实的解决方案。

A using statement is just syntactic sugar for a try finally block. using语句只是try finally块的语法糖。 You can get what you want by writing the try finally out in full and then adding a catch statement to handle your special case: 你可以通过最终完整地编写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();
}

Inside MyThing you can do this if you want, for example: 在MyThing内部,如果需要,您可以执行此操作,例如:

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

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

Note: I also have to wonder why you want to do this. 注意:我也想知道你为什么要这样做。 It has some code smell. 它有一些代码味道。 The Dispose method is intended to clean up unmanaged resources, not to handle exceptions. Dispose方法旨在清理非托管资源,而不是处理异常。 You would probably be better off writing your exception handling code in the catch block, not in the dispose, and if you need to share code make some useful helper functions that you can call from both places. 您可能最好在catch块中编写异常处理代码,而不是在dispose中编写,如果需要共享代码,可以创建一些可以从两个地方调用的有用辅助函数。

Here's a better way of doing what you want: 这是一个更好的方式来做你想要的:

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

This doesn't seem to be that bad an idea; 这似乎不是一个坏主意; it just doesn't seem to be ideal in C#/.NET . 在C#/ .NET中似乎并不理想。

In C++ there is a function that enables code to detect if it's being called due to an exception. 在C ++中,有一个函数可以使代码检测是否由于异常而被调用。 This is of most importance in RAII destructors; 这在RAII析构函数中最为重要; it is a trivial matter for the destructor to choose to commit or abort depending on whether control flow is normal or exceptional. 根据控制流是正常还是异常,析构函数选择提交或中止是一件微不足道的事。 I think this is a fairly natural approach to take, but the lack of built-in support (and the morally dubious nature of the workaround; it feels rather implementation-dependent to me) probably means that a more conventional approach should be taken. 我认为这是一种相当自然的方法,但缺乏内置支持(以及解决方法在道德上可疑的性质;它感觉相当依赖于实现)可能意味着应采取更传统的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM