10

I was looking at the memory impact of a simple LINQ query and noticed that the LINQ query created 2 extra objects of types Enumerable+WhereListIterator<Int32> and Func<Int32, Boolean>.

The code used is this:

static void Main(string[] args)
{
    // Setting baseline snapshot
    var list1 = new List<int> { 4862, 6541, 7841 };
    var list2 = new List<int>(list1.Count);
    var list3 = new List<int>(list1.Count);

    // First snapshot: LINQ usage
    list2.AddRange(list1.Where(item => item > 5000 && item < 7000));

    // Second snapshot: foreach-loop
    foreach (var item in list1)
    {
        if (item > 5000 && item < 7000)
        {
            list3.Add(item);
        }
    }

    // End gather
    Console.Read();
}

At the snapshot after the foreach loop I notice that the Enumerable+WhereListIterator<Int32> object is garbage collected but the Func<Int32, Boolean> is still in memory.

Why is this still kept around? At that point in time (at the Console.Read statement) I don't think anything is still referencing it and a GC has been forced by the profiler (which is why the iterator is collected).

Note: collecting additional snapshots does not alter how many objects are freed so it's not a matter of the Func being marked for collection for the next run.

Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170

1 Answers1

13

The reason the lambda is not GC-ed is the structure of the lambda itself:

item => item > 5000 && item < 7000

This lambda does not capture anything, meaning that it can be compiled once, and reused forever. C# discovers this, and takes advantage of lambdas like that by caching them statically to improve performance.

Had your lambda captured a variable from its context, it would have been garbage collected when it is no longer needed.

See this answer for more information on lambda lifetime in .NET.

Community
  • 1
  • 1
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523