This project has moved. For the latest updates, please go here.

Best Practices: How to implement INotifyPropertyChanged right? (Part 2)

Coordinator
Apr 23, 2009 at 7:31 PM
Edited Apr 23, 2009 at 7:41 PM

The first part of this discussion was about different approaches to implement the interface. Now, this part deals with calculated properties and unit testing of the INotifyPropertyChanged implementation.
I try to explain the solution with the help of an example. First, I have created a base class for the INotifyPropertyChanged interface. The pros and cons about this implementation can be seen in the first part of this discussion.

[Serializable]
public class Model : INotifyPropertyChanged
{
    [NonSerialized]
    private PropertyChangedEventHandler propertyChanged;
 
    public event PropertyChangedEventHandler PropertyChanged
    {
        add { propertyChanged += value; }
        remove { propertyChanged -= value; }
    }
 
    [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
    protected void RaisePropertyChanged(string propertyName)
    {
        CheckPropertyName(propertyName);
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }
 
    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (propertyChanged != null) { propertyChanged(this, e); }
    }
 
    [Conditional("DEBUG")]
    private void CheckPropertyName(string propertyName)
    {
        PropertyDescriptor propertyDescriptor = TypeDescriptor.GetProperties(this)[propertyName];
        if (propertyDescriptor == null)
        {
            throw new InvalidOperationException(string.Format(null,
                "The property with the propertyName '{0}' doesn't exist.", propertyName));
        }
    }
}

The next step is to create a business object that inherits from model. In my case it’s a simple Person class that contains the name, the height and the weight of a person. Every change of these properties raises the PropertyChanged event.

[Serializable]
public class Person : Model
{
    private string name;
    private double height;
    private double weight;
 
    public string Name
    {
        get { return name; }
        set
        {
            if (name != value)
            {
                name = value;
                RaisePropertyChanged("Name");
            }
        }
    }
 
    public double Height
    {
        get { return height; }
        set
        {
            if (height != value)
            {
                height = value;
                RaisePropertyChanged("Height");
            }
        }
    }
 
    public double Weight
    {
        get { return weight; }
        set
        {
            if (weight != value)
            {
                weight = value;
                RaisePropertyChanged("Weight");
            }
        }
    }
}

So far the sample is straight forward. In the next step I create a Patient class which inherits from Person. The Patient class extends the Person with a property that calculates the body mass index. The calculation of the body mass index depends on the height and the weight of a person. Therefore, I have to ensure that the PropertyChanged event is raised when one of the depending properties changes. I implement this logic in the overridden OnPropertyChanged method.

[Serializable]
public class Patient : Person
{
    public double BodyMassIndex
    {
        get { return Weight / (Height * Height); }
    }
 
    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
 
        if (e.PropertyName == "Weight" || e.PropertyName == "Height")
        {
            RaisePropertyChanged("BodyMassIndex");
        }
    }
}

That’s the way I implement calculated properties. My approach uses string constants to identify the property that changed. Whereas, the RaisePropertyChanged method checks the property name in debug mode, the property name comparison inside the OnPropertyChanged method is never checked. These string constants are error-prone because of typos or refactoring of the property name. A way to avoid such errors is the writing of unit tests. With a little helper method such a unit test can be done in one line.

[TestMethod]
public void PropertyChangedTest()
{
    Patient patient = new Patient();
 
    AssertUtil.PropertyChangedEvent(patient, x => x.Name, () => patient.Name = "Luke");
 
    AssertUtil.PropertyChangedEvent(patient, x => x.Height, () => patient.Height = 1.8);
 
    AssertUtil.PropertyChangedEvent(patient, x => x.Weight, () => patient.Weight = 80);
 
    AssertUtil.PropertyChangedEvent(patient, x => x.BodyMassIndex, () => patient.Height = 1.7);
    AssertUtil.PropertyChangedEvent(patient, x => x.BodyMassIndex, () => patient.Weight = 75);
}

The AssertUtil.PropertyChangedEvent method uses a type-safe implementation to retrieve the property name. The reason that this implementation isn’t used in the business object is that it performs poorly. However, this performance issue doesn’t matter for the unit tests. The first parameter of this method gets the object to listen for PropertyChanged events. The second parameter identifies the property name via a lambda expression. And the last parameter defines an action that should result in the property changed event. AssertUtil.PropertyChangedEvent method fails when the property changed event isn’t raised for the specified property.
The implementation of the AssertUtil class can be seen in the code download.

