Skip to content

Memory leak in ListView #1242

@4nonym0us

Description

@4nonym0us

Describe the bug

OnLoaded method of the ListView from WPF-UI uses DependencyPropertyDescriptor to invoke AddValueChanged, but never unregisters the event handler using RemoveValueChanged, which leads to a memory leak because a strong refence is created to the component.

private void OnLoaded(object sender, RoutedEventArgs e)
{
Loaded -= OnLoaded; // prevent memory leaks
// Setup initial ViewState and hook into View property changes
var descriptor = DependencyPropertyDescriptor.FromProperty(
System.Windows.Controls.ListView.ViewProperty,
typeof(System.Windows.Controls.ListView)
);
descriptor?.AddValueChanged(this, OnViewPropertyChanged);
UpdateViewState(); // set the initial state
}

To Reproduce

An instance of a ListView from WPF-UI stays in memory forever because a strong reference to the component is created and never removed.

public class UnitTest1
{
    [Fact]
    public Task TestWpfUiControlsListView()
    {
        return TestListViewMemoryLeakAsync<Wpf.Ui.Controls.ListView>();
    }

    [Fact]
    public Task TestSystemWindowsControlListView()
    {
        return TestListViewMemoryLeakAsync<System.Windows.Controls.ListView>();
    }

    public async Task TestListViewMemoryLeakAsync<T>() where T : ListView, new()
    {
        WeakReference? wr = null;

        await StartSTATask(() =>
        {
            var listView = new T();

            wr = new WeakReference(listView);

            listView.RaiseEvent(new RoutedEventArgs(FrameworkElement.LoadedEvent));
            listView.RaiseEvent(new RoutedEventArgs(FrameworkElement.UnloadedEvent));
        });

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);

        Assert.NotNull(wr);
        Assert.False(wr.IsAlive);
    }

    private static Task StartSTATask(Action action)
    {
        var tcs = new TaskCompletionSource<object>();
        var thread = new Thread(() =>
        {
            try
            {
                action();
                tcs.SetResult(new object());
            }
            catch (Exception e)
            {
                tcs.SetException(e);
            }
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        return tcs.Task;
    }
}

image

Expected behavior

 ListView should subscribe to Unloaded event and call RemoveValueChanged.

Screenshots

No response

OS version

All.

.NET version

All.

WPF-UI NuGet version

3.0.5 - latest (4.0.0-rc.2)

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions