C#使用语句捕获错误

本文关键字:错误 语句 | 更新日期: 2023-09-27 17:47:46

我只是在看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,将有更多的代码行。

C#使用语句捕获错误

在执行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();
}

编辑:为了明确起见,我在这里避免使用块,因为我认为在这种情况下登录很重要。经验告诉我,你永远不知道会出现什么样的奇怪例外。在这种情况下进行日志记录可能有助于检测死锁,或者发现模式更改对代码库中很少使用和测试的部分的影响,或者任何其他问题。

编辑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。为什么不给开发人员一个统一的处理和异常处理方法呢?

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

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块中做同样的事情,但这很好地确定了代码的范围,并确保你记得清理。

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

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

但是,如果using语句隐藏的样板无论如何都在那里,那么如果你喜欢的话,就自己处理吧。

如果您的代码看起来像这样:

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

然后我会重构它,使用try。。接住最后取而代之。

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();
}

在这种情况下,我将处理异常,所以我别无选择,只能添加该尝试。。catch,我还不如放入finally子句,为自己保存另一个嵌套级别。请注意,我必须在catch块中执行某些操作,而不仅仅是忽略异常。

详细阐述Chris Ballance所说的话,C#规范(ECMA-334版本4)第15.13节规定:"using语句被转换为三个部分:获取、使用和处置。资源的使用隐含地包含在一个包含finally子句的try语句中。这个finally子句处置资源。如果获取了空资源,则不会调用Dispose,也不会引发异常。"

该描述将近2页,值得一读。

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

使用并不是为了捕捉异常。这是关于正确处理垃圾收集器视图之外的资源。

"using"的一个问题是它不处理异常。如果"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 ...
}

仍然不需要编写代码来处理CCD_ 15 b/c,它将由CCD_ 16处理。。。

你觉得怎么样?

是的,您仍然需要捕获异常。使用块的好处是可以为代码添加作用域。你说,"在这个代码块中做一些事情,当它结束时,关闭并处理资源"

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

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

不管怎样。。。将对"using"块中的对象调用"Dispose"方法。如果放入return语句或抛出错误,则将调用"Dispose"。

示例:

我制作了一个名为"MyDisposable"的类,它实现了IDisposable,只做了一个Console.Write。即使在所有这些场景中,它也总是写入控制台:

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

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

正如上面TheSoftwareJedi所提到的,您需要确保SqlConnection和SqlCommand对象都得到了正确的处理。将两者堆叠到一个单独的使用块中有点混乱,可能不会像你认为的那样。

此外,要注意使用try/catch块作为逻辑。这是一种我特别不喜欢的代码气味,经常被新手或我们这些急于赶在最后期限前使用。

FYI,在这个特定的例子中,因为你使用的是ADO.net连接和Command对象,请注意using语句只是执行Command.Dispose和connection.Dispose(),它们实际上并没有关闭连接,只是简单地将其释放回ADO.net连接池中,供下一个连接重用。打开…这很好,绝对正确的做法是,bc如果你不这样做,连接将保持不可用,直到垃圾收集器将其释放回池,这可能要等到许多其他连接请求,否则这些连接将被迫创建新的连接,即使有一个未使用的连接等待垃圾收集。

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

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

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

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

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

因此,基本上,"使用"与"Try/catch/finally"完全相同,只是在错误处理方面更灵活。

对示例的轻微更正: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];
}

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

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

我知道我宁愿写哪一个