2

I already read the corresponding Doc-Page, but my question is still not answered. Assume I want to use a disposable Object in a while loop, like this:

StreamReader reader; while (!ShouldStop) { using (reader = new StreamReader(networkStream)) { // Some code here... } } 

How you can see, I declare StreamReade reader outside the using-statement. I usually do this because I think that then an area of the memory is being allocated for that StreamReader only one time. And when I use the using-statement like this:

while (!ShouldStop) { using (StreamReader reader = new StreamReader(networkStream)) { // Some code here... } } 

I think that there's a continuous allocation of the memory for the StreamReader-object and so it is much more less efficient & perfomant. However I dont know if the first usage of the using-statement calls the instance's Dispose()-function regularly. So does the first usage of the using-statement the same as the 2nd usage?

3
  • 1
    Did you test if it there is a difference in efficiency and performance ? In both cases you will have multiple memory allocations, so there should be no measurable difference between the two implementations. Commented May 1, 2015 at 12:10
  • 1
    Memory gets allocated with "new" operator, and not where the variable is defined. So it both ways, you have multiple memory allocation. Commented May 1, 2015 at 12:10
  • k thanks. I thought when I declare an object, an area of the memory is being reserved. And when I declare + initialize an object, there's always another area of memory being taken by the instance (but the same size/ amount of bytes of course) Commented May 1, 2015 at 12:19

5 Answers 5

3

I usually do this because I think that then an area of the memory is being allocated for that StreamReader only one time.

That's what you're getting wrong.

There is a certain amount of stack space taken for the local variable for the duration of its use, and a certain amount of heap space taken by the object upon new. This isn't going to change.

Indeed, the compiler ends up taking slightly more stack space with your approach. Just compare the IL of two methods using each approach. We'll use this C#:

private static string LastLine1(NetworkStream networkStream) { string last = null; StreamReader reader; while(!ShouldStop) { using(reader = new StreamReader(networkStream)) { string line = reader.ReadLine(); if(line != null) last = line; } } return last; } private static string LastLine2(NetworkStream networkStream) { string last = null; while(!ShouldStop) { using(StreamReader reader = new StreamReader(networkStream)) { string line = reader.ReadLine(); if(line != null) last = line; } } return last; } 

And we get this CIL:

.method private hidebysig static string LastLine1 ( class [System]System.Net.Sockets.NetworkStream networkStream ) cil managed { .maxstack 2 .locals init ( [0] string, [1] class [mscorlib]System.IO.StreamReader, [2] string, [3] class [mscorlib]System.IO.StreamReader ) IL_0000: ldnull IL_0001: stloc.0 IL_0002: br.s IL_0025 IL_0004: ldarg.0 IL_0005: newobj instance void [mscorlib]System.IO.StreamReader::.ctor(class [mscorlib]System.IO.Stream) IL_000a: dup IL_000b: stloc.1 IL_000c: stloc.3 .try { IL_000d: ldloc.1 IL_000e: callvirt instance string [mscorlib]System.IO.TextReader::ReadLine() IL_0013: stloc.2 IL_0014: ldloc.2 IL_0015: brfalse.s IL_0019 IL_0017: ldloc.2 IL_0018: stloc.0 IL_0019: leave.s IL_0025 } finally { IL_001b: ldloc.3 IL_001c: brfalse.s IL_0024 IL_001e: ldloc.3 IL_001f: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0024: endfinally } IL_0025: call bool Demonstrate.Program::get_ShouldStop() IL_002a: brfalse.s IL_0004 IL_002c: ldloc.0 IL_002d: ret } .method private hidebysig static string LastLine2 ( class [System]System.Net.Sockets.NetworkStream networkStream ) cil managed { .maxstack 1 .locals init ( [0] string, [1] class [mscorlib]System.IO.StreamReader, [2] string ) IL_0000: ldnull IL_0001: stloc.0 IL_0002: br.s IL_0023 IL_0004: ldarg.0 IL_0005: newobj instance void [mscorlib]System.IO.StreamReader::.ctor(class [mscorlib]System.IO.Stream) IL_000a: stloc.1 .try { IL_000b: ldloc.1 IL_000c: callvirt instance string [mscorlib]System.IO.TextReader::ReadLine() IL_0011: stloc.2 IL_0012: ldloc.2 IL_0013: brfalse.s IL_0017 IL_0015: ldloc.2 IL_0016: stloc.0 IL_0017: leave.s IL_0023 } finally { IL_0019: ldloc.1 IL_001a: brfalse.s IL_0022 IL_001c: ldloc.1 IL_001d: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0022: endfinally } IL_0023: call bool Demonstrate.Program::get_ShouldStop() IL_0028: brfalse.s IL_0004 IL_002a: ldloc.0 IL_002b: ret } 

(Strictly, the two should really have resulted in identical code, but the fact is they didn't, and your approach was the slightly longer and slightly greater use of stack space).

Because the C# compiler wasn't able to optimise away your having reader outside of the using, it's actually your approach that results in extra stack space being taken up for another copy of the reader.

If you aren't familiar with CIL, compare how ILSpy tries to decompile these back to C# again:

private static string LastLine1(NetworkStream networkStream) { string result = null; while (!Program.ShouldStop) { StreamReader streamReader2; StreamReader streamReader = streamReader2 = new StreamReader(networkStream); try { string text = streamReader.ReadLine(); if (text != null) { result = text; } } finally { if (streamReader2 != null) { ((IDisposable)streamReader2).Dispose(); } } } return result; } private static string LastLine2(NetworkStream networkStream) { string result = null; while (!Program.ShouldStop) { using (StreamReader streamReader = new StreamReader(networkStream)) { string text = streamReader.ReadLine(); if (text != null) { result = text; } } } return result; } 

(It's also possible that you've reduced the chances of the null-check being optimised away when this is then turned into machine code that is actually run).

it is much more less efficient & perfomant. However I dont know if the first usage of the using-statement calls the instance's Dispose()-function regularly.

The using is calling Dispose() fine either way, however you are being slightly more wasteful and hence slightly less efficient and performant. Probably negligible, but the approach you avoid is certainly not "much more less efficient & performant" as you claim.


In general, keep your scopes tight. The main reason is that a variable that's no longer in scope is a variable you can no longer do something wrong with or even have to think about, so you'll have cleaner code with fewer bugs and where the bugs are more easily found. A secondary reason is that there are a few cases, like this, where a wider scope results in very slightly more wasteful code.

Now, putting assignments outside of a loop can indeed be more performant. If your code could work with:

using(var reader = new StreamReader(networkStream)) while(!ShouldStop) { // do stuff } 

Then that would save heap churn and most of all do less and therefore if it would work, it would be an improvement.

Declarations however don't do anything, so it doesn't help to have them outside of loops, and sometimes slightly hinders.

Sign up to request clarification or add additional context in comments.

Comments

3

You're not saving any memory by doing:

StreamReader reader; while (!ShouldStop) { using (reader = new StreamReader(networkStream)) { // Some code here... } } 

You've declared the variable outside of the loop, but you're still creating a new object each iteration.

2 Comments

"Declaration space" and scope has nothing to do with garbage collection. Objects that are "in scope" in the source code can be collected if the "in scope" reference is no longer used. If this wasn't true we wouldn't need GC.KeepAlive() specifically to prevent something being collected while still in scope. See the first few paragraphs of this answer on when GC can happen.
In a way part of the point of automatic collection is that we can forget that… up until it becomes important to remember!
1

it makes no difference between these two, because whenever you call new, a chunk of memory consider for allocation. the using statement is syntactic sugar for this ( I mean the using statement translates to this):

 StreamReader reader = new StreamReader(networkStream) try { //some codes here } finally { if (reader!=null) ( (IDisposable) reader).Dispose(); } 

so in both case memory deallocate in finally, and again allocate, if it's needed.

Comments

0

Both scenarios will have same effect in terms of instance disposition. On each iteration, inside loop, every time an object will be created and disposed.

To have better understanding Kindly have a look at the following code

class Program { static void Main(string[] args) { Disposableclass obj; for (int i = 0; i < 3; i++) { using (obj = new Disposableclass()) { } } /*for (int i = 0; i < 3; i++) { using (Disposableclass obj2 = new Disposableclass()) { } }*/ Console.ReadKey(); } } public class Disposableclass : IDisposable { public Disposableclass() { Console.WriteLine("Constructor called: " + this.GetType().ToString()); } public void Dispose() { Console.WriteLine("Dispose called: " + this.GetType().ToString()); } } 

In both scenarios we will have same output which is as follows

Constructor called: TestSolution.Disposableclass

Dispose called: TestSolution.Disposableclass

Constructor called: TestSolution.Disposableclass

Dispose called: TestSolution.Disposableclass

Constructor called: TestSolution.Disposableclass

Dispose called: TestSolution.Disposableclass

Comments

0

the preceding answers about memory allocation are all correct.

What you do when you declare the 'reader' variable outside the loop: You reserve memory, but only for the reference, essentially a pointer, not for the object itself; the object itself gets allocated on the heap with the new() statement, and gets disposed when Dispose() is called at the end of the using block. The next new() allocates a new object.

The danger is: If you were to to use the reader object reference after the 'using' block, you would get an ObjectDisposedException.

In your context, it doesn't make sense to dispose and recreate the reader object all the time. You should probably put the using statement on the outside of the while loop.

But that leads to the wider question what you are trying to achieve at all. Why are you checking ShouldStop? Do you want your reading loop to be able to be cancelled? By another thread? Make sure that the inter-thread communication is implemented properly, and you are not in a blocked state waiting for data to be read when someone wants to cancel you. On the other hand, you don't want to run through the loop too often, or you will create unnecessary load.

1 Comment

Declarations don't reserve any memory, they reserve a name. The corresponding use of memory may not be one-to-one; upon compilation it can be optimised out entirely, it can become more than one slot in the method's locals at the IL level, or slots can be re-used, and then upon jitting the same applies again.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.