代码之家  ›  专栏  ›  技术社区  ›  Dan Bryant

确定是否由于引发异常而在finally块中执行

  •  32
  • Dan Bryant  · 技术社区  · 16 年前

    是否可以确定代码当前是否在 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();
        }
    }
    

    还有其他方法可以实现这一点(将委托传递给用特定行为包围调用的函数),但我很好奇是否可以使用 不可分的 模式。


    事实上,这显然是以前问过和回答过的 here . 有可能以一种非常黑客的方式进行检测。我实际上不会使用这种技术,但知道它是可能的很有趣。

    8 回复  |  直到 13 年前
        1
  •  17
  •   Reed Copsey    16 年前

    我所看到的实现这一点的方法需要一种额外的方法:

    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 )

        2
  •  13
  •   thecoop    16 年前

    作为一个侧重点,il允许您指定seh fault 与类似的块 finally 但进入 只有 当抛出异常时-您可以看到一个示例 here ,大约2/3。不幸的是,C_没有公开这个功能。

        3
  •  7
  •   dmo    15 年前

    我在为单元测试寻找类似的东西——我有一个助手类,我在测试运行后用来清理对象,我想保持良好的、干净的“使用”语法。我还希望在测试失败的情况下不清理。我想到的是打电话 Marshal.GetExceptionCode() . 我不知道这是否适用于所有情况,但对于测试代码来说,它似乎工作得很好。

        4
  •  5
  •   Brian Genisio    16 年前

    我能想到的最好办法是:

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

    当然, scope.Cancel() 将确保Dispose()中没有发生任何事件

        5
  •  3
  •   Tim Lloyd    16 年前

    以下模式避免了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;
            }
        }
    }   
    
        6
  •  1
  •   Hamish Grubijan    16 年前

    我认为最好的办法是用写出来 try/catch/finally 子句手动。从第一本“有效C”书中学习一个项目。一个好的C黑客应该知道使用扩展到什么。自.NET 1.1以来,它已经发生了一些变化-现在可以在另一个下使用多个。所以,使用Reflector,研究无糖代码。

    然后,当您编写自己的代码时-或者使用 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();
        }
    }
    
        7
  •  1
  •   supercat    13 年前

    如果有 IDisposable 谁的 Dispose 方法接受了一个参数来指示运行异常时挂起的异常(如果有)。除此之外,如果 处置 无法执行预期的清理,它将能够引发包含有关早期异常的信息的异常。它还允许 处置 方法在代码“忘记”执行在 using 块,但不覆盖可能导致using块过早退出的任何其他异常。不幸的是,到目前为止还没有这样的功能。

    有许多文章建议使用API函数来查明是否存在挂起的异常。这种方法的一个主要问题是,代码可能在 finally A块 try 已成功完成,但可以嵌套在 最后 块体 尝试 过早退出。即使是 处置 方法可以识别出这种情况的存在,它无法知道 尝试 “属于”的块。在任何一种情况下,都可以举例说明。

    事实上,最好的方法可能是有一个明确的“成功”方法,如果不调用它,就假定失败,并且认为忘记调用“成功”方法的后果应该是显而易见的,即使没有抛出异常。作为一个简单的实用方法,有一件事可能会有所帮助,比如

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

    因此,允许如下代码:

    return scopeGuard.Success(thingThatMightThrow());
    

    而不是

    var result = thingThatMightThrow();
    scopeGuard.Success();
    return result;
    
        8
  •  0
  •   drharris    16 年前

    为什么不简单地从一个 try { } 在最末端阻塞,根本不使用finally?这似乎是你想要的行为。

    从其他人如何使用您的类来看,这似乎更为现实。您确定每个曾经使用过它的人在发生异常时都不会希望处理它吗?或者这个行为应该由类的消费者来处理?