判断是否由于抛出异常而在finally块中执行

发布于 2024-09-10 19:04:03 字数 1104 浏览 4 评论 0原文

是否可以确定代码当前是否由于抛出异常而在 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 模式。


实际上,这显然已经在此处<之前被问及并回答过/a>.可以通过一种非常黑客的方式进行检测。我实际上不会使用该技术,但知道它是可能的很有趣。

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? 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. 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.


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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

给不了的爱 2024-09-17 19:04:04

我所看到的完成此操作的方法需要一个额外的方法:

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

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

凹づ凸ル 2024-09-17 19:04:04

顺便说一句,IL 允许您指定与 finally 类似的 SEH fault 块,但仅在引发异常时才输入 - 您可以看到一个例子 此处,大约页面下方的 2/3。不幸的是,C# 没有公开此功能。

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. Unfortunately, C# doesn't expose this functionality.

み零 2024-09-17 19:04:04

我一直在寻找类似的单元测试工具 - 我有一个辅助类,用于在测试运行后清理对象,并且我想保留漂亮、干净的“using”语法。我还希望选择如果测试失败则不进行清理。我想出的是调用 Marshal.GetExceptionCode ()。我不知道这是否适合所有情况,但对于测试代码来说它似乎工作得很好。

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(). I don't know if this is appropriate for all cases, but for test code it seems to work fine.

一曲爱恨情仇 2024-09-17 19:04:04

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

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

当然,scope.Cancel() 会确保 Dispose() 中没有任何反应

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

时光暖心i 2024-09-17 19:04:04

下面的模式避免了 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;
        }
    }
}   

The following pattern avoids the problem with API misuse i.e. a scope completion method not being called i.e. omitted completely, or not being called because of a logical condition. I think this answers your question more closely and is even less code for the API user.

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;
        }
    }
}   
我ぃ本無心為│何有愛 2024-09-17 19:04:04

如果有一个 IDisposable 的变体,其 Dispose 方法接受一个参数来指示运行时待处理的异常(如果有),那将会(恕我直言,非常)有帮助。除此之外,如果 Dispose 无法执行预期的清理,它将能够抛出一个异常,其中包含有关先前异常的信息。如果代码“忘记”执行它应该在 using 块中执行的操作,它还允许 Dispose 方法抛出异常,但不会覆盖任何其他异常这可能会导致 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;

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. 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. 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. 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. 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. 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. 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;
天气好吗我好吗 2024-09-17 19:04:04

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

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

Why not simply dispose from inside a try { } block at the very end, and not use a finally at all? 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?

刘备忘录 2024-09-17 19:04:04

我认为最好的方法是手动使用 write out try/catch/finally 子句。研究第一本“Effective c#”书中的一个项目。一个优秀的 C# 黑客应该确切地知道 using 扩展到什么。自 .Net 1.1 以来它已经发生了一些变化 - 现在你可以在另一个下使用多个 using。所以,使用 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();
    }
}

I think the best way is to use write out try/catch/finally clause manually. 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.

Then, when you write your own code - either use the using or write your own stuff. 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();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文