简体   繁体   English

确定是否由于抛出异常而在finally块中执行

[英]Determine if executing in finally block due to exception being thrown

Is it possible to determine if code is currently executing in the context of a finally handler as a result of an exception being thrown? 是否可以通过抛出异常来确定代码当前是否在finally处理程序的上下文中执行? I'm rather fond of using the IDisposable pattern to implement entry/exit scoping functionality, but one concern with this pattern is that you might not necessarily want the end-of-scope behavior to occur if an exception occurs in the body of the using . 我非常喜欢使用IDisposable模式来实现入口/出口作用域功能,但是这种模式的一个问题是如果在using的主体中发生异常,您可能不一定希望发生作用域结束行为。 。 I'd be looking for something like this: 我会找这样的东西:

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();
    }
}

There are other ways I can accomplish this (pass a delegate to a function that surrounds the call with particular behavior), but I'm curious if it's possible to do it using the IDisposable pattern. 还有其他方法可以实现这一点(将委托传递给围绕具有特定行为的调用的函数),但我很好奇是否可以使用IDisposable模式来完成它。


Actually, this has apparently been asked and answered before here . 实际上,这显然已经在之前得到了回答。 It's possible to detect in a very hackish sort of way. 有可能以一种非常黑客的方式进行检测。 I wouldn't actually use that technique, but it's interesting to know that it's possible. 我实际上不会使用这种技术,但知道这是可能的,这很有趣。

The means of accomplishing this that I've seen require an extra method: 我见过的实现这个目标的方法需要一个额外的方法:

public static void MyMethod()
{
    using (var scope = MyScopedBehavior.Begin())
    {
        //Do stuff with scope here
        scope.Complete(); // Tells the scope that it's good
    }
}

By doing this, your scope object can track whether it's disposing because of an error, or a successful operation. 通过执行此操作,您的范围对象可以跟踪由于错误或成功操作而处置它。 This is the approach taken by TransactionScope , for example (see TransactionScope.Complete ). 这是TransactionScope采用的方法,例如(参见TransactionScope.Complete )。

As a side point, IL allows you to specify SEH fault blocks that are similar to finally but are entered only when an exception is thrown - you can see an example here , about 2/3rds down the page. 作为一个侧点,IL允许您指定SEH fault块,这些块与finally类似但在抛出异常时输入 - 您可以在此处看到一个示例,大约是页面下方的2 / 3rds。 Unfortunately, C# doesn't expose this functionality. 不幸的是,C#没有公开这个功能。

I was looking for something similar for unit testing - I have a helper class I use to clean up objects after a test run and I want to keep the nice, clean 'using' syntax. 我正在为单元测试寻找类似的东西 - 我有一个帮助类,我用它来测试运行后清理对象,我想保持漂亮,干净的“使用”语法。 I also wanted the option of not cleanup up if the test failed. 如果测试失败,我还想要不清理的选项。 What I came up with is to call Marshal.GetExceptionCode() . 我想出的是调用Marshal.GetExceptionCode() I don't know if this is appropriate for all cases, but for test code it seems to work fine. 我不知道这是否适用于所有情况,但对于测试代码,它似乎工作正常。

The best I can come up with would be: 我能想到的最好的是:

using (var scope = MyScopedBehavior.Begin())
{
  try
  {
    //Do stuff with scope here
  }
  catch(Exception)
  {
    scope.Cancel();
    throw;
  }
}

Of course, scope.Cancel() would make sure nothing happens in Dispose() 当然, scope.Cancel()会确保在Dispose()中没有任何反应

The following pattern avoids the problem with API misuse ie a scope completion method not being called ie omitted completely, or not being called because of a logical condition. 以下模式避免了API误用的问题,即未调用范围完成方法,即完全省略,或者由于逻辑条件而未被调用。 I think this answers your question more closely and is even less code for the API user. 我认为这会更密切地回答您的问题,并且API用户的代码更少。

Edit 编辑

