【问题标题】:How to determine whether a .NET exception is being handled?如何确定是否正在处理 .NET 异常?
【发布时间】:2010-12-21 09:23:37
【问题描述】:

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

【问题讨论】:

  • 我想你需要问问你为什么要这么做。 Dispose() 模式并非旨在通过引发异常来实现控制逻辑。
  • 质疑整个想法总是公平的。我明白这不是“使用”的意思。我很欣赏它会导致错误的代码。我仍然对答案感兴趣:)
  • 为什么这个问题被否决了?
  • "using" 语句被转换为 'try/finally' 块 & 对象被释放。

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


【解决方案1】:

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) 的编译器支持而公开。 注注:

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

【讨论】:

  • 这可能是您问题的唯一实际答案,但追求它似乎是一个非常糟糕的主意。
  • 谢谢你,当场。 @编程英雄:可能。不能否认看起来是这样的。我们将在一个小型测试项目中试一试,看看这种方法是否存在重大问题。
  • 如果你真的走这条路,请先看看下面的博客:geekswithblogs.net/akraus1/archive/2008/04/08/121121.aspx
  • romkyns:使用这种 hack,您不会在小型测试项目中看到重大问题。当您的代码部署到具有 1000 多个独特且不同的配置怪癖的机器上时,您就会发现问题。
  • 只是给任何想使用这个 hack 的人的提示:它仍然未经“野外”测试,因为我们最终没有使用它;请参阅下面我的帖子,了解我们所做的示例。
【解决方案2】:

不是问题的答案,只是说明我从未在实际代码中使用“公认的”hack,因此它在很大程度上仍未“在野外”测试。相反,我们选择了这样的东西:

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

关键是它与问题中的“使用”示例一样紧凑,并且没有使用任何技巧。

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

【讨论】:

【解决方案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.

编辑我看到您已经编辑了您的问题以表明您了解这种模式(您的示例使用 TransactionScope)。尽管如此,我认为这是唯一现实的解决方案。

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 啊,我应该提到这一点。关键是进入“catch”和“finally”的代码是相同的,但也很重要。关键是将这段代码移到别处。
    • 没问题。在这两种情况下都会调用 dispose 代码,但在 catch 代码中,您可以在调用 dispose 之前执行 extra 代码。例如,可以在 x 内部设置一些布尔值,可以在调用 Dispose 时进行检查。
    • 感谢您的努力,但如果我们仍然必须放置一个“正常”的 try/catch 子句,那么这没有任何意义 - 正如您正确地观察到的那样,@987654324 中的内容@ 应该只是进入catch/finally。这个想法是用using 保存重复的try/catch/finally,因为它会减少整个地方的样板代码。
    • 我已经更新了我的评论,看到它的结尾(现在很长)。不再有 try catch 块,只有一个额外的 commit 语句。
    【解决方案5】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-29
      • 2021-05-22
      • 2013-11-19
      • 2016-04-12
      • 2016-12-30
      • 2012-06-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多