为什么“using”没有catch块?

10

我理解 "using" 的作用是保证对象的 Dispose 方法将被调用。但是如果 "using" 语句中出现异常应该如何处理呢? 如果发生异常,我需要在 "using" 语句中包装一个 try-catch 语句。例如:


假设在 using 参数内创建对象时出现异常

 try
 {
    // Exception in using parameter
    using (SqlConnection connection = new SqlConnection("LippertTheLeopard"))
    {
       connection.Open();
    }
 }
 catch (Exception ex)
 {

 }

或者在 using 范围内引发异常

 using (SqlConnection connection = new SqlConnection())
 {
    try
    {
       connection.Open();
    }
    catch (Exception ex)
    {

    }
 }

看起来,如果我已经需要使用try catch处理异常,那么也许我应该同时处理对象的释放。在这种情况下,“using”语句似乎没有帮助我解决问题。如何正确地使用“using”语句处理异常?我是否错过了更好的方法?

 SqlConnection connection2 = null;
 try
 {
    connection2 = new SqlConnection("z");
    connection2.Open();
 }
 catch (Exception ex)
 {

 }
 finally
 {
    IDisposable disp = connection2 as IDisposable;
    if (disp != null)
    {
       disp.Dispose();
    }
 }

使用"using"关键字的语法能否更加简洁易懂...
这样会更好:

 using (SqlConnection connection = new SqlConnection())
 {
    connection.Open();
 }
 catch(Exception ex)
 {
   // What went wrong? Well at least connection is Disposed
 }
8个回答

27
因为你会在无关的关键字中“隐藏”额外的功能。
不过,你可以这样编写代码。
using (...) try
{
}
catch (...)
{
}

这种方式代表了你的意图 —— 一个既是 using 语句又是 try 语句


不错,这似乎处理了 using 内部的任何错误,但它并没有涵盖 using 参数中的异常。知道这一点很好。 - SwDevMan81
1
好的,思考了一下,这与我的第二个例子相同。 - SwDevMan81
1
确实如此,但冗长程度要少得多。它看起来更像是真正适合的东西,而不是拼凑出来的东西。 - Joel Coehoorn
@Joel Coehoorn:非常感谢,能够从像您这样的高级成员那里听到积极的反馈总是很棒的 :) - Kevin Laity
1
Rep很少代表技能或专业知识,但我欣赏这种情感。 ;) - Joel Coehoorn
显示剩余2条评论

12

using 与错误处理无关。它是“在离开此块时调用Dispose”的速记法。你的第二个代码示例完全可接受...为什么要破坏已经工作良好的东西呢?


问题的关键是,为什么使用时没有可选的catch块。这更多是修辞性质,除非你在微软工作。 - Chap
我不会选择第二个示例。 在我看来,这是在错误级别处理错误的错误示例。让它“上升”到调用函数。 - Joel Coehoorn
1
@Joel同意在更大的范围内考虑,如果你有理由在那个级别上进行任何错误处理,那就是我会这样做的方式。 - Dave Swersky
那么,为什么ifwhile没有可选的catch块呢?更好的问题是,为什么应该有呢?catchtry一起使用,就像elseif一起使用。 - Pavel Minaev

8

using块只是try-finally块的语法糖。如果需要catch子句,只需使用try-catch-finally:

SqlConnection connection;
try 
{
    connection = new SqlConnection();
    connection.Open();
}
catch(Exception ex)
{
    // handle
}
finally 
{
    if (connection != null)
    {
        connection.Dispose();
    }
}

是的,相比于你理论上的“使用catch”,这需要更多的代码;我认为语言开发者并没有将其视为非常重要的事情,而且我从未感到过它的损失。


当抛出异常时,您将退出使用语句块,因此在我看来,使用try/catch更好。 - ChadNC
2
@ChadNC:显然你不理解using - 它可以保护你免受异常的影响,并且无论如何都会处理对象。 - Joel Coehoorn

5

我曾经遇到过需要这样做的情况。但更多时候,当我想这样做时,问题实际上出在我的设计上;我试图在错误的地方处理异常。

