C# using statement catch error

不羁的心 提交于 2019-12-31 08:12:08

问题


I am just looking at the using statement, I have always known what it does but until now not tried using it, I have come up with the below code:

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

This seems to work but is there any point in this since as far as I can tell I would still need to enclose this in a try catch block to catch unforseen errors e.g. sql server down. Am I missing something?

As far as I can currently see it just stops me closing and disposing of cmd but there will be more lines of code due to the try catch still being needed.


回答1:


This code should be as follows to ensure timely closing of the connection. Closing just the command doesn't close the connection:

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

To answer your question, you can do the same in a finally block, but this scopes the code nicely and ensures that you remember to clean up.




回答2:


When doing IO work I code to expect an exception.

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

Edit: To be explicit, I avoid the using block here because I believe it to be important to log in situations like this. Experience has taught me that you never know what kind of weird exception might pop up. Logging in this situation might help you detect a deadlock, or find where a schema change is impacting a little used and little tested part of you code base, or any number of other problems.

Edit 2: One can argue that a using block could wrap a try/catch in this situation, and this is completely valid and functionally equivalent. This really boils down to preference. Do you want to avoid the extra nesting at the cost of handling your own disposal? Or do you incur the extra nesting to have auto-disposal. I feel that the former is cleaner so I do it that way. However, I don't rewrite the latter if I find it in the code base in which I am working.

Edit 3: I really, really wish MS had created a more explicit version of using() that made it more intuitive what was really happening and given more flexibility in this case. Consider the following, imaginary code:

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

A using statement just creates a try/finally with Dispose() calls in the finally. Why not give the developer a unified way of doing disposal and exception handling?




回答3:


There may be no advantage to using a using statement in this case if you're going to have a try/catch/finally block anyway. As you know, the using statement is syntactic sugar for a try/finally that disposes of the IDisposable object. If you're going to have your own try/finally anyway, you can certainly do the Dispose yourself.

This really mainly boils down to style - your team may be more comfortable with using statements or using statements may make the code look cleaner.

But, if the boilerplate the using statement would be hiding is there anyway, go ahead and handle things yourself if that's your preference.




回答4:


If your code looks like this:

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

Then I would refactor it to use try.. catch.. finally instead.

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

In this scenario, I would be handling the exception so I have no choice but to add in that try..catch, I might as well put in the finally clause and save myself another nesting level. Note that I must be doing something in the catch block and not just ignoring the exception.




回答5:


Elaborating on what Chris Ballance said, the C# specification (ECMA-334 version 4) section 15.13 states "A using statement is translated into three parts: acquisition, usage, and disposal. Usage of the resource is implicitly enclosed in a try statement that includes a finally clause. This finally clause disposes of the resource. If a null resource is acquired, then no call to Dispose is made, and no exception is thrown."

The description is close to 2 pages - worth a read.

In my experience, SqlConnection/SqlCommand can generate errors in so many ways that you almost need to handle the exceptions thrown more than handle the expected behaviour. I'm not sure I'd want the using clause here, as I'd want to be able to handle the null resource case myself.




回答6:


using isn't about catching exceptions. It's about properly disposing of resources that are outside the view of the garbage collector.




回答7:


one issue with "using" is that it doesn't handles exceptions. if the designers of "using" would add "catch" optionally to its syntax like below pseudocode, it would be much more useful:

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

}

still there'll be no need to write code to dispose of MyDisposableObj b/c it'd be handled by using...

How do like that?




回答8:


Yes you would still need to catch exceptions. The benefit of the using block is you are adding scope to your code. You are saying, "Within this block of code do some stuff and when it gets to the end, close and dispose of resources"

It's not completely necessary at all, but it does define your intentions to anyone else using your code, and it also helps not leaving connections etc open by mistake.




回答9:


There are a lot of great answers here, but I don't think this has been said yet.

No matter what... the "Dispose" method WILL be called on the object in the "using" block. If you put a return statement, or throw an error, the "Dispose" will be called.

Example:

I made a class called "MyDisposable", and it implements IDisposable and simply does a Console.Write. It always writes to the console even in all these scenarios:

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

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



回答10:


The using statement is actually changed into a try/finally block by the compiler in which the parameter of the using block is disposed of so long as it implements the IDisposable interface. Aside from ensuring the specified objects are properly disposed when they fall out of scope, there is really no error capturing gained by using this construct.

As is mentioned by TheSoftwareJedi above, you will want to make sure both the SqlConnection and SqlCommand objects are disposed of properly. Stacking both into a single using block is a bit messy, and might not do what you think it does.

Also, be mindful of using the try/catch block as logic. It's a code smell that my nose has a particular dislike for, and often used by newbies or those of us in a big hurry to meet a deadline.




回答11:


FYI, in this specific example, because you're using an ADO.net connection and Command object, be aware that the using statement just executes the Command.Dispose, and the Connection.Dispose() which do not actually close the connection, but simply releases it back into the ADO.net Connection pool to be reused by the next connection.open ... which is good, and the absolutely correct thing to do, bc if you don't, the connection will remain unuseable until the garbage collector releases it back to the pool, which might not be until numerous other connection requests, which would otherwise be forced to create new connections even though there's an unused one waiting to be garbage collected.




回答12:


I would make my decision on when to and when not to use the using statement dependant on the resource I am dealing with. In the case of a limited resource, such as an ODBC connection I would prefer to use T/C/F so I can log meaningful errors at the point they occurred. Letting database driver errors bubble back to the client and potentially be lost in the higher level exception wrapping is sub optimal.

T/C/F gives you peace of mind that the resource is being handled the way you want it to. As some have already mentioned, the using statement does not provide exception handling it just ensures the resource is destructed. Exception handling is an underuitilised and underestimated language structure that is often the difference between the success and failure of a solution.




回答13:


If the caller of your function is responsible for dealing with any exceptions the using statement is a nice way of ensuring resources are cleaned up no matter the outcome.

It allows you to place exception handling code at layer/assembly boundaries and helps prevent other functions becoming too cluttered.

Of course, it really depends on the types of exceptions thrown by your code. Sometimes you should use try-catch-finally rather than a using statement. My habit is to always start with a using statement for IDisposables (or have classes that contain IDisposables also implement the interface) and add try-catch-finally as needed.




回答14:


So, basically, "using" is the exact same as "Try/catch/finally" only much more flexible for error handling.




回答15:


Minor correction to the example: SqlDataAdapter also needs to be instantiated in a using statement:

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



回答16:


First, your code example should be:

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

With the code in your question, an exception creating the command will result in the just-created connection not being disposed. With the above, the connection is properly disposed.

If you need to handle exceptions in construction of the connection and command (as well as when using them), yes, you have to wrap the entire thing in a 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...
}

But you don't need to handle cleaning up conn or cmd; it's already been done for you.

Contrast with the same thing without 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

I know which I'd rather write. :-)



来源:https://stackoverflow.com/questions/248961/c-sharp-using-statement-catch-error

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!