开发者

Why doesn't 'using' have a catch block?

开发者 https://www.devze.com 2022-12-11 11:49 出处:网络
I understand the point of \"using\" is to guarantee that the Dispose method of the object will be called.But how should an exception within a \"using\" statement be handled?If there is an exception, I

I understand the point of "using" is to guarantee that the Dispose method of the object will be called. But how should an exception within a "using" statement be handled? If there is an exception, I need to wrap my "using" statement in a try catch. For example:

Lets say there is an exception created in the creation of the object inside the using parameter

 try
 {
    // Exception in using parameter
    using (SqlConnection connection = new SqlConnection("LippertTheLeopard"))
    {
       开发者_StackOverflow社区connection.Open();
    }
 }
 catch (Exception ex)
 {

 }

Or an Exception within the using scope

 using (SqlConnection connection = new SqlConnection())
 {
    try
    {
       connection.Open();
    }
    catch (Exception ex)
    {

    }
 }

It seems like if I already need to handle an exception with a try catch, that maybe I should just handle the disposing of the object as well. In this case the "using" statement doesn't seem to help me out at all. How do I properly handle an exception with "using" statement? Is there a better approach to this that I'm missing?

 SqlConnection connection2 = null;
 try
 {
    connection2 = new SqlConnection("z");
    connection2.Open();
 }
 catch (Exception ex)
 {

 }
 finally
 {
    IDisposable disp = connection2 as IDisposable;
    if (disp != null)
    {
       disp.Dispose();
    }
 }

Could the "using" keyword syntax be a little more sugary...

It sure would be nice to have this:

 using (SqlConnection connection = new SqlConnection())
 {
    connection.Open();
 }
 catch(Exception ex)
 {
   // What went wrong? Well at least connection is Disposed
 }


Because you would be 'hiding' extra functionality inside an unrelated keyword.

However you could always write it this way

using (...) try
{
}
catch (...)
{
}

And this way the line represents your intentions -- a using statement that is also a try


using Has nothing to do with error handling. It's shorthand for "call Dispose when you leave this block." Your second code example is perfectly acceptable... why mess with what works?


The using block is just syntactic sugar for a try-finally block. If you need a catch clause, just use a try-catch-finally:

SqlConnection connection;
try 
{
    connection = new SqlConnection();
    connection.Open();
}
catch(Exception ex)
{
    // handle
}
finally 
{
    if (connection != null)
    {
        connection.Dispose();
    }
}

Yes, this is more code than your theoretical "using-catch"; I judge the language developers didn't consider this a very high priority, and I can't say I've ever felt its loss.


I've had places where this would be useful. But more often, when I want to do this it turns out that the problem is in my design; I'm trying to handle the exception in the wrong place.

Instead, I need to allow it to go up to the next level — handle it in the function that called this code, rather than right there.


An interesting idea, but it would make the following kinda confusing:

 using (SqlConnection connection = new SqlConnection())
 using (SqlCommand cmd = new SqlCommand())
 {
     connection.Open();
 }
 catch(Exception ex)
 {
     // Is connection valid? Is cmd valid?  how would you tell?
     // if the ctor of either throw do I get here?
 }


You are mixing concerns in my opinion. Resource management (i.e. disposale of objects) is completely separated from exception handling. The one-to-one mapping that you describe in your question is just a very special case. Usually exception handling will not happen in the same place as the using scope ends. Or you might have multiple try-catch regions inside a using block. Or ...


I recommend you use example #1 and #2 combined. The reason is your using statement could read a file, for instance, and throw an exception (i.e File Not Found). If you don't catch it, then you have an unhandled exception. Putting the try catch block inside the using block will only catch exceptions that occur after the using statement has executed. A combination of your example one and two is best IMHO.


The purpose of the "using" statement is to ensure that some type of cleanup operation will occur when execution exits a block of code, regardless of whether that exit is via fall-through, an exception, or return. When the block exits via any of those means, it will call Dispose on the parameter of using. The block exists, in some sense, for the benefit of whatever is being specified as the using parameter, and in general that thing won't care why the block was exited.

There are a couple of unusual cases for which provisions might be helpful; their level of additional utility would be well below that provided by having using in the first place (though arguably better than some other features the implementers see fit to provide):

(1) There is a very common pattern in constructors or factories of objects which encapsulate other IDisposable objects; if the constructor or factory exits via an exception, the encapsulated objects should be Disposed, but if it exits via return they should not. Presently, such behavior must be implemented via try/catch, or by combining try/finally with a flag, but it would IMHO be helpful if there were either a variation of using which would only call Dispose when it exited via exception, or else a keep using statement which would null out the temporary employed by the using statement to hold the object which needed disposal (since using cannot be preceded by an identifier, such a feature could be added in a manner somewhat analogous to yield return).

(2) In some cases, it would be helpful if the finally keyword extended to accept an Exception argument; it would hold the exception which caused the guarded clause to exit (if any), or null if the guarded clause exits normally (via return or fall-through), and if the using block could make use of interface IDisposeExOnly {void DisposeEx(Exception ex);} and Interface IDisposeEx : IDisposable, IDisposableExOnly {} (at compile-time, selected DisposeEx() if implemented, or Dispose() otherwise). This could allow transaction-based objects to safely support auto-commit (i.e. perform the commit if the passed-in exception is null, or roll-back if non-null) and would also allow for improved logging in situations where Dispose fails as a consequence of a problem within the guarded clause (the proper thing would be for Dispose to throw an exception which encapsulates both the exception that was pending when it was called, and the exception that occurred as a consequence, but there's presently no clean way to do that).

I don't know if Microsoft will ever add such features; the first, and the first part of the second, would be handled entirely at the language level. The latter part of the second would be at the Framework level.

0

精彩评论

暂无评论...
验证码 换一张
取 消