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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With