Tuesday, 27 January 2009

Implementing Observable Objects – Part II

In Part I we looked at a very straightforward implementation of the INotifyPropertyChanged interface to create an observable object. Unfortunately there aren’t many data models where one observable object with two properties will suffice J. This means you can find yourself repeating a lot of boilerplate code in each property setter. We can use the following base class to make implementing the property setters a little more trivial:

public abstract class ObservableItem : INotifyPropertyChanged
{
  protected ObservableItem()
    : base()
  {
  }

  public event PropertyChangedEventHandler PropertyChanged;

  protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
  {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
      handler(this, e);
    }
  }

  protected void SetProperty<T>(string propertyName, ref T field, T value)
  {
    SetProperty(propertyName, ref field, value, null);
  }

  protected void SetProperty<T>(
    string propertyName, ref T field, T value, IEqualityComparer<T> comparer)
  {
    if (comparer == null)
    {
      comparer = EqualityComparer<T>.Default;
    }
    if (!comparer.Equals(field, value))
    {
      field = value;
      OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }
  }
}

The SetProperty method compares the current value in a property’s backing field with the new value and raises the PropertyChanged event if the value has changed. In order to update the property’s backing field we have to pass it by reference. For those of you that make use of the code analysis support in Visual Studio, you’ll notice that the SetProperty method actually violates the DoNotPassTypesByReference rule. I think this is a justifiable violation given that it improves the maintainability of you observable objects, so I always add the following suppression:

[SuppressMessage(
  "Microsoft.Design", 
  "CA1045:DoNotPassTypesByReference", 
  Justification = 
    "This method performs the common functionality required for setting a " + 
    "property, which includes raising events. To set the value of the property's " + 
    "backing field, the backing field must be passed by reference.", 
  MessageId = "1#")]

Now we can re-write the Person class as follows:

public class Person : ObservableItem
{
  private string m_firstName;
  private string m_lastName;

  public string FirstName
  {
    get { return m_firstName; }
    set { SetProperty("FirstName", ref m_firstName, value, StringComparer.Ordinal); }
  }

  public string LastName
  {
    get { return m_lastName; }
    set { SetProperty("LastName", ref m_lastName, value, StringComparer.Ordinal); }
  }
}

The property setters now contain just one line of code but we're not done yet. A weakness of this approach is that with an unfortunate typo – an occupational hazard in software development J –or with some copy/paste abuse we can end up supplying the wrong property name to the SetProperty method. In Part III we’ll look at making the SetProperty method more robust to these mistakes.

0 comments:

Post a Comment