相反,我需要让它向上传递到下一个级别——在调用此代码的函数中处理它,而不是在此处直接处理。


+1 这是一个很好的观点。异常可能应该在上一级处理。 - SwDevMan81

2
一个有趣的想法,但这会让下面的内容变得有些混乱:

 using (SqlConnection connection = new SqlConnection())
 using (SqlCommand cmd = new SqlCommand())
 {
     connection.Open();
 }
 catch(Exception ex)
 {
     // Is connection valid? Is cmd valid?  how would you tell?
     // if the ctor of either throw do I get here?
 }

1
你的例子是无效的。"Stacked" using语句不是一种语言特性。你的代码等同于将第二个using嵌套在第一个using块内。这与连续写两个if语句而没有使用花括号是没有区别的。 - David Pfeffer
1
如果您想单独捕获每个异常,可以嵌套使用块。尽管我确信他们永远不会在语言中实现它,但我实际上喜欢OP的想法。这并不太有用,而且可能会令人困惑。 - Meta-Knight
2
@bytenik:那并不重要。这段代码仍然可能会让人们感到困惑。它并不需要是特殊功能才有影响。 - Joel Coehoorn

2

在我看来,您混淆了问题。资源管理(即对象处理)与异常处理是完全分离的。您在问题中描述的一对一映射只是一个非常特殊的情况。通常情况下,异常处理不会发生在使用范围结束的同一位置。或者您可能有多个try-catch区域在一个using块中。或...


1
finally块的目的是确保资源被清理。在使用{ try {} catch {} finally {} }的情况下,using变得多余。 - Matt Briggs
是的,但为什么要使用finally?在大多数情况下,将try/catch包装在using内更清晰且更短。 - Pavel Minaev

0
我建议您结合使用示例#1和#2。原因是您的using语句可能会读取文件并抛出异常(即文件未找到)。如果您不捕获它,则会出现未处理的异常。将try catch块放在using块内仅会捕获在执行using语句后发生的异常。在我看来,最好结合使用您的第一个和第二个示例。

0
“using”语句的目的是确保在执行退出代码块时,无论是通过 fall-through、异常还是return方式退出,都会发生某种类型的清理操作。当代码块通过任何一种方式退出时,它将调用using参数上的Dispose方法。从某种意义上说,代码块存在是为了让作为using参数指定的任何内容受益,通常该对象不关心代码块被退出的原因。

有几种不寻常的情况可能会有所帮助;它们的额外实用性水平远低于首先拥有using提供的水平(尽管可以认为比实现者认为适合提供的其他功能更好):

(1) 在封装其他IDisposable对象的对象的构造函数或工厂中,有一个非常常见的模式;如果构造函数或工厂通过异常退出,则应该Dispose封装的对象,但如果通过return退出,则不应该。目前,这种行为必须通过try/catch实现,或者通过将try/finally与标志组合来实现,但我认为如果有一个变体的using语句,它只会在通过异常退出时调用Dispose,或者一个keep using语句,它将使using语句使用的临时对象为空(因为using不能以标识符开头,所以可以通过一种类似于yield return的方式添加此功能)。

(2) 在某些情况下,如果finally关键字能够接受一个Exception参数将会很有帮助;它将保存导致受保护子句退出的异常(如果有),或者null(如果通过返回或穿透正常退出受保护子句),如果using块可以使用interface IDisposeExOnly {void DisposeEx(Exception ex);}Interface IDisposeEx : IDisposable, IDisposableExOnly {}(在编译时,如果实现了DisposeEx()则选择它,否则选择Dispose())。这可以使基于事务的对象安全地支持自动提交(即如果传入的异常为null则执行提交,否则回滚),并且还可以在Dispose因受保护子句中的问题而失败的情况下提供改进的日志记录(正确的做法是让Dispose抛出一个异常,该异常封装了调用它时挂起的异常和作为结果发生的异常,但目前没有干净的方法来实现这一点)。

我不知道微软是否会添加这样的功能;第一个和第二个部分的前半部分将完全在语言层面上处理。第二部分的后半部分将在框架层面上处理。

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