Determine if executing in finally block due to exc

2019-01-23 22:52发布

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.

8条回答
Luminary・发光体
2楼-- · 2019-01-23 23:13

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

查看更多
三岁会撩人
3楼-- · 2019-01-23 23:14

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.

查看更多
爱情/是我丢掉的垃圾
4楼-- · 2019-01-23 23:20

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?

查看更多
聊天终结者
5楼-- · 2019-01-23 23:23

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();
    }
}
查看更多
做自己的国王
6楼-- · 2019-01-23 23:26

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

查看更多
The star\"
7楼-- · 2019-01-23 23:28

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.

查看更多
登录 后发表回答