2008-10-30 82 views
28

我只是看着using语句,我一直都知道它做什么,但到现在为止使用它没有尝试过,我想出了下面的代码:C#using语句catch错误

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服务器关闭。我错过了什么吗?

据我目前可以看到,它只是停止我关闭和处置cmd,但由于try catch仍然需要,会有更多的代码行。

回答

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块,但是这很好作用域的代码,并确保你还记得清理。

4

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

+2

我明白你的意思了,但我看不到优于try catch的优势,最后用close和dispose语句阻塞。 – PeteT 2008-10-30 01:05:45

+2

资源可能不在垃圾收集器视图之外。尽快清理它们仍然有帮助,而不是等待GC。 – 2010-03-24 22:38:37

+1

它也不是垃圾收集器。 – 2010-08-12 19:20:47

2

是的,你仍然需要捕捉异常。使用块的好处是您正在向代码添加范围。你在说:“在这块代码中做一些东西,当它结束时,关闭并处理资源”

这不是完全必要的,但它确实将你的意图定义为使用你的代码的其他人,它也有助于不会让连接等错误地打开。

57

在做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:有人可能会争辩说,在这种情况下,使用块可以包装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。为什么不给开发人员进行处理和异常处理的统一方式?

1

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

正如上面提到的TheSoftwareJedi,您将需要确保SqlConnection和SqlCommand对象都正确处置。将两者叠加到一个使用区块中有点麻烦,并且可能无法达到您认为的效果。

此外,请注意使用try/catch块作为逻辑。这是一种我鼻子特别不喜欢的代码味道,经常被新手或我们这些人匆忙使用以满足最后期限。

1

仅供参考,在此特定示例中,因为您使用的是ADO.net连接和Command对象,请注意using语句只是执行Command.Dispose,而Connection.Dispose()实际上并不关闭连接,但只是简单地将它释放回ADO.net连接池中,以便在下一次连接时重新使用它。这是很好的,也是绝对正确的事情,如果你不这样做,那么连接将会保留直到垃圾收集器将其释放回池中才可用,直到许多其他的连接请求,否则,即使有一个未使用的连接请求被垃圾收集,否则这些连接请求将被迫创建新的连接。

5

C#规范(ECMA-334版本4)第15.13节阐述了Chris Ballance所说的内容:“使用语句被翻译为三部分:获取,使用和处置。资源的使用隐含在try语句包含finally子句,finally子句处理资源,如果获得null资源,则不会调用Dispose,也不会抛出异常。

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

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

2

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

无论如何......“Dispose”方法将在“using”块中的对象上调用。如果您放置返回语句或抛出错误,则将调用“Dispose”。

例子:

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

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

    return; // <-- calls Dispose before returning. 
} 
6

如果你的代码看起来是这样的:

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块中做一些事情,而不是忽略异常。

0

如果函数的调用者负责处理任何异常,则使用语句是确保资源清理的好方法,无论结果如何。

它允许您将异常处理代码放置在图层/装配体边界,并有助于防止其他函数变得非常混乱。

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

14

无论如何,如果您要拥有try/finally块,在这种情况下使用using语句可能没有优势。如您所知,using声明是处理IDisposable对象的try/finally的语法糖。如果你打算拥有自己的try/finally,你当然可以自己做Dispose

这实际上主要归结为风格 - 您的团队可能会更加适应using声明或using声明可能会使代码看起来更干净。

但是,如果样本using声明会隐藏,无论如何,请继续处理,如果这是您的偏好。

0

因此,基本上,“使用”与“Try/catch/finally”完全相同,只是对错误处理更加灵活。

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]; 
} 
4

一个问题与“使用”是,它不处理异常。 如果“用”会增加“抓”任选其语法像下面的伪代码的设计师,这将是有用得多:

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 ... 

} 

仍然将没有必要写代码来处理MyDisposableObj B的/ c它会被处理using ...

这是怎么回事?

1

我会决定何时和何时不使用依赖于我正在处理的资源的using语句。在资源有限的情况下,比如ODBC连接,我宁愿使用T/C/F,这样我就可以在发生的时候记录有意义的错误。让数据库驱动程序错误回滚到客户端并可能在更高级别的异常换行中丢失是最佳选择。

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

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 

我知道,我宁愿写。 :-)