Code Download: http://compositeextensions.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=26547 

May 28, 2009 at 2:43 PM

How best to implement INotifyPropertyChanged? I would argue "not at all".

Please take a look at Update Controls. This small library automatically discovers dependencies. You don't have to subscribe to or fire notification events.

How it works is that you add fields of type Independent to your class to ride along side your regular fields.

public class Person
{
private string _firstName;
private string _lastName;
private int _displayStrategy;

#region Independent properties
// Generated by Update Controls --------------------------------
private Independent _indDisplayStrategy = new Independent();
private Independent _indFirstName = new Independent();
private Independent _indLastName = new Independent();

public string FirstName
{
get { _indFirstName.OnGet(); return _firstName; }
set { _indFirstName.OnSet(); _firstName = value; }
}

public string LastName
{
get { _indLastName.OnGet(); return _lastName; }
set { _indLastName.OnSet(); _lastName = value; }
}

public int DisplayStrategy
{
get { _indDisplayStrategy.OnGet(); return _displayStrategy; }
set { _indDisplayStrategy.OnSet(); _displayStrategy = value; }
}
// End generated code --------------------------------
#endregion
}
Then you wrap your object before giving it to the view:

public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = ForView.Wrap(new Person());
}
}
It even works through a view model:

public class PersonPresentation
{
private Person _person;

public PersonPresentation(Person person)
{
_person = person;
}

public Person Person
{
get { return _person; }
}

public string FirstLast
{
get { return _person.FirstName + " " + _person.LastName; }
}

public string LastFirst
{
get { return _person.LastName + ", " + _person.FirstName; }
}

public string Title
{
get { return "Person - " + (_person.DisplayStrategy == 0 ? FirstLast : LastFirst); }
}
}
Just wrap the view model:

public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = ForView.Wrap(new PersonPresentation(new Person()));
}
}
INotifyPropertyChanged is a problem that can just go away.

Sep 22, 2009 at 9:42 PM

Is it possible to list 2 or more properties that must have a change event?  I'm worried that the action the causes the properties to change will have to be "pumped" twice in order to test each property individually.

Thanks,

John

 

Sep 24, 2009 at 3:08 AM
Edited Sep 24, 2009 at 3:09 AM

I'm not sure I understand your question, or if you are asking about Update Controls in particular. I can think of three scenarios that you might be asking about.

Scenario 1: One dependent property depends upon two or more independent properties.

 

public string FullName
{
    get { return _person.FirstName + " " + _person.LastName; }
}

 

In this scenario, if you change either FirstName or LastName, FullName fires a property changed event. If you change both at once, then FullName only fires once.

Scenario 2: Two dependent properties depend upon the same independent property.

 

public Person SelectedPerson
{
    get { return _navigation.SelectedPerson; }
}

public bool IsPersonSelected
{
    get { return _navigation.SelectedPerson != null; }
}

 

When you change _navigation.SelectedPerson, both SelectedPerson and IsPersonSelected fire property changed events.

Scenario 3: A chain of intermediate dependents.

 

public string Title
{
    get { return "Person - " + FullName; }
}

This property depends upon the FullName property from scenario 1. In this scenario, changing first or last name (or both) causes both FullName and Title to fire property changed events. You do not have to change them twice to cause both events to fire.

 

I hope one of these scenarios is the one that concerns you. Update Controls just does the right thing in all three cases.

Nov 18, 2009 at 2:10 PM
Edited Nov 18, 2009 at 2:11 PM
MichaelLPerry1971 wrote:

Scenario 1: One dependent property depends upon two or more independent properties.

 

public string FullName
{
get { return _person.FirstName + " " + _person.LastName; }
}

 

In this scenario, if you change either FirstName or LastName, FullName fires a property changed event. If you change both at once, then FullName only fires once.

When you say 'at once' you are truly referring to the time frame used by the batched/deferred/asynchronous notification policy used in the Update Controls library. Depending on how 'close' such set operations are, there could be 2 notifications. Moreover, using INotifyPropertyChanged (or any other synchronous notification system) there are always 2 notifications.

Coordinator
Jul 27, 2014 at 10:50 AM
.
An updated version of this article can be found here: Implementing and usage of INotifyPropertyChanged
.
Marked as answer by jbe2277 on 7/27/2014 at 2:50 AM