37

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模式来做到这一点。


实际上,这显然在此之前已被问及。有可能以一种非常骇人听闻的方式进行检测。我实际上不会使用这种技术,但有趣的是知道它是可能的。

4

8 回答 8

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)。

于 2010-07-21T16:27:10.260 回答
15

另外,IL 允许您指定fault类似于finally仅在引发异常时输入的 SEH 块 - 您可以在此处查看示例,大约在页面下方的 2/3 处。不幸的是,C# 没有公开这个功能。

于 2010-07-21T16:44:58.650 回答
7

我一直在为单元测试寻找类似的东西——我有一个帮助类,用于在测试运行后清理对象,我想保持漂亮、干净的“使用”语法。如果测试失败,我还想要不清理的选项。我想出的是调用Marshal.GetExceptionCode()。我不知道这是否适用于所有情况,但对于测试代码,它似乎工作正常。

于 2010-10-07T17:19:26.750 回答
5

我能想到的最好的方法是:

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

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

于 2010-07-21T16:27:42.833 回答
4

以下模式避免了 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;
        }
    }
}   
于 2010-07-21T16:49:46.323 回答
1

如果有一个变体IDisposableDispose方法接受一个参数来指示在运行时挂起的异常(如果有),那将是非常有用的(恕我直言)。除其他外,如果Dispose无法执行预期的清理,它将能够引发异常,其中包含有关早期异常的信息。Dispose如果代码“忘记”执行它应该在块中执行的操作,它还允许方法抛出异常using,但不会覆盖任何其他可能导致 using 块过早退出的异常。不幸的是,目前还没有这样的功能。

有许多文章建议使用 API 函数来确定是否存在未决异常。这种方法的一个主要问题是代码可能在一个成功完成的finally块中运行try,但它可能嵌套在一个过早退出的finally块中。try即使一种Dispose方法可以识别出这种情况的存在,它也无法知道try它“属于”哪个块。可以制定适用于任何一种情况的示例。

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

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

因此允许如下代码:

return scopeGuard.Success(thingThatMightThrow());

而不是

var result = thingThatMightThrow();
scopeGuard.Success();
return result;
于 2013-06-05T15:59:05.567 回答
1

我认为最好的方法是手动使用 write outtry/catch/finally子句。研究第一本“Effective c#”书中的一个项目。一个好的 C# 黑客应该确切地知道 using 扩展为什么。自 .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();
    }
}
于 2010-07-21T16:26:30.870 回答
0

为什么不简单地try { }在最后从块内部处理,而不使用 finally 呢?这似乎是您正在寻找的行为。

就其他人如何使用您的课程而言,这似乎也更现实。你确定每个曾经使用过它的人都不想在异常情况下进行处理吗?或者这种行为应该由类的消费者处理?

于 2010-07-21T16:30:15.100 回答