What Scope Does a Using Statement Have Without Curly Braces
I Inherited the Following Code: Using (Var dataAccessConnection = Da. Getconnection()) //No Opening Curly Brace Here Using (Var Command = New Sqlcommand(Sql...
I inherited the following code:
using (var dataAccessConnection = da.GetConnection()) //no opening curly brace here
using (var command = new SqlCommand(sql, dataAccessConnection.Connection))
{
command.CommandType = CommandType.Text;
using (var sqlDataReader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
while (sqlDataReader.Read())
{
rueckgabe.Add(new Myclass
{
Uid = Guid.NewGuid(),
ImportVersionUid = versionUid,
MyProperty = Convert.ToInt32(sqlDataReader["MyProperty"])
});
}
}
command.Connection.Close();
dataAccessConnection.Connection.Close();
}
Looking at the code I expexted an opening curly brace after the using clause.
The code compiles and does what it is expected to do. The application behaves unpredictable. At some time it cant access the Database server.
Does this code make sense? Does dataAccessConnection have the rigth scope?
4 Answers
Beginning with C# 8.0, the using keyword can be used as an attribute in the variable declarations of disposable objects (Reference). The semantics is as you would expect -- the objects are auto-disposed at the end of the scope.
public class Disposable : IDisposable
{
string name;
public Disposable(string name)
{
this.name = name;
}
public void Dispose()
{
Console.WriteLine(name + " disposed");
}
public void Identify()
{
Console.WriteLine(name);
}
static void Main(string[] args)
{
using Disposable d1 = new Disposable("Using 1");
Disposable d2 = new Disposable("No Using 2");
using Disposable d3 = new Disposable("Using 3");
Disposable d4 = new Disposable("No Using 4");
d1.Identify();
d2.Identify();
d3.Identify();
d4.Identify();
}
}
Output
Using 1 No Using 2 Using 3 No Using 4 Using 3 disposed Using 1 disposed
using statements without explicit curly braces apply only to the following statement.
using (Idisp1)
// use it
// it's disposed
Thus, when chained, they work the same way. The second using here acts as a single statement.
using (Idisp1)
using (Idisp2)
{
}
Commenter stakx suggested that formatting to make it clear how the compiler reads the using blocks. In reality, these would usually be formatted as the OP encountered:
using (Idisp1)
using (Idisp2)
{
}
That is equivalent to this:
using (Idisp1)
{
using (Idisp2)
{
}
}
Notice that the first at the top is always the last to dispose. Thus, in all previous examples, Idisp2.Dispose() is called before Idisp1.Dispose(). That isn't relevant in many cases where you would do something like this, but I believe you should always be aware of what your code will do and make the informed decision not to care.
An example of this is when reading a web page:
HttpWebRequest req = ...;
using (var resp = req.GetResponse())
using (var stream = resp.GetResponseStream())
using (var reader = new StreamReader(stream))
{
TextBox1.Text = reader.ReadToEnd(); // or whatever
}
We get the response, get the stream, get the reader, read the stream, dispose the reader, dispose the stream, and finally, dispose the response.
Note, as commenter Nikhil Agrawal pointed out, that this is a language feature regarding blocks that is not specific to the using keyword. For example, the same applies to if blocks:
if (condition)
// may or may not execute
// definitely will execute
Versus
if (condition1)
if (condition2)
// will execute if both are true
// definitely will execute
Although you should never, of course, use if statements this way as it's dreadful to read, but I thought it'd help you understand the using case. I'm personally very okay with chaining using blocks.
The C# language specification (Version 5) describes a using statement as:
using-statement:
using (resource-acquisition)embedded-statement
That is:
The
usingstatement obtains one or more resources, executes a statement, and then disposes of the resource.
(My emphasis)
So, how then do we end up using it with curly braces? Because the definition of embedded-statement is:
embedded-statement:
block
empty-statement
expression-statement
selection-statement
iteration-statement
jump-statement
try-statement
checked-statement
unchecked-statement
lock-statement
using-statement
yield-statement
And:
The embedded-statement nonterminal is used for statements that appear within other statements
And finally, we discover that block is defined as:
A block permits multiple statements to be written in contexts where a single statement is allowed.
block:
{statement-listopt}
So basically, curly braces can always be used to take a situation where a single statement is accepted and to instead have multiple statements.
It just so happens that, almost always, we do want to make use of more than one statement, and so the curly-braces tend to be seen as part of if, using, etc statements. Whereas, in fact, they're a separate part of the language.
The existing responses are incorrect or incomplete respectively.
There are two cases actually:
using (var resp = req.GetResponse())
which has a scope for the next statemenet.
but there is also
using var resp = req.GetResponse()
(Note the lack of parantheses after using compared to 1)
Which has block scope, e.g. the current block or method.
So if you are using using var resp = req.GetResponse() inside of the root block of a method, then the using variable will have method scope, actually similar like the defer statement in go.
Note that defer in GO has FUNCTION scope, while using (without parantheses) in C# has BLOCK scope.
Example
public static ILoggerFactory TestLogging()
{
// dotnet add package Microsoft.Extensions.Logging
// dotnet add package OpenTelemetry.Exporter.Console
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
{
options.AddConsoleExporter();
});
});
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
System.GC.Collect();
var fac = loggerFactory.CreateLogger<Program>();
System.Console.WriteLine(fac);
logger.LogInformation(eventId: 123, "Hello from {name} {price}.", "tomato", 2.99);
if (logger.IsEnabled(LogLevel.Debug))
{
// If logger.IsEnabled returned false, the code doesn't have to spend time evaluating the arguments.
// This can be especially helpful if the arguments are expensive to calculate.
logger.LogDebug(eventId: 501, "System.Environment.Version: {version}.", System.Environment.Version);
}
return loggerFactory;
}
You see inside the function, you can create a second logger after calling GC.Collect() using loggerFactory.
But when you return loggerFactory from the function, call GC.Collect and want to create another logger, it will throw System.ObjectDisposedException.
var x = TestLogging();
System.GC.Collect();
var log = x.CreateLogger<Program>(); // throws ObjectDisposedException