28

我只是在看 using 语句,我一直都知道它的作用,但直到现在还没有尝试使用它,我想出了以下代码:

 using (SqlCommand cmd = 
     new SqlCommand(reportDataSource, 
         new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)))
 {
     cmd.CommandType = CommandType.StoredProcedure;
     cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
     cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
     cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
     cmd.Connection.Open();

     DataSet dset = new DataSet();
     new SqlDataAdapter(cmd).Fill(dset);
     this.gridDataSource.DataSource = dset.Tables[0];
 }

这似乎可行,但是这有什么意义,因为据我所知,我仍然需要将其包含在 try catch 块中以捕获不可预见的错误,例如 sql server 关闭。我错过了什么吗?

据我目前所见,它只是阻止我关闭和处理 cmd,但由于仍然需要 try catch,所以会有更多的代码行。

4

16 回答 16

56

在进行 IO 工作时,我编写代码以期望出现异常。

SqlConnection conn = null;
SqlCommand cmd = null;

try
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString)
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;

        conn.Open(); //opens connection

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch(Exception ex)
{
    Logger.Log(ex);
    throw;
}
finally
{
    if(conn != null)
        conn.Dispose();

        if(cmd != null)
        cmd.Dispose();
}

编辑:明确地说,我在这里避免使用 using块,因为我认为登录这样的情况很重要。经验告诉我,你永远不知道会出现什么样的奇怪异常。在这种情况下登录可能会帮助您检测死锁,或找出架构更改影响了您的代码库中很少使用和很少测试的部分,或任何数量的其他问题。

编辑 2:有人可能会争辩说,在这种情况下 using 块可以包装 try/catch,这是完全有效且功能等效的。这真的归结为偏好。您是否想以处理自己的处置为代价来避免额外的嵌套?或者您是否会产生额外的嵌套以进行自动处理。我觉得前者更干净,所以我这样做。但是,如果我在我正在工作的代码库中找到后者,我不会重写后者。

编辑 3:我真的非常希望 MS 已经创建了一个更明确的 using() 版本,它可以更直观地了解实际发生的情况,并在这种情况下提供更大的灵活性。考虑以下虚构代码:

SqlConnection conn = null;
SqlCommand cmd = null;

using(conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString),
          cmd = new SqlCommand(reportDataSource, conn)
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString);
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
        cmd.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch(Exception ex)
{
    Logger.Log(ex);
    throw;
}

using 语句只是在 finally 中使用 Dispose() 调用创建 try/finally。为什么不给开发者一个统一的处理方式和异常处理方式呢?

于 2008-10-30T01:23:48.517 回答
18

此代码应如下所示,以确保及时关闭连接。仅关闭命令不会关闭连接:

using (SqlConnection con = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, con))
         {
             cmd.CommandType = CommandType.StoredProcedure;
             cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
             cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
             cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
             cmd.Connection.Open();

             DataSet dset = new DataSet();
             new SqlDataAdapter(cmd).Fill(dset);
             this.gridDataSource.DataSource = dset.Tables[0];
         }

要回答您的问题,您可以在 finally 块中执行相同操作,但这很好地限定了代码范围并确保您记得清理。

于 2008-10-30T01:18:13.987 回答
14

using在这种情况下,如果您无论如何都要有一个 // 块,那么在这种情况下使用语句try可能没有catch任何好处。finally如您所知,该语句是处理对象的/using的语法糖。如果你要拥有自己的/无论如何,你当然可以自己做。tryfinallyIDisposabletryfinallyDispose

这实际上主要归结为样式 - 您的团队可能对语句更满意,using或者using语句可能会使代码看起来更干净。

但是,如果using声明将隐藏的样板文件仍然存在,那么如果这是您的偏好,请继续自己处理事情。

于 2008-10-30T04:05:52.340 回答
6

如果您的代码如下所示:

using (SqlCommand cmd = new SqlCommand(...))
{
  try
  {
    /* call stored procedure */
  }
  catch (SqlException ex)
  {
    /* handles the exception. does not rethrow the exception */
  }
}

然后我会重构它以使用 try.. catch.. finally 代替。

