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

发布于 2024-08-12 13:45:18 字数 609 浏览 3 评论 0原文

我们正在研究 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 主体正常退出。

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.

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. 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

凉城凉梦凉人心 2024-08-19 13:45:18

https://www.codewrecks.com/post/old/2008/07/Detecting-if-finally-block-is-executing-for-an-manhandled-exception/ 描述了一种用于检测的“黑客”行为您的代码是否在异常处理模式下执行。它使用 Marshal.GetExceptionPointers 查看异常是否“活动”。

但请记住:

备注

GetExceptionPointers 仅为结构化异常处理 (SEH) 的编译器支持而公开。
注意注:

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

https://www.codewrecks.com/post/old/2008/07/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. It uses Marshal.GetExceptionPointers to see if an exception is "active".

But keep in mind:

Remarks

GetExceptionPointers is exposed for compiler support of structured exception handling (SEH) only.
NoteNote:

This method uses SecurityAction.LinkDemand to prevent it from being called from untrusted code; only the immediate caller is required to have SecurityPermissionAttribute.UnmanagedCode permission. If your code can be called from partially trusted code, do not pass user input to Marshal class methods without validation. For important limitations on using the LinkDemand member, see Demand vs. LinkDemand.

睫毛溺水了 2024-08-19 13:45:18

不是问题的答案,只是注意我从未最终在实际代码中使用“接受的”黑客,因此它仍然在很大程度上未经“野外”测试。相反,我们采用了这样的方法:

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

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

其中的缺点是性能损失(在我们的例子中完全可以忽略不计),以及当我实际上希望 F10 直接进入 x.DoSomething() 时,它会进入 DoThings。两者都很小。

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(). Both very minor.

浪荡不羁 2024-08-19 13:45:18

您无法获得此信息。

我将使用类似于 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)。尽管如此,我认为这是唯一现实的解决方案。

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(). 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).

Users of your class would then use the following pattern, similar to a typical 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). Nevertheless I think it's the only realistic solution.

不交电费瞎发啥光 2024-08-19 13:45:18

using 语句只是 try finally 块的语法糖。您可以通过完整地编写 tryfinally ,然后添加一个 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();
    }
}

A using statement is just syntactic sugar for a try finally block. 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
{
    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:

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. 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.

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();
    }
}
Saygoodbye 2024-08-19 13:45:18

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

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

This doesn't seem to be that bad an idea; it just doesn't seem to be ideal in C#/.NET.

In C++ there is a function that enables code to detect if it's being called due to an exception. This is of most importance in RAII destructors; 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文