Почему GC после запроса LINQ освобождает объект WhereListIterator, но не Func, представляющий условие?

Я рассматривал влияние простого запроса LINQ на память и заметил, что запрос LINQ создал 2 дополнительных объекта типов Enumerable+WhereListIterator<Int32> и Func<Int32, Boolean>.

Используемый код:

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();
}

В моментальном снимке после цикла foreach я замечаю, что объект Enumerable+WhereListIterator<Int32> собран мусором, но Func<Int32, Boolean> все еще находится в памяти.

Почему это все еще сохраняется? В этот момент времени (в выражении Console.Read) я не думаю, что что-то все еще ссылается на него, и GC был принудительно запущен профилировщиком (именно поэтому итератор собран).

Примечание: сбор дополнительных снимков не изменяет количество освобожденных объектов, поэтому для следующего прогона не будет маркироваться Func.

Ответ 1

Причина, по которой лямбда не является GC-ed, - это сама структура лямбда:

item => item > 5000 && item < 7000

Эта лямбда не захватывает ничего, что означает, что ее можно скомпилировать один раз и повторно использовать навсегда. С# обнаруживает это и использует такие лямбды, что статически их кэширует для повышения производительности.

Если бы ваша лямбда захватила переменную из ее контекста, это был бы сбор мусора, когда он больше не нужен.

См. этот ответ для получения дополнительной информации о времени жизни лямбда в .NET.