New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Support catch and finally for using #2018
Comments
Related: #1874 |
The using statement is basically a try - finally, where the finally contains disposal of the object in the using statement. What moment in execution would the catch block represent? Would it be executed before or after object disposal? This is a obviously an important question, but otherwise, I'm very much for this idea. It tightens up code and makes it more concise. |
try {
// ...
using (var foo = new DisposableFoo()) {
// ...
}
catch {
// ...
}
}
finally {
// ...
}
Update: I need to count my braces better, it's not legal code. |
To follow the current implementation, I think the best approach would be to expand the following: using (var foo = new Bar())
{
//do something
}
catch
{
//catch something
} ... to the following: var foo = new Bar();
try
{
//do something
}
catch
{
//catch something
}
finally
{
if (foo != null)
((IDisposable)foo).Dispose();
} But there's an ambiguity problem: the using (var foo = new Bar())
try
{
//do something
}
catch (ArgumentException) { }
catch
{
//catch what???
} Because of this problem, every proposal to make |
This makes it trivial to create this behavior using existing syntax: using (var foo = new Bar()) try
{
// do something
}
catch (Exception exception)
{
// oops, something went wrong
} The formatter in VS might fight you but I think that it would be easier to fix the formatter in this case rather than modify the language. |
@HaloFour your proposal would not catch an exception in the I'm all in for making braces mandatory for a using statement that you want a catch for. We would not break anything existing either because you can't use catch for using at all right now. |
Mine neither. A change would be required: Bar foo = default;
try
{
foo = new Bar();
//do something
}
catch
{
//catch something
}
finally
{
if (foo != null)
((IDisposable)foo).Dispose();
} But I disagree. I believe that if |
Intuitively, I would not expect an exception in the expression part of the statement to be caught, violating the principle of least surprise.
This would be really confusing - it would be akin to having braces optional for if unless you wanted to have an else as well, in which case the braces are mandatory . |
I'd say it's closer to having braces optional for a while loop, but making braces and semicolon mandatory for do-while. |
|
I know :P It was an analogy. |
Both analogies come down to making perfectly legal code today illegal, which is the biggest No-No when it comes to evolving C#. |
We were discussing syntax for the proposed feature, which would not invalidate currently correct code. Let's just say we got dragged off topic and leave it at that. So, since using expands into a try/finally block, what would happen when someone wants to append a finally block to the using statement? The usual disposal gets inserted at the end of the existing finally block, or a new try/finally surrounds the user's changes? using(var disposable = ...)
{
//using disposable
}
catch(Exception ex)
{
//omitted
}
finally
{
//omitted
}
//Expands into
IDisposable disposable = ...
try
{
//using disposable
}
catch
{
//omitted
}
finally
{
//omitted
disposable.Dispose();
}
//or
IDisposable disposable = ...
try
{
try
{
//using disposable
}
catch
{
//omitted
}
finally
{
//omitted
}
}
finally
{
disposable.Dispose();
} |
Just like with lock how it expands to try enter now we should ask the user to be as explicit as possible hence why we allow and perform special null checking on type right? This is already valid syntax when written in another properly encapsulated block. I'm against this as it promotes bad design. Make the class not require the external try catch or document it to need one. This notion of additional preservation for using beyond the scope of what's already there seems like bandaid for lack of good design paradigm for users to adopt and utilize. E.g. a BaseDisposable implementation |
using (var foo = new Bar())
try
{
//do something
}
catch (ArgumentException) { }
catch
{
//catch what???
} to the following var foo = new Bar();
try
{
try
{
//do something
}
catch (ArgumentException) { }
}
catch
{
//catch what???
}
finally
{
if (foo != null)
((IDisposable)foo).Dispose();
} So, "using" is "try" with default finally. But "try" has optional catch and "using" hasn't. |
Just an idea, maybe we could use the DateTime startTime = DateTime.UtcNow;
using (var scope => new TransactionScope())
{
using var db = new DBContext();
var johnList = db.Persons.Where(x => x.Name == "John");
johnList.ForEach(j => { j.Surname = "Doe"; });
db.SaveChanges();
scope.Complete();
Log.Info("Johns updated successfully.");
}
catch (Exception ex)
{
Log.Error("Johns updating failed.", ex);
throw ex;
}
finally
{
DateTime endTime = DateTime.UtcNow;
Log.Info($"Johns update method took { (endTime - startTime).TotalMilliseconds }ms to complete.");
} to DateTime startTime = DateTime.UtcNow;
TransactionScope scope = default(TransactionScope);
try
{
scope = new TransactionScope();
DBContext db = new DBContext();
try
{
List<Person> johnList = db.Persons.Where(x => x.Name == "John");
johnList.ForEach(j => { j.Surname = "Doe"; });
db.SaveChanges();
scope.Complete();
Log.Info("Johns updated successfully.");
}
catch
{
throw;
}
finally
{
if (db != null)
db.Dispose();
}
}
catch (Exception ex)
{
Log.Error("Johns updating failed.", ex);
throw ex;
}
finally
{
if (scope != null)
scope.Dispose();
DateTime endTime = DateTime.UtcNow;
Log.Info($"Johns update method took { (endTime - startTime).TotalMilliseconds }ms to complete.");
} |
I personally have no immediate case for the If you are doing multiple return branches within a using statement, or throwing exceptions, it would be extremely useful if there would be some kind of finally strategy were you could handle log messages. One of the things I do regularly is throwing HTTP status code exceptions which are handled by my middleware to return the correct status code to the user. Granted this could be done in a different way, but it just makes for a nice clean piece of code. _logger.LogInformation("starting block");
using (var a = new A())
{
var result = a.DoSomething();
if (result == 404)
{
throw new HttpNotFoundException();
}
else if (result == 400)
{
throw new HttpBadRequestException(result.ErrorMessage);
}
// do something else.
}
finally
{
_logger.LogInformation("completed block");
} |
That seems to only be saving a single token. i.e. i could write that today as:
being able to trim off the I also far prefer this approach because i totally understand the finally semantics. i.e. it will happen before |
Proposal
Please allow us to append a
catch
and/orfinally
block to the end of ausing
, similar totry
.Example
Improvements
This change would be compatible with existing code and would put a stop to this pattern, which is not recommended according to the documentation of the using statement (see example right above the linked chapter):
Or even worse, this, which utilizes the using properly but stacks try blocks:
Other considerations
Another possibility would be that the
using(...){...}catch(...){...}
statement is set to catch all exceptions, including those that happen inside of the using encapsulated code. In that case, a newUsingException
type would probably be necessary that encapsulates theusing(...)
related exception to allow the developer to distinguish between locations of exceptions.The text was updated successfully, but these errors were encountered: