是否有理由使用Try / Finally和ExceptionThrown变量而不是Try / Catch

时间:2013-09-14 04:41:53

标签: c# coding-style

我正在仔细阅读.Net Reference Source并在ButtonBase.cs的第408行找到了这个宝石:

bool exceptionThrown = true;
try
{ 
    OnClick();
    exceptionThrown = false; 
}
finally
{
    if (exceptionThrown) 
    {
        // Cleanup the buttonbase state 
        SetIsPressed(false); 
        ReleaseMouseCapture();
    } 
}

问题是,什么会激励某人使用exceptionThrown标志而不是将其写为

try 
{
    OnClick();
}
catch
{
    SetIsPressed(false);
    ReleaseMouseCapture();
    throw;
}

它只是风格还是我遗漏了一些副作用?

5 个答案:

答案 0 :(得分:11)

此代码的原因有两个。是的,格雷格提到,它不需要重新抛出异常..但这不是真正的原因。

真正的原因是语义之一。不应使用例外来处理控制流。这样做是为了处理这样一个事实:如果一个异常被抛出一个按钮,它可以将按钮视觉状态保持为“按下”状态。这并不是真正“处理”异常。这仅仅是在抛出异常时纠正视觉问题。

此代码不关心异常是什么,并且它不想捕获所有异常,因为这是不好的做法。此外,代码没有做任何异常......它只是说“嘿,如果我们到达函数的末尾,那么我们都很好。如果我们没有,那么让我们重置按钮状态只是为了确定“。

因此,这不是真正的异常处理,因此它不会捕获异常。它只是注意到抛出异常并做了一些清理。

编辑:

这种方法可能不那么有争议,如果只是像这样重命名,删除对异常的任何引用,那就更有意义了:

bool cleanupRequired = true;
try
{ 
    OnClick();
    cleanupRequired = false; 
}
finally
{
    if (cleanupRequired) 
    {
        // Cleanup the buttonbase state 
        SetIsPressed(false); 
        ReleaseMouseCapture();
    } 
 }

编辑:

为了支持我在下面的评论,我编写了以下测试程序来测试场景:

static void Main(string[] args)
{
    TimeSpan ts = new TimeSpan();
    TimeSpan ts2 = new TimeSpan();
    TimeSpan ts3 = new TimeSpan();
    TimeSpan ts4 = new TimeSpan();
    TimeSpan ts5 = new TimeSpan();
    TimeSpan ts6 = new TimeSpan();
    TimeSpan ts7 = new TimeSpan();
    TimeSpan ts8 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    // throw away first run
    for (int i = 0; i < 2; i++)
    {
        sw.Restart();
        try
        {
            throw new NotImplementedException();
        }
        catch
        {
            ts = sw.Elapsed;
        }
        sw.Stop();
        ts2 = sw.Elapsed;

        try
        {
            sw.Restart();
            try
            {
                throw new NotImplementedException();
            }
            finally
            {
                ts3 = sw.Elapsed;
            }
        }
        catch
        {
            ts4 = sw.Elapsed;
        }
        sw.Stop();
        ts5 = sw.Elapsed;

        try
        {
            sw.Restart();
            try
            {
                throw new NotImplementedException();
            }
            catch
            {
                ts6 = sw.Elapsed;
                throw;
            }
        }
        catch
        {
            ts7 = sw.Elapsed;
        }
        sw.Stop();
        ts8 = sw.Elapsed;
    }
    Console.WriteLine(ts);
    Console.WriteLine(ts2);
    Console.WriteLine(ts3);
    Console.WriteLine(ts4);
    Console.WriteLine(ts5);
    Console.WriteLine(ts6);
    Console.WriteLine(ts7);
    Console.WriteLine(ts8);
    Console.ReadLine();
}

我得到了以下结果(我将它们分开以便于阅读):

00:00:00.0028424
00:00:00.0028453

00:00:00.0028354
00:00:00.0028401
00:00:00.0028427

00:00:00.0028404
00:00:00.0057907
00:00:00.0057951

最后3个显示当使用throw;重新抛出异常时,它不会简单地传递现有异常,它必须重新创建异常并重新抛出它,花费两倍的时间。

正如我们所看到的,捕获异常和捕获之间没有显着差异,但使用finally。但是,重新抛出异常是成本的来源。

这是在VS 2012 Update 3中运行的。

编辑:

没有调试器的计时。正如您所看到的,重新抛出仍然是昂贵的两倍:

00:00:00.0000149
00:00:00.0000154

00:00:00.0000137
00:00:00.0000140
00:00:00.0000146

00:00:00.0000137
00:00:00.0000248
00:00:00.0000251

答案 1 :(得分:8)

如果你使用try,那么捕获异常必须抛出两次。使用try,最后只抛出一个异常,因此很多更有效,尤其是,如果经常调用此代码。

答案 2 :(得分:2)

显然,throw;会产生副作用,至少仍会通过.Net 2删除堆栈跟踪信息。我想这个语法用于解决早期throw;的实现问题。框架的版本。

This blog article给出了两个例子,说明throw;如何不等同于永远不会捕获异常。

问题Showing stack trace of exception that is re-thrown, rather than stack trace from throw point给出了一个场景,重新抛出异常会导致Visual Studio中的不同操作。

答案 3 :(得分:1)

好吧,我能想到的一个原因是(将来)增加了捕获特定异常的必要性,同时保持了相同的清理行为。请考虑以下示例:

try 
{
    OnClick();
}
catch(System.SomeSpecificException ex)
{
    Handle(ex);
    throw;
    // now, we're missing the cleanup
}
catch
{
    SetIsPressed(false);
    ReleaseMouseCapture();
    throw;
}

VS

var exceptionThrown = true;
try 
{
    OnClick();
    exceptionThrown = false;
}
catch(System.SomeSpecificException ex)
{
    Handle(ex);
    throw;
    // whoa, I added specific handler, but cleanup is called anyway!
}
finally
{
    if (exceptionThrown) 
    {
        // Cleanup the buttonbase state 
        SetIsPressed(false); 
        ReleaseMouseCapture();
    } 
}

答案 4 :(得分:0)

小记:OnClick是一种虚拟方法。它应该被用户编写的代码覆盖,这可能会抛出任意异常。所写的代码表达了“从那些过度使用这种方法的人所造成的混乱中清理”的语义。