Niewidzialny
Niewidzialny

Reputation: 340

Why am I experiencing a memory trouble/leaks in UWP/C# app

I created extension to IOrderedIEnumerable which looks like this:

internal static ObservableCollection<T> ToObservableCollection<T>(this IOrderedEnumerable<T> list)
{
    var observableCollection = new ObservableCollection<T>();
    foreach (var p in list)
        observableCollection.Add(p);
    return observableCollection;
}

Also I have a page which looks like:

private ObservableCollection<BusStopName> _ListOfBusStopNames;

public BusStopsListPage()
{
    this.InitializeComponent();
    this.SetIsBackFromPageAllowed(true);

    _ListOfBusStopNames = Timetable.Instance.BusStopsNames
                                   .OrderBy(p => p.Name)
                                   .ToObservableCollection<BusStopName>();
}

And in this page i have a listview which has binding to _ListOfBusStopNames

Timetable.Instance.BusStopsNames has 2812 entries.

When I'm navigating to this page, application memory is growing up to infinity numbers. It looks like (red line is moment when I navigated to this page):

enter image description here

In memory snapshot we can see that this thing is making this problem: enter image description here

I don't have any idea what it can be. Do anyone have any idea?

When I change instance constructor to:

public BusStopsListPage()
{
    this.InitializeComponent();
    this.SetIsBackFromPageAllowed(true);

    _ListOfBusStopNames = new ObservableCollection<BusStopName>();
}

everything works nice. Also, when I changed in this constructor manually List ( Timetable.Instance.BusStopsNames ) to ObservableCollection, it works great too.

EDIT Hm.. i tried to make an example how to reproduce it, and i get to a solution for it. It is very confusing, so if someone can explain why this is happening it will be nice :)

  1. Create UWP APP
  2. Add in MainPage.xaml only this: <Frame Name="MainFrame" />
  3. In code-behind put:this.Loaded += (s, e) => MainFrame.Navigate(typeof(BlankPage1));
  4. Create new blank page with name BlankPage1
  5. In View of BlankPage add this:

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel> 
            <ListView ItemsSource="{x:Bind _ListOfTestClasses}">
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="local:TestClass">
                        <Grid>
                            <TextBlock Text="{x:Bind Name}" />
                        </Grid>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackPanel>
    </Grid>
    
  6. In code-behind put:

        public sealed partial class BlankPage1 : Page
        {
            private List<TestClass> _List;
            private ObservableCollection<TestClass> _ListOfTestClasses;
            public BlankPage1() {
                this.InitializeComponent();
                GenerateTestData();
                _ListOfTestClasses = _List.OrderBy(p => p.Name).ToObservableCollection<TestClass>();
            }
            private void GenerateTestData() {
                _List = new List<TestClass>();
                for(int i = 0; i < 2800; i++)
                    _List.Add(new TestClass() { Id = i, Name = Guid.NewGuid().ToString() });
            }
        }
    
        public static class Extension {
            public static ObservableCollection<T> ToObservableCollection<T>(this IOrderedEnumerable<T> list) {
                var observableCollection = new ObservableCollection<T>();
                foreach (var p in list)
                    observableCollection.Add(p);
                return observableCollection;
            }
        }
        public class TestClass {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    
  7. Run the app

So.. you can see that app is lagging and memory is going taking more and more.. And now.. try to delete in BlankPage1 view StackPanel. And now run the app.

Upvotes: 1

Views: 434

Answers (1)

Laith
Laith

Reputation: 6091

This is a common problem. The issue is the misuse of panels in XAML. A Grid will stretch to fit its container's boundaries and cascade this to its children. A StackPanel will only cascade the size of the opposing orientation.

For example, when you have <StackPanel Orientation="Vertical"/>, all children will receive the width of the parent Grid, but not the height. Because the orientation is vertical, the height will be infinite to allow the panel to stack its children based on their requested heights.

When you have <StackPanel Orientation="Horizontal"/>, all children will receive the height of the parent Grid, but not the width. Same reason as above. It needs to stack horizontally.

So..., when you have a <ListView/> inside a <StackPanel/>:

<StackPanel>
   <ListView>...</ListView>
</StackPanel>

The StackPanel is telling the ListView to render its size with height = infinity. And therefore, all 2,800 items in your ListView are actually being rendered, not virtualized.

You have to avoid putting ListViews in StackPanels when the orientations are the same.

This should work:

<Grid>
   <StackPanel Orientation="Horizontal">
      <ListView>...</ListView>
   </StackPanel>
</Grid>

... but it's silly.

Upvotes: 2

Related Questions