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.
Related
I have a ListView bound to a collection, and I want the ListView to automatically update when an item is added to the collection. I managed to get it working using an ObservableCollection, but I'd rather to use INotifyPropertyChanged instead. Maybe you can give me a hint what I am doing wrong?
First, here is the (relevant part of) XAML:
<StackPanel DataContext="{Binding Family}"> <!-- DataContext is of type Family -->
<TextBlock Text="{Binding LastName}"/>
<ListView ItemsSource="{Binding Members}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
Here are the relevant classes:
public class Family : INotifyPropertyChanged
{
public string LastName { get; private set; }
private readonly IList<Member> _members;
public IEnumerable<Member> Members { get => _members; }
public Family(string lastName, IEnumerable<Member> members)
{
LastName = lastName;
_members = members.ToList();
}
public void AddMember(string name)
{
var member = new Member { FirstName = name };
_members.Add(member);
OnPropertyChanged(nameof(Members));
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class Member
{
public string FirstName { get; set; }
}
If I use this code and call AddMember somewhere, it will not update the ListView GUI. I don't see why not, because AddMember calls OnPropertyChanged(nameof(Members)), and Members is what the ListView is bound to. So it should get notified about the change.
So what am I doing wrong?
If I change IList<Member> _members into ObservableCollection<Member> _members and _members = members.ToList() into _members = new ObservableCollection<Member>(members) accordingly, it works as expected.
After adding an item to the _members collection, the reference returned by Members is still the same. The Equals method of collections will usually compare references, not items. Consequently, the binding will not detect a change and does not reevaluate the property.
If you want to get this to work, you could do one of the following:
Assign null temporarily, raise property changed, reassign the collection and raise property changed again, so the binding detects a changed reference (thanks to #Ash).
public void AddMember(string name)
{
var member = new Member { FirstName = name };
_members.Add(member);
var members = _members;
_members = null;
OnPropertyChanged(nameof(Members));
_members = members;
OnPropertyChanged(nameof(Members));
}
Naive approach, recreate the collection when you add a member, e.g:
public void AddMember(string name)
{
var member = new Member { FirstName = name };
_members = _members.ToList();
_members.Add(member);
OnPropertyChanged(nameof(Members));
}
This is costly due to lots of unnecessary allocations, don't do it.
As you can see, both approaches have their downsides, either firing additional property changed notifications or unnecessary allocations which will additionally cause the ListView to remove and recreate all of its items each time. This is why there is an ObservableCollection<T> type that implements INotifyCollectionChanged, which allows notifying added and removed items specifically, as well as other operations.
First of all, it is good that you decided that a Family is not an ObservableCollection. After all, you can do a lot of things with ObservableCollections that can't be done in Families. For instance: what would Replace(Member) mean in the context of a family?
The problem is, that you forgot to implement INotifyCollectionChanged.
With this interface you can notify others that you added an element (and moved, and deleted, etc.)
public class Family : INotifyPropertyChanged, INotifyCollectionChanged
{
...
Because you also have to notify if elements are moved / deleted / etc. This will cost some development effort if your family can do more than just Add.
Therefore it might be a good idea to change your
private readonly IList<Member> _members;
into an ObservableCollection<Member>, and implement the interface via this ObservableCollection.
class Family : INotifyPropertyChanged, INotifyCollectionChanged
{
public string LastName { get; private set; }
private readonly ObservableCollection<Member> members;
public IEnumerable<Member> Members => this.members;
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => this.members.NotifyCollectionChangedEventHandler.CollectinChanged += value;
remove => this.members.NotifyCollectionChangedEventHandler.CollectinChanged -= value;
}
Now your Add / Remove / Replace / Move / etc methods will be one-liners; the appropriate event will be raised.
public void Add(Member member)
{
this.members.Add(member);
}
public void Remove(Member member)
{
this.members.Remove(member);
}
Not sure if you need methods to Move and Replace family Members, but even if you need to, they will also be one-liner calls to the corresponding ObservableCollection method
You have completely hidden that you are using an ObservableCollection<Member>, so if in future you need to completely get rid of the ObservableCollection, your users won't have to change, as long as you promise to implement the interfaces.
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>
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." };
I was experimenting with Data Binding in Windows Forms and found a glitch that I can't explain. I post the question here in hopes that someone in the community can come up with an answer that makes sense.
I tried to come up with a clever way of binding read-only values that depend on operations on other values, and update it automatically when the dependent values change.
I created a form with 3 textboxes, where I want the sum of the first 2 to appear in the 3rd textbox.
The following code should work, but doesn't, at least not properly:
public class Model : INotifyPropertyChanged
{
private int m_valueA;
private int m_valueB;
public int ValueA
{
get { return m_valueA; }
set { m_valueA = value; RaisePropertyChanged("ValueA"); }
}
public int ValueB
{
get { return m_valueB; }
set { m_valueB = value; RaisePropertyChanged("ValueB"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class DynamicBindingProperty<T> : INotifyPropertyChanged
{
private Func<T> m_function;
private HashSet<string> m_properties;
public DynamicBindingProperty(Func<T> function, INotifyPropertyChanged container, IEnumerable<string> properties)
{
m_function = function;
m_properties = new HashSet<string>(properties);
container.PropertyChanged += DynamicBindingProperty_PropertyChanged;
}
public T Property { get { return m_function(); } }
void DynamicBindingProperty_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!m_properties.Contains(e.PropertyName)) return;
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs("Property"));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitializeDataBinding();
}
private void InitializeDataBinding()
{
Model model = new Model();
DynamicBindingProperty<int> tmp = new DynamicBindingProperty<int>(() => model.ValueA + model.ValueB, model, new[] {"ValueA", "ValueB"});
textBox1.DataBindings.Add("Text", model, "ValueA");
textBox2.DataBindings.Add("Text", model, "ValueB");
textBox3.DataBindings.Add("Text", tmp, "Property");
tmp.PropertyChanged += (sender, args) => Console.WriteLine(args.PropertyName);
}
}
After experimenting for a while, I tried renaming DynamicBindingProperty<T>.Property to something else (e.g. DynamicProperty), and everything worked as expected!. Now, I was expecting something to break by renaming Model.ValueA to Property, but it didn't, and still worked flawlessly.
What is going on here?
I did some debugging and it looks like a bug (or requirement "the property must not be named Property" I am not aware of). If you replace
PropertyChanged(this, new PropertyChangedEventArgs("Property"));
with
PropertyChanged(this, new PropertyChangedEventArgs(null));
it still does not work - null or an empty string means any property may have changed. This indicates that problem is not in the handling of the change notification but that the binding has not been correctly established.
If you add a second property Property2 to DynamicBindingProperty<T> that does the same as Property and bind it to a fourth text box, then both text boxes will get update correctly if you perform a change notification with an empty string, null or "Property2". If you perform the change notification with "Property" both text boxes will not get update correctly. This indicates that the binding to Property is not completely broken and also that the change notification is somewhat broken.
Sadly I was unable to pin down the exact location where things go wrong, but if you invest enough time stepping through optimized framework source code you can probably figure it out. The earliest difference between the case with property name Property and the case with property name Property2 I could identify when processing a change notification was in OnValueChanged() in the internal class System.ComponentModel.ReflectPropertyDescriptor. In one case the base implementation gets called while it gets skipped in the other case - at least if the debugger didn't trick me, but this is hard to tell in optimized code.
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");
}
}
}