是否可以通过抛出异常来确定代码当前是否在finally
处理程序的上下文中执行?我非常喜欢使用IDisposable
模式来实现入口/出口作用域功能,但是这种模式的一个问题是,如果发生异常,您可能不一定希望发生作用域结束行为。 using
的正文。我会找这样的东西:
public static class MyClass
{
public static void MyMethod()
{
using (var scope = MyScopedBehavior.Begin())
{
//Do stuff with scope here
}
}
}
public sealed class MyScopedBehavior : IDisposable
{
private MyScopedBehavior()
{
//Start of scope behavior
}
public void Dispose()
{
//I only want to execute the following if we're not unwinding
//through finally due to an exception:
//...End of scope behavior
}
public static MyScopedBehavior Begin()
{
return new MyScopedBehavior();
}
}
我可以通过其他方式实现此目的(将委托传递给围绕具有特定行为的调用的函数),但我很好奇是否可以使用IDisposable
模式执行此操作。
实际上,显然已经在here之前询问并回答了这个问题。有可能以一种非常黑客的方式进行检测。我实际上不会使用这种技术,但知道这是可能的,这很有意思。
答案 0 :(得分:17)
我所见到的实现这一目标的方法需要额外的方法:
public static void MyMethod()
{
using (var scope = MyScopedBehavior.Begin())
{
//Do stuff with scope here
scope.Complete(); // Tells the scope that it's good
}
}
通过执行此操作,您的范围对象可以跟踪由于错误或成功操作而处置它。这是TransactionScope采用的方法(例如TransactionScope.Complete)。
答案 1 :(得分:13)
作为一个侧面点,IL允许您指定与fault
类似的SEH finally
块,但在抛出异常时仅输入 - 您可以看到示例here,大约2 / 3rds页面。不幸的是,C#没有公开这个功能。
答案 2 :(得分:7)
我正在为单元测试寻找类似的东西 - 我有一个帮助类,我用它来测试运行后清理对象,我想保持漂亮,干净的“使用”语法。如果测试失败,我还想要不清理的选项。我想出的是致电Marshal.GetExceptionCode()。我不知道这是否适用于所有情况,但对于测试代码,它似乎工作正常。
答案 3 :(得分:5)
我能想到的最好的是:
using (var scope = MyScopedBehavior.Begin())
{
try
{
//Do stuff with scope here
}
catch(Exception)
{
scope.Cancel();
throw;
}
}
当然,scope.Cancel()
会确保在Dispose()
答案 4 :(得分:3)
以下模式避免了API误用的问题,即未调用范围完成方法,即完全省略,或者由于逻辑条件而未被调用。我认为这会更密切地回答您的问题,而且API用户的代码更少。
修改强>
丹的评论后更加直截了当:
public class Bling
{
public static void DoBling()
{
MyScopedBehavior.Begin(() =>
{
//Do something.
}) ;
}
}
public static class MyScopedBehavior
{
public static void Begin(Action action)
{
try
{
action();
//Do additonal scoped stuff as there is no exception.
}
catch (Exception ex)
{
//Clean up...
throw;
}
}
}
答案 5 :(得分:1)
我认为最好的方法是手动使用写出try/catch/finally
子句。从第一本“有效的c#”书中学习一个项目。一个优秀的C#黑客应该确切地知道使用扩展的内容。它已经改变了一点点.Net 1.1 - 你现在可以有几个使用另一个。所以,使用反射器,和研究未加糖的代码。
然后,当您编写自己的代码时 - 使用using
或编写自己的东西。这不是非常困难,也是一件好事。
你可能会喜欢其他技巧,但感觉太沉重,甚至效率也不高。我请包含一个代码示例。
懒散的方式:
using (SqlConnection cn = new SqlConnection(connectionString))
using (SqlCommand cm = new SqlCommand(commandString, cn))
{
cn.Open();
cm.ExecuteNonQuery();
}
手动方式 :
bool sawMyEx = false;
SqlConnection cn = null;
SqlCommand cm = null;
try
{
cn = new SqlConnection(connectionString);
cm = new SqlCommand(commandString, cn);
cn.Open();
cm.ExecuteNonQuery();
}
catch (MyException myEx)
{
sawMyEx = true; // I better not tell my wife.
// Do some stuff here maybe?
}
finally
{
if (sawMyEx)
{
// Piss my pants.
}
if (null != cm);
{
cm.Dispose();
}
if (null != cn)
{
cn.Dispose();
}
}
答案 6 :(得分:1)
如果IDisposable
的{{1}}方法接受了一个参数来指示运行时有哪些异常(如果有),那么(恕我直言)非常有用。除其他外,如果Dispose
无法执行预期的清理,它将能够抛出一个异常,其中包含有关早期异常的信息。如果代码“忘记”执行它应该在Dispose
块中执行的操作,它还允许Dispose
方法抛出异常,但不会覆盖可能导致使用块的任何其他异常过早退出。不幸的是,目前还没有这样的功能。
有许多文章建议使用API函数来确定是否存在挂起的异常。这种方法的一个主要问题是代码可能在using
块中运行,成功完成finally
,但可能嵌套在try
块中{ {1}}过早退出。即使finally
方法可以识别出存在这种情况,也无法知道哪个try
阻止它“属于”。可以在任何一种情况适用的情况下制定例子。
实际上,最好的方法可能是有一个明确的“成功”方法,如果没有被调用则假设失败,并认为忘记调用“成功”方法的后果应该是显而易见的,即使没有例外是抛出。作为简单的实用方法可能有用的一件事就像
Dispose
因此允许代码如下:
try
而不是
T Success<T>(T returnValue)
{
Success();
return T;
}
答案 7 :(得分:0)
为什么不简单地从最后的try { }
块内部进行处理,而不是最终使用?这似乎是你正在寻找的行为。
就其他人如何使用你的课程而言,这似乎也更为现实。你确定每个使用它的人都不会想要在例外的情况下处理吗?或者这种行为是否应由该类的消费者处理?