16

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

4

5 回答 5

13

https://www.codewrecks.com/post/old/2008/07/detecting-if-finally-block-is-executing-for-an-manhandled-exception/描述了一个“hack”来检测你的代码是否被执行是否处于异常处理模式。它使用Marshal.GetExceptionPointers来查看异常是否“活动”。

但请记住:

评论

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

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

于 2009-11-29T13:23:46.810 回答
8

不是问题的答案,只是说明我从未在实际代码中使用过“公认的”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();
    }
}

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

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

于 2010-03-09T23:06:21.550 回答
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)。尽管如此,我认为这是唯一现实的解决方案。

于 2009-11-29T13:20:49.733 回答
2

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();
    }
}
于 2009-11-29T13:06:38.987 回答
2

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

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

于 2009-11-29T15:06:21.423 回答