C# 确定是否由于引发异常而在finally块中执行
是否可以确定由于引发异常,代码当前是否在C# 确定是否由于引发异常而在finally块中执行,c#,exception,idisposable,finally,try-catch,C#,Exception,Idisposable,Finally,Try Catch,是否可以确定由于引发异常,代码当前是否在finally处理程序的上下文中执行?我非常喜欢使用IDisposable模式来实现进入/退出范围功能,但是这种模式的一个问题是,如果使用的主体中发生异常,您可能不一定希望范围结束行为发生。我在找这样的东西: public static class MyClass { public static void MyMethod() { using (var scope = MyScopedBehavior.Begin())
finally
处理程序的上下文中执行?我非常喜欢使用IDisposable
模式来实现进入/退出范围功能,但是这种模式的一个问题是,如果使用的主体中发生异常,您可能不一定希望范围结束行为发生。我在找这样的东西:
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
模式来实现这一点
事实上,这显然已经被问过并回答过了。可以用一种非常粗俗的方式进行检测。实际上我不会使用这种技术,但很有意思的是知道这是可能的。我认为最好的方法是手动使用write outtry/catch/finally
子句。研究第一本“有效的c#”书中的一个项目。一个好的c#黑客应该确切地知道使用扩展到什么。自.Net 1.1以来,它已经发生了一些变化-现在可以有多个使用一个接一个。因此,使用reflector,研究未加糖的代码
然后,当你写你自己的代码时,要么用用或者写你自己的东西。这并不难,而且是一件好事
你可能会喜欢其他的技巧,但感觉太重,甚至没有效率。让我包括一个代码示例
懒惰的方式:
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();
}
}
手动方式:
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();
}
}
我所看到的实现这一点的方法需要额外的方法:
public static void MyMethod()
{
using (var scope = MyScopedBehavior.Begin())
{
//Do stuff with scope here
scope.Complete(); // Tells the scope that it's good
}
}
通过这样做,您的范围对象可以跟踪它是由于错误还是由于成功的操作而进行处理。例如,这就是所采取的方法(请参阅)。我能想到的最佳方法是:
using (var scope = MyScopedBehavior.Begin())
{
try
{
//Do stuff with scope here
}
catch(Exception)
{
scope.Cancel();
throw;
}
}
当然,scope.Cancel()
将确保Dispose()中不会发生任何事情为什么不从块的最末端尝试{}
块进行Dispose,而根本不使用finally?这似乎是您正在寻找的行为
就其他人如何使用您的类而言,这似乎更为现实。您确定所有使用过它的人都不会在异常情况下进行处理吗?还是应该由类的使用者处理此行为?作为一个补充,IL允许您指定与类似的SEHfault
块最后
,但只有在抛出异常时才输入-您可以看到一个示例,大约在页面下方的2/3处。不幸的是,C#没有公开此功能。以下模式避免了API误用的问题,即未调用范围完成方法,即完全忽略,或由于逻辑错误而未调用我认为这更接近地回答了您的问题,对于API用户来说,代码更少
编辑
在Dan的评论之后,更直截了当的是:
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;
}
}
}
我一直在为单元测试寻找类似的东西-我有一个助手类,用于在测试运行后清理对象,我希望保持良好、干净的“使用”语法。我还希望在测试失败时不清理。我想到的是调用。我不知道这是否适用于所有情况,但对于测试代码来说似乎是这样It’很好。会的(我觉得很好)如果有一个IDisposable
的变体,其Dispose
方法接受一个参数,以指示运行时挂起的异常(如果有)。除此之外,如果Dispose
无法执行预期的清理,它将能够抛出一个包含信息的异常关于前面的异常。如果代码“忘记”在using
块中执行它应该执行的操作,它还允许Dispose
方法引发异常,但不会覆盖可能导致using块过早退出的任何其他异常。遗憾的是,到目前为止还不存在此类功能
有许多文章建议使用API函数来确定是否存在挂起的异常。这种方法的一个主要问题是,对于成功完成的try
,代码可能正在finally
块中运行,但可能嵌套在finally
块中其try
过早退出。即使Dispose
方法可以识别出这种情况的存在,它也无法知道它“属于”哪个try
块。可以在任何一种情况下制定示例
事实上,最好的方法可能是有一个明确的“成功”方法,如果没有调用它,则假设失败,并且认为即使没有抛出异常,忘记调用“成功”方法的后果也应该是显而易见的。作为一个简单的实用工具方法,可能会有帮助的一件事是
T Success<T>(T returnValue)
{
Success();
return T;
}
而不是
var result = thingThatMightThrow();
scopeGuard.Success();
return result;
是的,我想我更喜欢你的(乐观的)而不是我的(悲观的)@Brian:这两种方法都有效,但这对你的类的用户来说需要更少的代码:)这是非常干净的,而且更明确的是,如果你没有达到完整的目标,会发生一些不同的事情。我更喜欢这种风格,而不是我想要的“技巧”,但我仍然很好奇这在技术上是否可行。@Dan:这已经成为han的标准方式据我所知,在.NET中使用了一个分解类型(即:它在BCL中使用)。我不知道有什么更好的方法来处理这个问题,比如Dispose()未提供有关当前异常状态的任何信息。对于嵌套作用域无效,并且不会实际检测异常是否触发了finally块。例如,此类作用域是否可以像使用(新作用域)使用(新作用域)使用(n)一样堆叠