views:

115

answers:

6

I know the Dispose() method is called on the StreamReader object when you have the following code:

//Sample 1
using (StreamReader sr1 = new StreamReader(@"C:\Data.txt"))
{
    string s1 = sr1.ReadToEnd();
    //Do something with s1...
}

But if you write the code like this (Sample 2) will the Dispose() method get called too?

//Sample 2
StreamReader sr2 = new StreamReader(@"C:\Data.txt");
using (sr2)
{
    string s2 = sr2.ReadToEnd();
    //Do something with s2...
}
+5  A: 

Yes, absolutely. The details are in section 8.13. There isn't a concise statement of your exact question, but:

A using statement of the form

using (expression) statement

has the same three possible expansions, but in this case ResourceType is implicitly the compile-time type of the expression, and the resource variable is inaccessible in, and invisible to, the embedded statement.

The "three possible expansions" referred to cover the more common case of declaring a variable at the same time. Basically the important thing is that it behaves the same way, aside from the variable's scope. Dispose will still be called - otherwise there'd be no point in putting in a using statement at all :)

Note that the two aren't quite equivalent in terms of what's valid within the block, because a variable declared by the using statement is readonly. So this is valid:

// Warning but no error
MemoryStream ms = new MemoryStream();
using (ms)
{
    ms = null;
}

but this isn't:

using (MemoryStream ms = new MemoryStream())
{
    // error CS1656: Cannot assign to 'ms' because it is a 'using variable'
    ms = null;
}

Note that even in the case where it's valid, it's the original value of ms which is used for disposal. The compiler makes this clear:

warning CS0728: Possibly incorrect assignment to local 'ms' which is the argument to a using or lock statement. The Dispose call or unlocking will happen on the original value of the local.

Note that this form doesn't generate a warning:

// No warning
MemoryStream ms;
using (ms = new MemoryStream())
{
    ms = null;
}

On the other hand, aside from that, they really will behave in the same way.

EDIT: As bzlm notes, the fact that the variable remains in scope after the using statement means it's usually not a good idea. However, objects which have been disposed aren't always unusable. For example:

MemoryStream ms = new MemoryStream();
using (ms) 
{
    // Do stuff
}
byte[] data = ms.ToArray();

That will work just fine - the MemoryStream keeps the data even when it's disposed. It still feels somewhat wrong to me though.

Jon Skeet
"You can instantiate the resource object and then pass the variable to the using statement, but this is not a best practice. In this case, the object remains in scope after control leaves the using block even though it will probably no longer have access to its unmanaged resources" seems relevant here too.
bzlm
Thanks Jon. I hadn't seen this covered in this depth of scope before.
drachenstern
+8  A: 

Yes, Dispose() would be called in both examples. They are functionally equivalent except that in the second example the disposed StreamReader would still be in scope. Therefore the first method is preferred, as using a disposed object is usually a Bad Idea.

However as others have pointed out, it is sometimes OK to use a disposed object. In such cases you might want to use your second example. But you have to know what you're doing and I would avoid it if at all possible.

Greg
the first form is preferred, as to avoid polluting the scope of the enclosing function with sr2, which after the using construct points to a disposed object.
Marco
@Macro - Excellent point. I updated accordingly.
Greg
Thanks for all the good answers.
hungster
A: 

Both codes are pretty much the same. Dispose will be called once control leaves the using block. If the exception occurs before then it won't be called in either case. But apart from asynchronous exceptions this won't happen in your code.

http://stackoverflow.com/questions/3923457/is-cs-using-statement-abort-safe/

Is a similar discussion focusing on the interaction with thread abortion.

CodeInChaos
A: 

using takes an object that implements IDisposable. How and where that object is created is not taken into account, when the compiler generates the code to call Dispose at the end for the using block.

Franci Penov
A: 

Good question. I would say yes, because the using code block is little more than syntax sugar for the following:

try
{
   var myObj = <parameter from using>
   <using block code>
}
finally
{
   myObj.Dispose();
}

Notice that, when substituted for your using block, the variable you end up disposing has another handle that is visible outside the code block (sr2). This reference will prevent the instance from being garbage-collected after the using statement, but since it's been disposed, unless it's smart enough to recover from a mid-scope Dispose(), it's not going to be of much use.

KeithS
The `myObj` variable declaration would be outside the `try` block, otherwise it's not visible in the `finally` block.
Greg
A: 

MSDN says not entirely equal regarding exceptions during initialization. Also consider the following scoping scenario:

//Sample 1
using (StreamReader sr1 = new StreamReader(@"C:\Data.txt"))
{
    string s1 = sr1.ReadToEnd();
    //Do something with s1...
}
sr1.ReadToEnd() // sr1 not in scope.

Does not compile, but

//Sample 2
StreamReader sr2 = new StreamReader(@"C:\Data.txt");
using (sr2)
{
    string s2 = sr2.ReadToEnd();
    //Do something with s2...
}

sr2.ReadToEnd() // possible to write, but accessing a disposed object.

compiles but access a disposed object.

Albin Sunnanbo