8

我了解try-catch 的工作原理以及try-finally 的工作原理,但我发现自己(通常)在两个完全不同的场景中使用它们:

  • try-finally(或using在 C# 和 VB 中)主要用于一些中型代码块,这些代码块使用一些需要正确处理的资源。
  • try-catch主要用于
    • 围绕可能以非常特定的方式失败的单个语句或
    • (作为一个包罗万象的)在应用程序的一个非常高的级别,通常直接在一些用户界面操作之下。

以我的经验,try-catch-finally是合适的情况,即我想要捕获某些特定异常的块与我使用一些一次性资源的块完全相同的情况非常罕见。然而,C#VBJava的语言设计者似乎认为这是一个非常常见的场景。VB 设计者甚至考虑将catch添加到using.

我错过了什么吗?还是我对try-catch的限制性使用过于迂腐?


编辑:澄清一下:我的代码通常看起来像这样(为清楚起见展开功能):

Try
    do something
    Aquire Resource (e.g. get DB connection)
    Try 
        do something
        Try
            do something that can fail
        Catch SomeException
            handle expected error
        do something else... 
    Finally 
        Close Resource (e.g. close DB connection)
    do something
Catch all
    handle unexpected errors

这似乎比将两个catch中的任何一个都放在与finally相同的水平上只是为了避免缩进更有意义。

4

10 回答 10

16

来自MSDN的引用

catch 和 finally 一起的常见用法是在 try 块中获取和使用资源,在 catch 块中处理异常情况,并在 finally 块中释放资源。

所以为了更清楚,想想你想要运行的代码,在 99% 的情况下它运行得很好,但是在块的某个地方可能会发生错误,你不知道在哪里和创建的资源是昂贵的。

为了 100% 确保资源已被处理,您使用 finally 块,但是,您希望指出发生错误的 1% 的情况,因此您可能需要在 catch- ing部分。

这是一个非常常见的场景。

编辑 - 一个实际的例子

这里有一些很好的例子:SQL Transactions with SqlTransaction Class。这只是使用 Try、Catch 和 finally 的众多方法之一,它很好地证明了这一点,尽管using(var x = new SqlTranscation)有时可能是有效的。

所以这里。

var connection = new SqlConnection();

var command = new SqlCommand();

var transaction = connection.BeginTransaction();

command.Connection = connection;
command.Transaction = transaction;

更有趣的部分来了

///
/// Try to drop a database
///
try
{
    connection.Open();

    command.CommandText = "drop database Nothwind";

    command.ExecuteNonQuery();
}

所以让我们假设上面由于某种原因失败并抛出了异常

///
/// Due to insufficient priviligies we couldn't do it, an exception was thrown
///
catch(Exception ex)
{
    transaction.Rollback();
}

事务将被回滚!请记住,您对 try/catch 中的对象所做的更改不会被回滚,即使您将其嵌套在 using 中也不会回滚!

///
/// Clean up the resources
///
finally
{

    connection.Close();
    transaction = null;
    command = null;
    connection = null;
}

现在资源被清理了!

于 2010-02-17T10:23:22.270 回答
12

不是你的问题的答案,而是一个有趣的事实。

C# 编译器的 Microsoft 实现实际上无法处理 try-catch-finally。当我们解析代码时

try { Foo() } 
catch(Exception e) { Bar(e); }
finally { Blah(); }

我们实际上假装代码是写的

try 
{
   try { Foo(); }
   catch(Exception e) { Bar(e); }
}
finally { Blah(); }

这样编译器的其余部分——语义分析器、可达性检查器、代码生成器等等——只需要处理 try-catch 和 try-finally,而不是 try-catch-finally。在我看来,这是一个愚蠢的早期转型,但它确实有效。

于 2010-02-17T15:11:42.233 回答
10

例子:

Try
   Get DB connection from pool
   Insert value in table
   do something else...
Catch
   I have an error... e.g.: table already contained the row I was adding
   or maybe I couldn't get the Connection at all???
Finally
   if DB connection is not null, close it.

你真的找不到更“常见”的例子。遗憾的是,有些人仍然忘记将紧密连接放在它所属的最后,而不是在 Try 块中...... :(

于 2010-02-17T10:29:06.493 回答
6

经常写这样的代码:

Handle h = ...
try {
   // lots of statements that use handle
} catch (SomeException ex) {
   // report exception, wrap it in another one, etc
} catch (SomeOtherException ex) {
   // ...
} finally {
   h.close();
}

所以也许你只是过于迂腐......例如通过在个别语句周围放置try / catch。(有时这是必要的,但根据我的经验,您通常不需要如此细粒度。)

于 2010-02-17T10:28:17.697 回答
4

嵌套 try/catch/finally 块没有任何问题。我实际上经常使用这个。即,当我使用一些需要处理或关闭的资源但我只想要一个围绕较大代码单元的单个 catch 块时,如果其中发生某些错误,则需要中止该代码单元。

try {
    // some code
    SomeResource res = new SomeResource();
    try {
        res.use();
    } finally {
        res.close();
    }
    // some more code
} catch( Exception ex ) {
    handleError( ex );
}

这在任何一种情况下都会尽早关闭资源(错误与否),但仍会在单个 catch 块中处理创建或使用资源的所有可能错误。

于 2010-02-17T10:46:09.400 回答
3

我觉得你说得很对。来自 。Net Framework 设计指南,由 Microsoft 的一些顶级架构师编写:

不要过度捕捞。通常应该允许异常向上传播调用堆栈。

在编写良好的代码中,try-finally [或 using] 远比 try-catch 更常见。乍一看似乎违反直觉,但在数量惊人的情况下不需要 catch 块。另一方面,您应该始终考虑 try-finally [或 using] 是否可以用于清理。

第 230 页第 7.2 节

于 2010-02-17T14:13:19.573 回答
1

如果您需要在所有情况下处理某些内容并且您使用该案例记录错误和/或通知用户,我几乎总是会使用 try-catch-finaly。

于 2010-02-17T10:19:07.457 回答
1

怎么样:

  将 mainException 调暗为 Exception = Nothing

  尝试
    ... 开始交易
    ...确认交易
  将 Ex 捕获为异常
    mainException = Ex
    扔
  最后
    尝试
      ... 清理,如果未确认则回滚交易
    将 Ex 捕获为异常
      抛出新的 RollbackFailureException(mainException, Ex)
    结束尝试
  结束尝试

这里假设 RollbackFailureException 包含“OriginalException”字段以及“InnerException”,并接受两者的参数。人们不想隐藏回滚期间发生异常的事实,但也不想丢失导致回滚的原始异常(并且可能会提供一些关于回滚发生原因的线索)。

于 2010-07-28T17:35:28.510 回答
0

另一种用途是在使用 System.Web.Mail 邮件对象发送电子邮件时为电子邮件附件设置文件句柄。当我必须以编程方式打开 Crystal Report、将其保存到磁盘、将其附加到电子邮件中,然后从磁盘中删除它时,我发现了这一点。Final 中需要一个显式的 .Dispose() 以确保我可以删除它,尤其是在抛出异常的情况下。

于 2010-02-17T16:11:49.527 回答
-1

以我的经验,try-catch-finally 合适的情况,即我想要捕获某些特定异常的块与我使用一些一次性资源的块完全相同的情况非常罕见。然而,C#、VB 和 Java 的语言设计者似乎认为这是一个非常常见的场景。VB 设计者甚至考虑在使用中添加捕获。

你:

try {
   //use resource
} catch (FirstException e) {
   // dispose resource
   // log first exception
} catch (SecondException e) {
   // dispose resource
   // log first exception
} catch (ThirdException e) {
   // dispose resource
   // log first exception
}

我:

try {
  //use resource
} catch (FirstException e) {
  // log first exception
} catch (SecondException e) {
  // log first exception
} catch (ThirdException e) {
  // log first exception
} finally {
  // dispose resource
}

感觉不尊重?)

于 2010-02-17T10:31:13.257 回答