We all know what the INotifyPropertyChanged interface does. It can be used to raise an event when a property of a class changes. Then in another section of code we can subscribe to these events and perform certain actions based on the application needs. It's all very cool but also old hat and pedestrian.
But the thing with this interface is that you can end up writing a lot of boiler plate code! For example, take this simple person class:
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public DateTime DateOfBirth { get; set; } }
It's nice. It's neat.. All is good with the world. But then you decide you want to be notified when one of the fields in the class is changed, so we introduce the INotifyPropertyChanged interface. Suddenly, for every property we find ourselves expanding the above code like this:
private string m_firstName; public string FirstName { get { return m_firstName; } set { OnPropertyChanged("FirstName", m_firstName, value); m_firstName = value; } }
For every property you need to include a backing field, then fill in the getter and setter functions yourself and in the setter field ensure you raise the property changed event handler.. Geez! If you need to add this to a number of objects within a sizeable project, the work can quickly become monotonous.
The full class definition now looks like this:
class PersonNotify : INotifyPropertyChanged { private string m_firstName; public string FirstName { get { return m_firstName; } set { OnPropertyChanged("FirstName", m_firstName, value); m_firstName = value; } } private string m_lastName; public string LastName { get { return m_lastName; } set { OnPropertyChanged("LastName", m_lastName, value); m_lastName = value; } } private DateTime m_dateOfBirth; public DateTime DateOfBirth { get { return m_dateOfBirth; } set { OnPropertyChanged("DateOfBirth", m_dateOfBirth, value); m_dateOfBirth = value; } } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName, object before, object after) { if (PropertyChanged != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
This is the situation I was in when I thought to myself “surely there must be a better way?”. And, in the internet age, if you can think of it, chances are someone else has already implemented it!
Enter Fody! You can find the nuget package here and install it with nuget like this:
Install-Package PropertyChanged.Fody
It’s a great little utility which, at compile time, looks for classes that implement the INotifyPropertyChanged interface and implements the backing fields for each property of your class as well as raising the event for you.
With fody installed we can now revert back to our original class with just a couple of small modifications:
public class Person : INotifyPropertyChanged { public string FirstName { get; set; } public string LastName { get; set; } public DateTime DateOfBirth { get; set; } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName, object before, object after) { if (PropertyChanged != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
In the code above we have implemented the INotifyPropertyChanged interface and added a small bit of code to raise an event when any of the properties change.
Side note: In my final code I also extract the event and OnPropertyChanged handler into a central base class which cleans up all the model classes that derive from it.
Now, when you build it you will see this output in the Visual Studio build window:
1>------ Build started: Project: FodyTest, Configuration: Debug Any CPU ------ 1> Fody: Fody (version 2.0.0.0) Executing 1> Fody/PropertyChanged: No reference to 'PropertyChanged' found. References not modified. 1> Fody: Finished Fody 52ms. 1> Fody: Skipped Verifying assembly since it is disabled in configuration 1> Fody: Finished verification in 0ms. 1> FodyTest -> C:\OceanAirdrop\TempCode\FodyTest\FodyTest\bin\Debug\FodyTest.exe ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
Now that’s nice and saves us a lot of work. Here's the full implementation:
class Program { static void Main(string[] args) { var p = new Person(); p.PropertyChanged += PropertyChangedEvent; p.FirstName = "Berty"; p.LastName = "Burnstein"; p.DateOfBirth = DateTime.Now; } private static void PropertyChangedEvent(object sender, PropertyChangedEventArgs e) { var propertyChanged = (OceanAirdropPropertyChangedArgs)e; Trace.WriteLine(string.Format("{0} changed from {1} to {2}.", propertyChanged.PropertyName, propertyChanged.Before, propertyChanged.After)); } } // Create your model objects as normal, but derive from BaseData and INotifyPropertyChanged public class Person : BaseData, INotifyPropertyChanged { public string FirstName { get; set; } public string LastName { get; set; } public DateTime DateOfBirth { get; set; } } // Create a base class that implements the INotifyPropertyChanged interface and raises the event public class BaseData : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName, object before, object after) { if (PropertyChanged != null) PropertyChanged.Invoke(this, new OceanAirdropPropertyChangedArgs(propertyName, before, after)); } } // Create own event args that capture property value before and after! public class OceanAirdropPropertyChangedArgs : PropertyChangedEventArgs { public OceanAirdropPropertyChangedArgs(string propertyName) : base(propertyName) { } public OceanAirdropPropertyChangedArgs(string propertyName, object before, object after) : base(propertyName) { Before = before; After = after; } public virtual object Before { get; } public virtual object After { get; } }
So there we have it. I have written this blog post as a reminder for me, the next time I need to implement notification changes in another project. Fody is a nice little utility to be aware of and kept in our software toolbox.