我们正在调查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
正文是否正常退出。
答案 0 :(得分:12)
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)的编译器支持而公开。 NoteNote:
此方法使用SecurityAction.LinkDemand来防止从不受信任的代码调用它;只有直接调用者才需要具有SecurityPermissionAttribute.UnmanagedCode权限。如果可以从部分受信任的代码调用代码,则不要在没有验证的情况下将用户输入传递给Marshal类方法。有关使用LinkDemand成员的重要限制,请参阅Demand vs. LinkDemand。
答案 1 :(得分:7)
不是这个问题的答案,而只是说明我从未在实际代码中使用“已接受”的黑客,所以它仍然在很大程度上未经测试“在野外”。相反,我们选择了这样的事情:
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
时,F10会进入x.DoSomething()
。两者都很小。
答案 2 :(得分: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)。不过,我认为这是唯一现实的解决方案。
答案 3 :(得分:2)
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方法旨在清理非托管资源,而不是处理异常。你可能最好在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();
}
}
答案 4 :(得分:2)
这似乎不是一个坏主意;它只是在C#/。NET 中似乎不是理想的。
在C ++中,有一个函数可以使代码检测是否由于异常而被调用。这在RAII析构函数中最为重要;根据控制流是正常还是异常,析构函数选择提交或中止是一件微不足道的事。我认为这是一种相当自然的方法,但缺乏内置支持(以及解决方法在道德上可疑的性质;它感觉相当依赖于实现)可能意味着应该采取更传统的方法。 / p>