Home > database >  Memory leak when using Linq with a 'Where' clause and 'ToList()'
Memory leak when using Linq with a 'Where' clause and 'ToList()'

Time:08-26

I have the following method in my code in my WPF application that uses a DataGrid:

List<IColumn> GetColumns()
{
    return _columnItems
        .Where(column => column is TextColumn<T> textColumn && 
                         textColumn.IsColumnEnabled
        )
        .ToList();
}

This code remains in memory even after closing a dialog containing the corresponding datagrid. I can see it by examining dotMemory by JetBrains, I have a Func<IColumn, bool> as a GC root, holding on to my DataGrid.

Yet, if I just perform this simple change:

List<IColumn> GetColumns()
{
    var columnsToReturn = new List<IColumn>();
    foreach (var column in _columnItems)
    {
        if (column is TextColumn<T> textColumn && textColumn.IsColumnEnabled)
            columnsToReturn.Add(textColumn);
    }

    return columnsToReturn ;
}

The memory leak is gone, the instance of DataGrid is cleaned after closing the dialog, this Func that used to be a GC root is gone.

As much as I'm happy that I've resolved the memory leak, I would like to understand what's going on?

I assume it is related to Linq projection and it's logic of caching entries, though I'm a bit surprised as the leaking code has ToList() in the end, which was already supposed to execute the query. My long syntax escapes the Linq Func (Where), and thus "cutting off" the tree branch in dotMemory.

I mean, I already return the Func execution from this method, as I've already invoked ToList() , so whatever uses this method's output uses the returned list, so why would the Func still be kept in memory by Linq or IEnumerable?

CodePudding user response:

Wow, this is caused due to compiler optimization.

You can find all the related info here: https://stackoverflow.com/a/46963119/3707896

In short, the compiler created a child class, put my function (lambda expression) as an instance method of that class, created a singleton instance of that class in a static field and finally created a static field with my Func referencing my method. So, no surprise that those static members generated by compiler cannot be collected by GC.

Credit to the original writer of the post in the attached link: @Evk

  • Related