Even more straightforward after Dan's comment: 丹的评论后更加直截了当:

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 would be (IMHO very) helpful if there were a variant of IDisposable whose Dispose method accepted a parameter to indicate what exception, if any, was pending when it was run. 如果存在IDisposable的变体,它的Dispose方法接受一个参数来指示运行时挂起的异常(如果有的话),那么(恕我直言)非常有帮助。 Among other things, in the event that Dispose is unable to perform the expected cleanup, it would be able to throw an exception which includes information about the earlier exception. 除其他外,如果Dispose无法执行预期的清理,它将能够抛出一个异常,其中包含有关早期异常的信息。 It would also allow a Dispose method to throw an exception if code "forgets" to do something that it was supposed to do within a using block, but not overwrite any other exception that might cause the using block to exit prematurely. 它还允许Dispose方法在代码“忘记”执行它应该在using块中执行的using抛出异常,但不会覆盖可能导致using块过早退出的任何其他异常。 Unfortunately, no such feature exists as of yet. 不幸的是,目前还没有这样的功能。

There are numerous articles which suggest means of using API functions to find out whether a pending exception exists. 有许多文章建议使用API​​函数来确定是否存在挂起的异常。 One major problem with such approaches is that it is possible that code may be running in a finally block for a try which completed successfully, but that may be nested in a finally block whose try exited prematurely. 这种方法的一个主要问题是代码可能在finally块中运行以进行成功完成的try ,但是可能嵌套在finally try过早退出的finally块中。 Even if a Dispose method could identify that such a situation existed, it would have no way of knowing which try block it "belonged" to. 即使Dispose方法可以识别存在这种情况,也无法知道它“属于”哪个try块。 One could formulate examples where either situation applies. 可以在任何一种情况适用的情况下制定实例。

As it is, the best approach is probably to have an explicit "success" method and assume failure if it's not called, and figure that the consequences of forgetting to call the "success" method should be obvious even if no exception is thrown. 事实上,最好的方法可能是有一个明确的“成功”方法,如果没有被调用则假设失败,并认为即使没有抛出异常,忘记调用“成功”方法的后果应该是显而易见的。 One thing that may be helpful as a simple utility method would be something like 作为一种简单的实用方法可能有用的一件事就是

T Success<T>(T returnValue)
{
  Success();
  return T;
}

thus allowing code like: 因此允许代码如下:

return scopeGuard.Success(thingThatMightThrow());

rather than 而不是

var result = thingThatMightThrow();
scopeGuard.Success();
return result;

I think the best way is to use write out try/catch/finally clause manually. 我认为最好的方法是手动使用写出try/catch/finally子句。 Study an item from the first 'Effective c#" book. A good C# hacker should know exactly what using expands to. It has changed a bit since .Net 1.1 - you can now have several using one under another. So, use reflector, and study the un-sugared code. 从第一本“有效的c#”书中学习一个项目。一个优秀的C#黑客应该确切地知道使用扩展的内容。它已经改变了一点点.Net 1.1 - 你现在可以有几个使用另一个。所以,使用反射器,和研究未加糖的代码。

Then, when you write your own code - either use the using or write your own stuff. 然后,当您编写自己的代码时 - 使用using或编写自己的东西。 It is not terribly hard, and a good thing to know. 这不是非常困难,也是一件好事。

You could get fancy with other tricks, but it feels too heavy, and even not efficient. 你可能会对其他技巧感到高兴,但感觉太沉重,甚至效率也不高。 Let me include a code sample. 让我包括一个代码示例。

LAZY WAY : 懒惰的方式

using (SqlConnection cn = new SqlConnection(connectionString))
using (SqlCommand cm = new SqlCommand(commandString, cn))
{
    cn.Open();
    cm.ExecuteNonQuery();
}

MANUAL WAY : 手动方式

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();
    }
}

Why not simply dispose from inside a try { } block at the very end, and not use a finally at all? 为什么不简单地从最终的try { }块中部署,而不是最终使用? This seems to be the behavior you're looking for. 这似乎是你正在寻找的行为。

This also seems more realistic in terms of how others might use your class. 就其他人如何使用您的课程而言,这似乎也更为现实。 Are you sure that everybody who ever uses it will never want to dispose in the case of an exception? 你确定每个使用它的人都不会想要在例外的情况下处理吗? Or should this behavior be handled by the consumer of the class? 或者这个行为是否应由该类的消费者处理?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM