如何在C#中使用"using"捕获异常

38

给定此代码:

using (var conn = new SqlConnection("..."))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = "...";
        using (var reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                // ...
            }
        }
    }
}

我习惯于编写try/catch/finally块来访问我的数据,但是我正在接触到“using”这个方法,它似乎是一个更简单的方法。但是,我正在尝试弄清楚如何捕获可能发生的异常。

请您举一个捕获异常的例子?

编辑添加:

我被引导相信“using”是替代我的try/catch/finally块的一种方式。我知道using不会捕获异常,那么这又怎么能成为替代品呢?

7个回答

65

using并非设计用于捕获异常,它旨在为您提供一种轻松的方式来将一个需要被处理的对象包含在try/finally中。如果您需要捕获和处理异常,那么您需要将其扩展为完整的try/catch/finally或将包含的try/catch放在整个内容周围。


回答你的问题(using是否可以替代try/catch/finally?)不行, 绝大多数情况下,在使用可释放资源时,您通常不会立即处理异常,因为通常没有有用的操作可以执行。所以,它提供了一种方便的方法来确保资源得到清理,无论您试图做什么工作是否成功。

通常,处理可释放资源的代码在处理异常时处于过低的级别,无法决定在出现故障时采取的正确操作,因此异常继续传播给调用者,由调用者决定采取什么操作(例如重试、失败、日志等)。只有当您要转换异常时(我猜这是您的数据访问层正在执行的操作),才会使用带catch块的可释放资源。


大多数情况下,当使用一次性资源时,你通常不会在那里处理异常,因为通常你无法做任何有用的事情。但是,using块也被滥用于日志记录、编写结构化文件等其他用途,因此能够在主题的Dispose方法中获取异常详细信息确实具有某些(虽然不建议)实用性。 - Dai

17

这是一个HTML段落。

using (var cmd = new SqlCommand("SELECT * FROM Customers"))
{
    cmd.CommandTimeout = 60000;
    ...
}

这与以下语法糖几乎在语义上完全相同:

{
    SqlCommand cmd = new SqlCommand("SELECT * FROM Customers");
    try
    {
        cmd.CommandTimeout = 60000;
        ...
    }
    finally
    {
        if (cmd is IDisposable disposable)
        {
            disposable.Dispose();
        }
    }
}

请注意:

  • cmd局部变量无法重新分配,这是C#中为数不多的几种情况之一,其中包括foreach
  • 外部大括号({})定义了一个匿名作用域(也称为复合语句或"块"),限制了cmd的词法作用域,因此在其被释放后不能通过名称引用它(但如果你真的想,你仍然可以使用别名)。
  • 虽然编译器会在编译时执行“is- IDisposable ”检查(否则using语句将无法编译),但如果主题(cmd)只通过公共void Dispose()方法而不是显式实现来实现IDisposable,则需要一个隐藏的隐式转换。

因此,当人们告诉你“using”是try/catch/finally的替代品时,他们暗示你应该使用长格式,但在catch块中添加以下内容:

var cmd = new SqlCommand("SELECT * FROM Customers");
try
{
    cmd.CommandTimeout = 60000;
    ...
}
catch (Exception ex)
{
    ...//your stuff here
}
finally
{
    if (cmd != null)
        cmd.Dispose();
}

我认为cmd的初始化需要在try块内进行。此外,你的finally块可以通过Null传播进一步简化:cmd?.Dispose(); - Bhu

14

将所有的using语句包装在try/catch中。就像其他人说的那样,using是用于清理实现IDisposable接口的类。

try
{

 using (var conn = new SqlConnection("..."))
 {
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = "...";
        using (var reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                // ...
            }
        }
    }
 }
}
catch(Exception ex)
{
//Handle, log, rethrow exception
}

这种方式与在try/catch/finally中调用dispose有何优势? - GregD
1
Erik,你为什么要在finally块中调用dispose?当你可以使用using语句时呢?如果你使用finally块,那么你必须在try块外声明对象... - Chuck Conway
内部来说,这与使用语句生成的 IL 是相同的,Charles。它只是一种语法上的糖果。 - Chris Marisic
起初我想不到那个解决方案,尝试在“using”括号中编写“try”子句,然后当然编译器说你在干嘛:D。然后来到页面上。并说:“哦,我的上帝,怎么可能想不到这个”。 - Tarık Özgün Güner

7

如果你想捕获异常,最好还是使用try/catch/finally语句。只需要将.Dispose()调用放在finally块中即可。


1
他可以将using语句放入try/catch块中。如果捕获到错误,则会在进入catch块之前调用Dispose(如果存在)。 - Leonidas
2
没错,但这样你就需要再多一层缩进,而且 using 语句也不那么方便了。我更喜欢只有一个 try/catch/finally 处理所有事情(但这只是个小风格问题)。 - Kevin Tighe
1
即使使用简单的错误处理,编写代码看起来像调用Dispose()但在某些情况下失败仍然很容易。 using()至少确保正确清理,即使它使得粒度异常处理几乎不可能。 - binki

6

Using语句与异常无关。使用代码块只是确保在退出该代码块时对使用的对象调用了Dispose方法。例如:

using(SqlConnection conn = new SqlConnection(conStr))
{
   //use conn
}//Dispose is called here on conn.

如果打开连接引发异常(或者在该块中的任何其他内容),它仍将冒泡到顶部,并像任何其他未处理的异常一样。


5

您仍然可以像以前一样捕获(或忽略)异常。重点是您不再需要担心处理数据库连接的释放问题。

也就是说,如果您的应用程序需要因其他原因(例如日志记录)捕获异常,则可以继续这样做,但如果您只想释放数据库连接,则不再需要这样做:

using (SqlConnection conn = new SqlConnection(...))
{
    // do your db work here
    // whatever happens the connection will be safely disposed
}

如果您想因其他原因捕获异常,仍然可以这样做:
try
{
    using (SqlConnection conn = new SqlConnection(...))
    {
        // do your db work here
        // whatever happens the connection will be safely disposed
    }
}
catch (Exception ex)
{
    // do your stuff here (eg, logging)
    // nb: the connection will already have been disposed at this point
}
finally
{
    // if you need it
}

0
using块放入try catch块中。它们隐式的finally语句将在外部catch语句及其内容之前执行,以处理它们的对象。

这与其他答案有何不同,例如 Chuck Conway 的答案? - Klaus Gütter
它解释了隐式的“finally”语句将在外部块“catch”语句之前执行。我认为这是一个重要的强调,即使最初的问题没有明确表达,或者至少我理解了这种需求。 - Ciro Corvino

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