SqlCommand cmd = new SqlCommand(...)
try
{
  /* call stored procedure */
}
catch (SqlException ex)
{
  /* handles the exception and does not ignore it */
}
finally
{
   if (cmd!=null) cmd.Dispose();
}

在这种情况下,我将处理异常,所以我别无选择,只能添加该 try..catch,我不妨放入 finally 子句并为自己保存另一个嵌套级别。请注意,我必须在 catch 块中做一些事情,而不仅仅是忽略异常。

于 2008-10-30T03:11:39.720 回答
5

C# 规范(ECMA-334 第 4 版)第 15.13 节详细阐述了 Chris Ballance 所说的“一条 using 语句被翻译成三个部分:获取、使用和处置。资源的使用隐式包含在 try 语句中,其中包括finally 子句。这个 finally 子句处理资源。如果获取了 null 资源,则不会调用 Dispose,也不会引发异常。

描述接近 2 页 - 值得一读。

根据我的经验,SqlConnection/SqlCommand 可以以多种方式生成错误,以至于您几乎需要处理抛出的异常而不是处理预期的行为。我不确定我是否想要在这里使用 using 子句,因为我希望能够自己处理 null 资源案例。

于 2008-10-30T02:31:11.080 回答
4

using 不是关于捕获异常。这是关于正确处理垃圾收集器视图之外的资源。

于 2008-10-30T01:01:57.313 回答
4

“使用”的一个问题是它不处理异常。如果“using”的设计者可以选择在其语法中添加“catch”,如下面的伪代码,它会更有用:

using (...MyDisposableObj...)
{

   ... use MyDisposableObj ...

catch (exception)

   ... handle exception ...

}

it could even have an optional "finally" clause to cleanup anything other than the "MyDisposableObj" allocated at the beginning of the "using" statement... like:

using (...MyDisposableObj...)
{

   ... use MyDisposableObj ...
   ... open a file or db connection ...

catch (exception)

   ... handle exception ...

finally

   ... close the file or db connection ...

}

仍然没有必要编写代码来处理MyDisposableObjb/c 它将由using...处理

怎么会这样?

于 2011-06-21T21:56:41.233 回答
2

是的,您仍然需要捕获异常。using 块的好处是您正在为代码添加范围。你是说,“在这段代码中做一些事情,当它结束时,关闭并处理资源”

这根本不是完全必要的,但它确实定义了您对使用您的代码的任何其他人的意图,并且它还有助于避免错误地打开连接等。

于 2008-10-30T01:03:11.350 回答
2

这里有很多很好的答案,但我认为这还没有说出来。

无论如何......“处理”方法将在“使用”块中的对象上调用。如果你放一个 return 语句,或者抛出一个错误,“Dispose”将被调用。

例子:

我创建了一个名为“MyDisposable”的类,它实现了 IDisposable 并简单地执行了 Console.Write。即使在所有这些情况下,它也总是写入控制台:

using (MyDisposable blah = new MyDisposable())
{
    int.Parse("!"); // <- calls "Dispose" after the error.

    return; // <-- calls Dispose before returning.
}
于 2008-10-30T02:39:40.330 回答
1

using 语句实际上被编译器更改为 try/finally 块,其中只要实现 IDisposable 接口, using 块的参数就会被释放。除了确保指定的对象在超出范围时被正确处理之外,使用此构造实际上不会获得错误捕获。

正如上面TheSoftwareJedi所提到的,您需要确保 SqlConnection 和 SqlCommand 对象都被正确处理。将两者堆叠到一个 using 块中有点混乱,并且可能不会像您认为的那样做。

另外,请注意使用 try/catch 块作为逻辑。这是我的鼻子特别讨厌的代码气味,并且经常被新手或我们这些急于赶上最后期限的人使用。

于 2008-10-30T01:25:22.693 回答
1

仅供参考,在这个特定示例中,因为您使用的是 ADO.net 连接和 Command 对象,请注意 using 语句只执行 Command.Dispose 和 Connection.Dispose() ,它们实际上并没有关闭连接,但是只需将其释放回 ADO.net 连接池以供下一个 connection.open 重用......这很好,而且绝对正确的做法,如果你不这样做,连接将一直不可用,直到垃圾收集器将其释放回池中,这可能要等到许多其他连接请求之后,否则即使有一个未使用的连接等待被垃圾收集,它们也会被迫创建新连接。

