何时需要使用finally?

10

我知道如何使用try-catch-finally。然而,我不理解使用finally的优势,因为我总是可以将代码放在try-catch块之后。 有清晰的示例吗?

我知道如何使用try-catch-finally。然而,我不明白使用finally的好处,因为我可以把代码放在try-catch块之后。是否有明显的例子呢?

5
“我总是能把代码放在try-catch块之后。” - 这并不相当于finally;如果catch块没有捕获异常或重新抛出它,程序不会执行到finally块。 - Ani
11个回答

4

你需要一个finally,因为你不应该总是有一个catch:

void M()
{
    var fs = new FileStream(...);
    try
    {
       fs.Write(...);
    }
    finally
    {
       fs.Close();
    }
}

上述方法不会捕获使用 fs 时的错误,而是将它们留给调用者。但是,它应该始终关闭流。

请注意,这种类型的代码通常会使用 using() {} 块,但这只是try/finally的缩写。为了完整起见:

    using(var fs = new FileStream(...))
    {
       fs.Write(...);
    } // invisible finally here

嗯,我仍然不明白为什么我不能将fs.close放在单独的语句中,即不在finally块中。 - Tray13
@Tray13:你会把它放在哪里以便即使没有catch块抛出异常,资源也会被清理? - Jon Skeet
@tray1:如果fs.Write()抛出异常,我的fs.Close()将会运行。这就是try/finally的全部意义所在。按照你的建议,文件将保持打开状态。 - H H

4

它几乎总是用于清理,通常是通过using语句隐式地使用:

FileStream stream = new FileStream(...);
try
{
    // Read some stuff
}
finally
{
    stream.Dispose();
}

现在这并不等同于。
FileStream stream = new FileStream(...);
// Read some stuff
stream.Dispose();

因为“读一些东西”的代码可能会抛出异常或返回错误,我们需要在执行完成后释放流。因此,使用finally块通常用于资源清理。不过,在C#中,通过using语句通常隐含了这个块:
using (FileStream stream = new FileStream(...))
{
    // Read some stuff
} // Dispose called automatically

finally块在Java中比C#更常见,这正是因为using语句。在C#中,我很少编写自己的finally块。


3
try 
{
    DoSomethingImportant();
}
finally
{
    ItIsRidiculouslyImportantThatThisRuns();
}

如果你有一个 finally 块,在 try 退出时其中的代码是保证会运行的。如果你将代码放在 try/catch 外面,那就不是这样了。一种更常见的例子是在使用 using 语句时,用于可释放资源。

using (StreamReader reader = new StreamReader(filename))
{
}

扩展为

StreamReader reader = null;
try
{
    reader = new StreamReader(filename);
    // do work
}
finally 
{
    if (reader != null)
       ((IDisposable)reader).Dispose();
}

这确保所有未受管理的资源都被释放和处理,即使在 try 中出现异常的情况下也是如此。

*请注意,有些情况下控制流程不会退出 try 块,因此 finally 块实际上不会运行。例如,PowerFailureException


2

即使以下情况发生,finally中的代码仍将执行:

  • try块或catch块中有return语句
    或者
  • catch块重新抛出异常

示例:

public int Foo()
{
  try
  {
    MethodThatCausesException();
  }
  catch
  {
    return 0;
  }

  // this will NOT be executed
  ReleaseResources();
}

public int Bar()
{
  try
  {
    MethodThatCausesException();
  }
  catch
  {
    return 0;
  }
  finally
  {
    // this will be executed
    ReleaseResources();
  }
}

2
更新:实际上这不是一个很好的答案。另一方面,也许这是一个好答案,因为它展示了一个完美的例子,即finally成功地确保清理工作,而开发人员(比如我)可能无法确保清理工作。在下面的代码中,考虑抛出一个除SpecificException以外的异常的情况。然后第一个示例仍将执行清理工作,而第二个示例则不会,尽管开发人员可能会认为“我捕获了异常并处理了它,所以肯定会运行后续代码。”

每个人都给出了使用try/finally没有catch的原因。即使你正在抛出异常,使用catch仍然有意义,考虑需要返回值的情况*。

try
{
    DoSomethingTricky();
    return true;
}
catch (SpecificException ex)
{
    LogException(ex);
    return false;
}
finally
{
    DoImportantCleanup();
}

如果没有 finally,我认为另一种替代方法的可读性会稍微差一些:

bool success;

try
{
    DoSomethingTricky();
    success = true;
}
catch (SpecificException ex)
{
    LogException(ex);
    success = false;
}

DoImportantCleanup();
return success;

*我认为更好的try/catch/finally示例是在catch块中重新抛出异常(使用throw,而不是throw ex——但这是另一个话题),因此finally是必要的,否则try/catch后面的代码将不会运行。通常情况下,这可以通过对IDisposable资源使用using语句来实现,但并非总是如此。有时清理工作不仅仅是调用Dispose方法。


1
当然,如果抛出的异常不是SpecificException之外的其他异常,DoImportantCleanup将不会发生。 - Massif
你们说得对。我已经添加了一个介绍段落来解决这个问题。 - Dan Tao

1

你不一定要用它来处理异常。你可以使用try/finally在块中的每个return之前执行一些清理工作。


哦,我以为只有 try 块结束时才会出现异常。如果整个方法结束了,那对我来说就很清楚了。 - Tray13

1

无论是否发生错误,finally块始终会被执行。通常用于清理目的。

对于您的问题,Catch的一般用途是将错误抛回给调用者,在这种情况下,代码仍然会最终执行。


0

如果在 catch 块中发生异常(或重新抛出异常),则 catch 后面的代码将不会被执行 - 相反,finally 中的代码仍将被执行。

此外,即使使用 return 退出方法,finally 中的代码也会被执行。

当处理需要关闭的外部资源(如文件)时,finally 特别方便:

Stream file;
try
{
  file = File.Open(/**/);
  //...
  if (someCondition)
     return;
  //...
}
catch (Exception ex)
{
   //Notify the user
}
finally
{
  if (file != null)
    file.Close();
}

请注意,但在这个例子中,您也可以使用“using”:
using (Stream file = File.Open(/**/))
{
  //Code
}

0
例如,在过程中,您可以禁用 WinForm...
try
{
this.Enabled = false;
// some process
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
this.Enabled = true;
}

0

无论异常是否在catch块中被重新抛出,finally块始终会被执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接