Question

Can you please explain to me what happens in the memory while executing the following code:

Case 1:

public static void Execute()
{
    foreach(var text in DownloadTexts())
    {
          Console.WriteLine(text);
    }
}


public static IEnumerable<string> DownloadTexts()
{
     foreach(var url in _urls)
     {
         using (var webClient = new WebClient())
         {
              yield return webClient.DownloadText(url);
         }
     }
}

Let's assume after the first iteration I get html1.

When will html1 be cleared from the memory ?

  1. on the next iteration?
  2. when the foreach ends?
  3. when the function ends ?

Thanks

** Edit **

Case 2:

public static void Execute()
{
    var values = DownloadTexts();
    foreach(var text in values)
    {
          Console.WriteLine(text);
    }
}


public static IEnumerable<string> DownloadTexts()
{
     foreach(var url in _urls)
     {
         using (var webClient = new WebClient())
         {
              yield return webClient.DownloadText(url);
         }
     }
}

To my understanding, Case 1 is better for the memory then case 2 right?

In case 2 will still keep a reference to the texts we already downloaded while in Case 1 every text is marked for garbage collection once its not used. Am I correct?

Was it helpful?

Solution

  • _urls will stay indefinitely because it is located in a field as it seems.
  • DownloadTexts() (the iterator returned by it) is kept alive until the end of the loop.
  • the WebClient and the html it produces stay alive for one iteration. If you want to know the absolute precise lifetime of it, you need to use Reflector and mentally simulate where the reference travels around. You'll find that the IEnumerator used in the loop references it until the next iteration has begun.

All objects that are not alive can be GC'ed. This happens whenever the GC thinks that is a good idea.

Regarding your Edit: The cases are equivalent. If you don't put the enumerator into a variable, the compiler will do that for you. It has to keep a reference till the end of the loop. It does not matter how many references there are. There is at least one.

Actually, the loop only requires the enumerator to be kept alive. The additional variable you added will also keep the enumerable alive. On the other hand you are not using the variable so the GC does not keep it alive.

You can test this easily:

//allocate 1TB of memory:
var items =
    Enumerable.Range(0, 1024 * 1024 * 1024)
    .Select(x => new string('x', 1024));
foreach (var _ in items) { } //constant memory usage

OTHER TIPS

It will be cleared from memory when the garbage collector runs and determines that it's no longer in use.

The value will no longer be in use at the moment when the foreach causes the IEnumerator.MoveNext() method to be invoked. So, effectively, #1.

It will be cleared from memory when Garbage Collector will feel like doing so.

But starting point is when code holds no more references to instance of object. So the answer in this case is: sometime after block in which you created object ends.

Have trust in GC, it is good at doing its job.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top