于 2008-10-30T02:12:03.613 回答
1

我会根据我正在处理的资源来决定何时以及何时不使用 using 语句。在资源有限的情况下,例如 ODBC 连接,我更喜欢使用 T/C/F,这样我就可以在它们发生时记录有意义的错误。让数据库驱动程序错误冒泡回到客户端并可能在更高级别的异常包装中丢失是次优的。

T/C/F 让您放心,资源正在按照您希望的方式处理。正如一些人已经提到的, using 语句不提供异常处理,它只是确保资源被破坏。异常处理是一种未被充分利用和被低估的语言结构,通常是解决方案成功与失败之间的区别。

于 2011-06-28T21:07:12.153 回答
0

如果你的函数的调用者负责处理任何异常,那么 using 语句是一种确保资源被清理的好方法,无论结果如何。

它允许您在层/程序集边界放置异常处理代码,并有助于防止其他功能变得过于混乱。

当然,这实际上取决于您的代码抛出的异常类型。有时你应该使用 try-catch-finally 而不是 using 语句。我的习惯是始终从 IDisposables 的 using 语句开始(或者让包含 IDisposables 的类也实现该接口)并根据需要添加 try-catch-finally。

于 2008-10-30T03:45:55.703 回答
0

因此,基本上,“使用”与“尝试/捕获/最终”完全相同,只是在错误处理方面更加灵活。

于 2010-03-25T16:57:29.520 回答
0

对示例的小修正:SqlDataAdapter也需要在using语句中实例化:

using (SqlConnection con = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, con))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    con.Open();

    DataSet dset = new DataSet();
    using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
    {
        adapter.Fill(dset);
    }
    this.gridDataSource.DataSource = dset.Tables[0];
}
于 2010-08-12T19:21:42.773 回答
0

首先,您的代码示例应该是:

using (SqlConnection conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
using (SqlCommand cmd = new SqlCommand(reportDataSource, conn))
{
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    cmd.Connection.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}

使用您问题中的代码,创建命令的异常将导致刚刚创建的连接未被释放。通过以上,正确地布置了连接。

如果您需要在构建连接和命令(以及使用它们时)处理异常,是的,您必须将整个事物包装在 try/catch 中:

try
{
    using (SqlConnection conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString))
    using (SqlCommand cmd = new SqlCommand(reportDataSource, conn))
    {
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
        cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
        cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
        cmd.Connection.Open();

        DataSet dset = new DataSet();
        new SqlDataAdapter(cmd).Fill(dset);
        this.gridDataSource.DataSource = dset.Tables[0];
    }
}
catch (RelevantException ex)
{
    // ...handling...
}

但是您不需要处理清理conncmd; 它已经为你完成了。

与没有 的相同事物对比using

SqlConnection conn = null;
SqlCommand cmd = null;
try
{
    conn = new SqlConnection(Settings.Default.qlsdat_extensionsConnectionString);
    cmd = new SqlCommand(reportDataSource, conn);
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.Add("@Year", SqlDbType.Char, 4).Value = year;
    cmd.Parameters.Add("@startDate", SqlDbType.DateTime).Value = start;
    cmd.Parameters.Add("@endDate", SqlDbType.DateTime).Value = end;
    cmd.Connection.Open();

    DataSet dset = new DataSet();
    new SqlDataAdapter(cmd).Fill(dset);
    this.gridDataSource.DataSource = dset.Tables[0];
}
catch (RelevantException ex)
{
    // ...handling...
}
finally
{
    if (cmd != null)
    {
        try
        {
            cmd.Dispose();
        }
        catch { }
        cmd = null;
    }
    if (conn != null)
    {
        try
        {
            conn.Dispose();
        }
        catch { }
        conn = null;
    }
}
// And note that `cmd` and `conn` are still in scope here, even though they're useless

我知道我更愿意写哪个。:-)

于 2015-06-27T13:08:41.410 回答