2

My understanding is that it is always the subscriber (consumer) of an event that is in risk of being leaked (if the producer lives longer). If I subscribe to an (non-static) event with an anonymous lambda function inside a static method, I should not have to unsubscribe if I want the lambda to live as long as the producer lives?

There is a variant of the question (Does lambda event subscription create memory leak?) with this answer, quoting:

Additionally, that lambda expression isn't using any variables from this, so it would probably be implemented by a static method with no target anyway... I assume the real situation you're concerned with has a more interesting lambda body.

I interpret this to mean that you might have to unsubscribe if the lambda expression is using variables from the target (this), but in a static method, this does not exist, hence the question.

The specific code i'm thinking of comes from this answer (see below). The comments to that answer suggests that you have to unsubscribe to avoid memory leaks, but is this really true? What is being leaked exaktly? Another answer to the same question that tried to handle unsubscription, actually added a potential memory leak instead (by storing the eventhandlers in a static dictionary that might not be cleaned up).

private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
    DataGrid dataGrid = source as DataGrid;
    ObservableCollection<DataGridColumn> columns = e.NewValue as ObservableCollection<DataGridColumn>;

    // There should be no need to unsubscribe to e.OldValue?

    dataGrid.Columns.Clear();

    if (columns == null)
    {
        return;
    }

    foreach (DataGridColumn column in columns)
    {
        dataGrid.Columns.Add(column);
    }

    // This event handler will not keep the columns alive, and the lambda will only be alive as long as the columns is alive?
    columns.CollectionChanged += (sender, e2) =>
    {
        NotifyCollectionChangedEventArgs ne = e2 as NotifyCollectionChangedEventArgs;
        if (ne.Action == NotifyCollectionChangedAction.Reset)
        {
            // Clear dataGrid.Columns
            ...
        }
        else if (ne.Action == NotifyCollectionChangedAction.Add)
        {
            // Add to dataGrid.Columns
            ...
        }
        else if (ne.Action == NotifyCollectionChangedAction.Move)
        {
            ...
        }
        else if (ne.Action == NotifyCollectionChangedAction.Remove)
        {
         ...
        }
        else if (ne.Action == NotifyCollectionChangedAction.Replace)
        {
          ...
        }
    };
}
micnil
  • 4,705
  • 2
  • 28
  • 39
  • 2
    It's not only about `this`, it's also about what your lambda *captures*. – dymanoid Oct 02 '19 at 09:59
  • @dymanoid That is interesting. So in the case above the `dataGrid` is used in the lambda. Does that mean that both the `dataGrid` and the `columns` will keep the lambda alive? – micnil Oct 02 '19 at 10:51

1 Answers1

4

Considering the answer you referenced and the comment about the leak, you have to note the following.

There is an object of type ObservableCollection<DataGridColumn> in a viewmodel. In WPF, viewmodels might live longer than their views (e.g. you can switch different views for the same viewmodel or you can keep your viewmodel alive the whole time and only display the corresponding view when needed, think of a hideable tool window, for example).

That viewmodel object gets an event subscription with a lambda:

columns.CollectionChanged += (sender, e2) => { /* ... */ };

The lambda itself captures a view element - the dataGrid:

// lambda body
{
    // ...
    dataGrid.Columns.Clear();
}

Now, you have a strong reference chain: columns -> lambda object -> dataGrid.

That means - the dataGrid object will live as long as the columns object is alive. As noted above, this is a viewmodel object and it can live the whole time the app is running. Thus, the dataGrid will continue to live even if the corresponding view is not visible anymore and has no other references.

That is the leak they talk about.

dymanoid
  • 14,771
  • 4
  • 36
  • 64
  • Great answer, this clicked for me: "`columns` -> lambda object -> `dataGrid`", one follow-up that might warrant a new question: does the lamba only reference the `dataGrid` because it is used within the lambda? Or does the lambda capture references to all variables in its lexical scope? I.e: If I stop using `dataGrid` in the lambda body, would there still be a leak because it exists in the lexical scope? – micnil Oct 02 '19 at 12:21
  • 2
    The lambdas in C# only capture those variables that are explicitly used in the lambda body. In contrast to C++, where you can instruct the lambda to capture everything in the scope. So if `dataGrid` won't be used (captured) by the lambda, there will be no `dataGrid` leak. – dymanoid Oct 02 '19 at 12:22