c# - 是否有理由在 Try/Catch 上使用带有 ExceptionThrown 变量的 Try/Finally

标签 c# coding-style

我正在细读 .Net Reference Source并在 ButtonBase.cs 中找到了这颗 gem 在第 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;
}

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

最佳答案

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

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

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

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

编辑:

这个方法可能没有那么多争议,如果像这样简单地重命名它并删除任何对异常的引用,它会更有意义:

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

关于c# - 是否有理由在 Try/Catch 上使用带有 ExceptionThrown 变量的 Try/Finally,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18798364/

相关文章:

coding-style - LabVIEW中不鼓励使用集群吗?

python - var = [[0]*5]*5 有助于理解 python 列表?

python - 更多Python主义 : prefix subdirectory with parent folder name?

c# - 在用户控件中配置 Brush 的更好方法是什么

c# - 静态方法如何调用虚方法?

c# - 无法从 VS2017 中的 WSDL 生成代理

c# - 如何为 asp.net webapi 构建可重用的 .Net 客户端,包括 IQueryable 功能等

c# - 需要对象引用才能访问非静态成员

c++ - 您如何在大型 C++ 项目中实现单元测试?

java - 使用后将 "null"分配给每个应用程序中的对象