dotMemory tells me (screenshot below, "WPF binding leak") what there is a memory leak when binding to dictionary like this:
<ComboBox ItemsSource="{Binding Items, Mode=OneTime}"
DisplayMemberPath="Value"
SelectedValue="{Binding SelectedItem}"
SelectedValuePath="Key" />
Question 1, to everyone: why is it a memory leak (namely what scenario should I use to run into problems) and how to fix it?
Queston 2, to dotMemory experts: why so basic mvvm application (see below) has so many problems reported? Should I fix those problems? How?
MCVE (create new WPF solution, use above code in xaml) code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string property = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
public Dictionary<string, string> Items { get; } = new Dictionary<string, string>
{
{ "1", "One" },
{ "1a", "One and a" },
{ "2a", "Two and a" },
};
string _selectedItem = "1a";
public string SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
}
Binding target objects that do not implement the
INotifyPropertyChanged interface or do not use the OneTime binding
mode
Answer 1:
Xaml is bound to Dictionary which is a collection of KeyValuePair and Value property of it is specified as a source for DisplayMemberPath.
KeyValuePair which is exposed doesn't implement INotifyPropertyChanged interface and there is no way to specify OneTime binding mode for DisplayMemberPath. So, all items of Dictionary will stay in memory forever.
Answer 2:
dotMemory reports potential problems, only you can determine if it is a real problem or not.
Unfortunately .NET itself makes string duplicates and creates array which is never will be filled with data, dotMemory reports them too because can't distinguish if these objects created by "user" or by system.
I would recommend you to see why do you have finalized objects, it seems that you forget to call IDisposable.Dispose method for some objects. And check if these not filled arrays created by you or not.
The reason you are getting a Memory leak is that you are binding to an object that doesn't implement the interface INotifyPropertyChanged.
When we bind to an dictionaries' Value property...
the binding target
starts listening for property change notifications. If the property is
not a DependencyProperty or an object that implements
INotifyPropertyChanged, WPF will resort to subscribing to the ValueChanged event of the System.ComponentModel.PropertyDescriptor class to get notifications when the source object’s property value changes.
Why is this a problem? Well, since the runtime creates a reference to
this PropertyDescriptor, which in turn references our source object,
and the runtime will never know when to deallocate that initial
reference (unless explicitly told), both the PropertyDescriptor as
well as our source object will remain in memory.
(source)
This is solved by binding to an ObservableDictionary<Key, Value>
Related
I'm trying to understand WPF memory leaks and after reading up on the subject, I have some unclear areas.
Questions are best derived from example, so let's define:
Model:
public class Mom : INotifyPropertyChanged
{
public ObservableCollection<Kid> Kids { get; set; }
private string name;
public string Name
{
get => name;
set => Set(ref name, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T field, T newValue = default(T), [CallerMemberName] string propertyName = null)
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The ViewModel (DataContext) could look like this:
public class MomViewModel, INotifyPropertyChanged
{
private Mom selected;
public Mom Selected
{
get => selected;
set => Set(ref selected, value);
}
}
Now I want to ask questions about these 2 binding scenarios in XAML:
First binding:
<ListView ItemsSource="{Binding Selected.Kids}">
...
</ListView >
Second binding:
<TextBlock Text="{Binding Selected.Kids.Count}" />
Now imagine that inside the ViewModel, we have a timer that assigns a new Mom every second. That is Selected = new Mom { .. };.
Q1:
Would binding 1 produce a memory leak? The property is of type ObservableCollection which implements INotifyPropertyChanged, but the property itself does not (just regular get,set).
Q2:
Would binding 2 produce a memory leak? The binding is directly against Count which is from Collection and doesn't implement INotifyPropertyChanged.
Notice that the view (XML) itself is never destroyed - only the "Selected" property is changed every second. It is (also) not clear to me when WPF allows for garbage collection - only when the view is destroyed, or whenever a binding changes. My tests are inconclusive here...
Thanks.
In the following sample code, the previous instance of Mom will be eligible for garbage collection after you have set the Selected source property to a new Mom object regardless of whether you bind to Selected.Kids or Selected.Kids.Count:
public sealed class MomViewModel : INotifyPropertyChanged, IDisposable
{
private readonly System.Timers.Timer _timer = new System.Timers.Timer();
public MomViewModel()
{
_timer.Interval = 2000;
_timer.Elapsed += (s, e) => Selected = new Mom();
_timer.Start();
}
private Mom selected;
public Mom Selected
{
get => selected;
set => Set(ref selected, value);
}
public event PropertyChangedEventHandler PropertyChanged;
private void Set<T>(ref T field, T newValue = default(T), [CallerMemberName] string propertyName = null)
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Dispose()
{
_timer.Dispose();
}
}
You don't introduce any memory leaks by setting up the following bindings in the view:
<ListView ItemsSource="{Binding Selected.Kids}" />
<TextBlock Text="{Binding Selected.Kids.Count}" />
None of the WPF specific parts you have there will cause memory leaks.
WPF bindings are weak references so they do not inherently keep things alive.
There is the potential for a memory leak if you bind to a poco which does not implement inotifypropertychanged. You avoided that.
Whether you raise property changed in a setter or not does not matter. Hence count also doesn't cause any memory leak.
If you have a problem anywhere it looks much more likely to be in how you are retaining a reference to each of these Mom you're newing up every second. Something still has a reference to these and is not allowing it to go out of scope. A fix might be as simple as taking older Mom's out the observablecollection and disposing them.
If you wanted to get to grips with exactly what is keeping things from being garbage collected in a complex enterprise sized app then you could give redgate Ants profiler a try. There's a free trial.
I experiment with the order of setting the DataContext property in the default constructor in WPF.
<StackPanel>
<ListBox ItemsSource="{Binding MyItems, PresentationTraceSources.TraceLevel=High}"></ListBox>
<TextBlock Text="{Binding SomeText}"></TextBlock>
<TextBlock Text="{Binding SomeNum}"></TextBlock>
<TextBlock Text="{Binding Path=Person.Name}"></TextBlock>
<ListBox ItemsSource="{Binding Path=PersonList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
1) With DataContext set before the InitializeComponent method
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string someText = "Default text";
public List<string> MyItems { get; set; }
public List<Person> PersonList { get; set; }
public Person Person { get; set; }
public int SomeNum { get; set; }
public string SomeText
{
get
{
return someText;
}
set
{
someText = value;
OnPropertyChanged("SomeText");
}
}
public MainWindow()
{
this.DataContext = this;
MyItems = new List<string>();
PersonList = new List<Person>();
Person = new Person();
InitializeComponent();
/*These changes are not reflected in the UI*/
SomeNum = 7;
Person.Name = "Andy";
/*Changes reflected with a help of INotifyPropertyChanged*/
SomeText = "Modified Text";
/* Changes to the Lists are reflected in the UI */
MyItems.Add("Red");
MyItems.Add("Blue");
MyItems.Add("Green");
MyItems[0] = "Golden";
PersonList.Add(new Person() { Name = "Xavier" });
PersonList.Add(new Person() { Name = "Scott" });
PersonList[0].Name = "Jean";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class Person
{
public string Name { get; set; } = "Default Name";
}
After the call to the InitializeComponent method changes to the values of properties are not reflected in the UI except for those properties which use INotifyPropertyChanged. Everything is clear so far.
However I noticed that changes to the list items are also reflected in the UI. How come?
I always thought that in order to reflect adding/removing from the collection I need ObservableCollection and to implement INotifyPropertyChanged on list object to detect modifications of these objects. What is the meaning of this?
2) With DataContext set after the InitializeComponent method
Why setting a DataContext property after the InitializeComponent is a bad practice with MVVM? Could you describe it more thoroughly or give a simple code example?
I always thought that in order to reflect adding/removing from the collection I need ObservableCollection<T> and to implement INotifyPropertyChanged on list object to detect modifications of these objects.
You do, if you want reliable updating of the UI during changes in the view model.
What is the meaning of this?
The "meaning" is that in your particular scenario, you are making assumptions that aren't valid. WPF components go through a variety of initialization steps, only some of which occur as part of the InitializeComponent() method.
If, for example, you were to move the code for your value updates into a handler for the Loaded event, you'd find some of the updates reflected in the UI, but not all.
If you move that same code into a method invoked via Dispatcher.InvokeAsync() using a priority of DispatcherPriority.SystemIdle, you'd find that none of the updates would be observed, except for the one backed by INotifyPropertyChanged. In that case, you're explicitly waiting until every aspect of initialization has completed, and there are no longer opportunities for the initialization code to observe your updated values.
It's all about timing. Any code that sets a value before the UI winds up observing it, can do so successfully without INotifyPropertyChanged or equivalent. But you're entirely at the mercy of the current implementation of the framework in that case. Different parts of the initialization happen at different times, and these are not all documented, so you're relying on undocumented behavior. It probably won't change, but you have no way to know for sure.
Why setting a DataContext property after the InitializeComponent is a bad practice with MVVM?
It's not. Don't believe everything you read, even (or especially!) on the Internet.
If you want to forego implementation of INotifyPropertyChanged, then it will be important that you initialize all of your view model data before assigning the DataContext. But, even if you assign the DataContext after calling InitializeComponent, that assignment will be observed (because DataContext is a dependency property and so provides property changed notification to the framework), and the UI will retrieve all of the bound data from your view model data.
What's important is that the view model data be initialized before the assignment of DataContext. Where that happens relative to InitializeComponent() is not important.
When a view model property does not fire the PropertyChanged event, its value must of course be set before assigning the view model instance to the view's DataContext.
It does however not matter if you assign the DataContext before or after calling InitializeComponent:
Given a Binding like
<TextBlock Text="{Binding SomeText}"/>
these two sequence will both result in showing the property value in the view:
DataContext = new { SomeText = "Hello, World." };
InitializeComponent();
and
InitializeComponent();
DataContext = new { SomeText = "Hello, World." };
Under certain conditions if the user selects an item in a combobox, it automatically must be changed to another item
ViewModel
public class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private string selected;
public string Selected
{
get { return selected; }
set
{
if (selected != value)
{
selected = value;
OnPropertyChanged("Selected");
}
}
}
private ObservableCollection<string> collection;
public ObservableCollection<string> Collection
{
get { return collection; }
set
{
collection = value;
OnPropertyChanged("Collection");
}
}
public VM()
{
this.Collection = new ObservableCollection<string>(new string[] { "A", "B", "C" });
this.Selected = "A";
this.PropertyChanged += VM_PropertyChanged;
}
void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.Selected = "C";
}
}
View
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Grid>
<ComboBox ItemsSource="{Binding Collection}" SelectedValue="{Binding Selected}"/>
</Grid>
<Label Content="{Binding Selected, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Window>
So, in this example, no matter what do I select, it should show "C" both, on the combobox and Label, but "C" only shows on Label, it means that the ViewModel is updated but not the view.
It seems the problem here is to try to change the property from the PropertyChanged method.
What could be wrong?
Here's how I would most likely do it, but the BeginInvoke() call that does the magic could just as easily be called from your PropertyChanged handler.
What it's doing is essentially queueing the action to happen after the entire property-set business has fully completed. The DispatcherPriority.ApplicationIdle flag is a key point.
As you've found, it's useless for the PropertyChanged handler and the property setter to raise PropertyChanged while the ComboBox is still in the process of changing its selection. This code lets that whole thing finish, and then immediately changes Selected to something else. At that point, the ComboBox will be at leisure to take notice of your PropertyChanged event and update its own selection.
private string selected;
public string Selected
{
get { return selected; }
set
{
if (selected != value)
{
// Don't let them select "B".
if (value == "B")
{
Dispatcher.CurrentDispatcher.
BeginInvoke(new Action(() => this.Selected = "C"),
DispatcherPriority.ApplicationIdle);
return;
}
selected = value;
OnPropertyChanged("Selected");
}
}
}
Under certain conditions if the user selects an item in a combobox, it automatically must be changed to another item
For what it's worth, I think it would be a good idea to revisit that design choice. It is likely to be confusing to users, and there is probably a better way to present that state of affairs to the user, than to ignore input they give the program. There's not enough context in your question to fully understand how you got into this situation in the first place, so I can't offer anything more than to suggest it's likely better to fix the design, than to finagle the code into doing what you want.
That said…
The issue you are running into is that WPF ignores property-changed events for the source of a binding it is currently already updating. In your scenario, the binding is updating the Selected value from its binding, and so changes to that property will be ignored until that binding update is completed.
There are a variety of ways to get the code to work the way you want. Probably the easiest is to simply defer the update of the source property until the handling of the user input has completed. You can do that by using the Dispatcher.InvokeAsync() method:
void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Dispatcher.CurrentDispatcher.InvokeAsync(() => this.Selected = "C");
}
I'm not a big fan of the above, because of the fact that it takes what ideally should be a view-agnostic object, the view model, and injects knowledge of your specific view API, i.e. use of the Dispatcher object. That said, there are other mechanisms you could use which are similar, and which don't rely on the Dispatcher object (e.g. using an asynchronous timer).
There are a variety of other examples of ways to address this on Stack Overflow. For example, you might look at this answer for inspiration. I don't think it will do exactly what you want "straight out of the box", but the attached property approach might be something you find more appropriate, by moving the logic from view model to view code, and thus a place where it's more appropriate to use Dispatcher. (And arguably, if you are going to do something like this, the logic probably belongs in the view anyway…it's weird enough there, but I see no compelling reason this should be inherent in the view model.)
Another approach can be seen in the question Coerce a WPF TextBox not working anymore in .NET 4.0. I.e. manually force the view's state to be updated after the fact.
I'm a total newbie, just learning the basics of DataContext and the MVVM model. I've now got a grid bound to a view model object which implements INotifyPropertyChanged, however it appears that UpdateSourceTrigger (which all the WPF tutorials tell me to use) is not available for WinRT / Metro Style apps!
How do I implement INotifyPropertyChanged then?
I'm at the end of my tether here. I've spend nearly the whole day on the most basic of app examples, simply trying to get a grid to update after I click something. The only way I've managed to do this so far is to create an entirely new instance of the view model and reassign the DataContext which I know is wrong
UPDATE:
I have made some progress, but things have gotten very weird. I have a view model, with a generic list of items. The items list is wired up with a PropertyChangedEventHandler. If I replace the entire collection with a new one, the listview updates.
model.Items = new List<DataItem>{ new DataItem{ Title = "new item" }};
This results in a one item list with the above item. However, if I try adding an item, nothing happens
model.Items.Add(new DataItem{ Title = "added item" });
I also tried creating a method which added an item and specifically fired PropertyChanged, but that also doesn't work
Here's where it gets weird. Next I tried this code.
model.Items.Add(new DataItem { Title = "added item" });
model.Items = new List<DataItem> { new DataItem { Title = "new item" }};
This results in a two item list:
- new item
- added item
How can this be? The code says, "add one item" then "replace the whole list" but it executes in the reverse order?
UPDATE 2:
I've switched to ObservableCollection as suggested, which has actually solved the original problem. I can now add an item and it shows up on the list.
However, the new weird behaviour is still in effect. Items added before the collection is reset are appended to the end of the new collection. Why is my code executing in reverse order?
You need to implement the interface and send out the notification once the given property you care about changes.
public event PropertyChangedEventHandler PropertyChanged;
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CustomerName"));
}
}
}
}
Keep in mind that for a collection, you should use an ObservableCollection as it will take care of the INotifyCollectionChanged being fired when an item is added or removed.
I would suggest to scale your sample back as far as possible. Don't start with a DataGrid but rather a simple TextBoxand Button, where the Button forces a change in your ViewModel which will then reflect on the UI.
Code taken from here.
It's best to implement a parent class which implements it like this:
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
And then in your subclass (i.e. ViewModel) in your property do something like this:
public class MyViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name {
get{ return _name; }
set{
_name = value;
RaisePropertyChanged("Name");
}
}
}
When using WPF databinding, I obviously can't do something along the lines of MyCollection = new CollectionType<Whatever>( WhateverQuery() ); since the bindings have a reference to the old collection. My workaround so far has been MyCollection.Clear(); followed by a foreach doing MyCollection.Add(item); - which is pretty bad for both performance and aesthetics.
ICollectionView, although pretty neat, doesn't solve the problem either since it's SourceCollection property is read-only; bummer, since that would have been a nice and easy solution.
How are other people handling this problem? It should be mentioned that I'm doing MVVM and thus can't rummage through individual controls bindings. I suppose I could make a wrapper around ObservableCollection sporting a ReplaceSourceCollection() method, but before going that route I'd like to know if there's some other best practice.
EDIT:
For WinForms, I would bind controls against a BindingSource, allowing me to simply update it's DataSource property and call the ResetBindings() method - presto, underlying collection efficiently changed. I would have expected WPF databinding to support a similar scenario out of the box?
Example (pseudo-ish) code: WPF control (ListBox, DataGrid, whatever you fancy) is bound to the Users property. I realize that collections should be read-only to avoid the problems demonstrated by ReloadUsersBad(), but then the bad code for this example obviously wouldn't compile :)
public class UserEditorViewModel
{
public ObservableCollection<UserViewModel> Users { get; set; }
public IEnumerable<UserViewModel> LoadUsersFromWhateverSource() { /* ... */ }
public void ReloadUsersBad()
{
// bad: the collection is updated, but the WPF control is bound to the old reference.
Users = new ObservableCollection<User>( LoadUsersFromWhateverSource() );
}
public void ReloadUsersWorksButIsInefficient()
{
// works: collection object is kept, and items are replaced; inefficient, though.
Users.Clear();
foreach(var user in LoadUsersFromWhateverSource())
Users.Add(user);
}
// ...whatever other stuff.
}
If the object MyCollection is of implements INotifyPropertyChanged, you can simply replace the collection.
An example:
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Whatever> _myCollection;
private void NotifyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public ObservableCollection<Whatever> MyCollection
{
get
{
return _myCollection;
}
set
{
if (!ReferenceEquals(_myCollection, value))
{
_myCollection = value;
NotifyChanged("MyCollection");
}
}
}
}
With this, when you assign a collection, WPF detects this and everything gets updated.
This is how I'd solve this.
The link below explains how to implement an AddRange method.
http://web.archive.org/web/20150715112054/http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx
It looks like you're stuck with implementing a sub-class that handles this case correctly.
Apparently, certain controls don't support batched collection change notifications. At least they didn't when that article was written. Though now you should have a bit more information if you want to investigate further.