Yesterday I wrote a post about different ways of working with
INotifyPropertyChanged. One of my readers (who only went by
"Guest") pointed out that binding "magically" worked in WPF with
POCOs (Plain Old CLR Objects) that didn't bother to implement
INotifyPropertyChanged. I thought for sure he/she was wrong … until
I tried it myself. Magic, I say! Magic!
Then again, as one of the WPF team members pointed out to me,
this is a great example of Clarke's third law. :)
Take this example:
<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">
<Grid>
<ListBox x:Name="PeopleList"
Height="287"
HorizontalAlignment="Left"
DisplayMemberPath="LastName"
Margin="12,12,0,0"
VerticalAlignment="Top"
Width="138" />
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="265,24,0,0"
x:Name="LastNameField"
VerticalAlignment="Top"
DataContext="{Binding ElementName=PeopleList, Path=SelectedItem}"
Text="{Binding LastName}"
Width="120" />
<TextBlock Height="23"
HorizontalAlignment="Left"
Margin="178,27,0,0"
Text="Last Name"
VerticalAlignment="Top" />
</Grid>
</Window>
Here's the Person Class
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Here's the Code-behind
public partial class MainWindow : Window
{
private ObservableCollection<Person> _people;
public MainWindow()
{
InitializeComponent();
_people = new ObservableCollection<Person>();
_people.Add(new Person() { LastName = "Brown" });
_people.Add(new Person() { LastName = "Fitz" });
_people.Add(new Person() { LastName = "Hanselman" });
_people.Add(new Person() { LastName = "Heuer" });
_people.Add(new Person() { LastName = "Galloway" });
_people.Add(new Person() { LastName = "Stagner" });
_people.Add(new Person() { LastName = "Liberty" });
_people.Add(new Person() { LastName = "Litwin" });
_people.Add(new Person() { LastName = "Papa" });
_people.Add(new Person() { LastName = "Guthrie" });
PeopleList.ItemsSource = _people;
}
}
When you run this little application, and modify the value in
the TextBox, the value in the ListBox is updated, seemingly by
magic.
There it is. The value in the ListBox updated right when I
tabbed off of the last name field. Magic code? Spirits? Too much
alcohol? What is happening here?
What's Really Happening
The Person class has a PropertyDescriptor for each property. The
PropertyDescriptor has the AddValueChanged method by which a
listener can sign up to get the property change notifications from
that descriptor. That's what the behind-the-scenes binding is doing
- wiring up a value changed event for the property descriptor in
play in the binding.
Note this only works when using binding. If you update the
values from code, the change won't be notified.
What's happening is actually a perfectly valid (if not optimal)
way to handle binding and change notification. What WPF is doing
behind the scenes in this case is something you could explicitly
put in your own code if you wish.
Implications
The PropertyDescriptor class is pretty heavy.
In addition, WPF must request the full set of descriptors for the
class, not just a single descriptor. If you have a class with a
large number of properties, regardless of how many are actually
involved in binding, the CPU and memory usage associated
with that call can be both significant and measurable. In
one instance, the memory used to get all the property descriptors
for a large class was around 800K!
But doesn't WPF need that for binding anyway?
No. WPF uses the much lighter weight PropertyInfo class when
binding. If you explicitly implement INotifyPropertyChanged, all
WPF needs to do is call the PropertyInfo.GetValue method to get the
latest value. That's quite a bit less work than getting all the
descriptors. Descriptors end up costing in the order of 4x
the memory of the property info classes.
Note also that if you have a reference from the POCO back in the
UI (we don't do that, right…right?) you may be subject to the
memory leak described in KB938416. That only applies to the
PropertyDescriptor-based approach, which in this post, is POCO
only.
Summary
Implementing INotifyPropertyChanged can be a fair bit of tedious
development work. However, you'll need to weigh that work against
the runtime footprint (memory and CPU) of your WPF application.
Implementing INPC yourself will save runtime CPU and memory.
Thanks to the WPF team for the detailed information about what's
going on here. I love being able to talk to the people who actually
wrote the code :)
Note that Silverlight does not have the PropertyDescriptor
class, so this doesn't apply there.