如何确定是否正在处理.NETexception?

我们正在调查C#中的编码模式,其中我们要使用带有特殊类的“using”子句,其Dispose()方法根据“using”主体是正常退出还是exception退出而执行不同的操作。

据我所知,CLR会跟踪当前正在处理的exception,直到它被“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正文是否正常退出。

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

但请记住:

备注

仅为编译器支持结构化exception处理(SEH)公开GetExceptionPointers。 NoteNote:

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

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

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

哪里

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

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

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

您无法使用此信息。

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

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方法旨在清理非托管资源,而不是处理exception。 您可能最好在catch块中编写exception处理代码,而不是在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(); } } 

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

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