ObservableObject - Is This The Proper Implementation of INotifyPropertyChanged?

|

I've not written any blog articles on WPF for a long time, and at the same time, I missed a lot of cool blog articles written by any WPF'ers too. So recently I spent some time reading those blogs published one or two months ago, and really got a lot of new and cool tricks and tips on WPF.

One of the article I always enjoy reading is Josh Smith's blog post on a base class which implements INotifyPropertyChanged and the those awesome comments following this post. Designing a base class for general data binding purpose is always a challenge, in particular, if you take into consideration things like multi-threading, and the various business scenarios and changing customer needs. I think that's why Microsoft doesn't come up with a default implementation of INotifyPropertyChanged class which developers can subclass to design their business entities. The following is my take on this challenge after reading Josh Smith's original article and those comments following it:

using System;
using System.Threading;
using System.Reflection;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;

namespace Sheva.Windows.Data
{
    /// <summary>
    /// This class acts as the base class for any data entity which provides notifications whenever any of its property is changed.
    /// </summary>
    public abstract class ObservableObject : INotifyPropertyChanged
    {
        #region Data & Constructors

        private const Int32 notificationDisabled = 0;
        private const Int32 notificationEnabled = 1;

        private const String Error_Msg = "{0} is not a public property of {1}";
        private const String Error_SuspendNotification = "Nested SuspendNotification is not supported";
        private const String Error_ResumeNotification = "ResumeNotification without first calling SuspendNotification is not supported";
        private Int32 objectState = notificationEnabled;

        protected ObservableObject()
        {
        }

        #endregion

        #region
Public Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Suspend the property changed notification.
        /// </summary>
        /// <remarks>
        /// After this call, the property changed notification is disabled.
        /// </remarks>
        public void SuspendNotification()
        {
            if (Interlocked.CompareExchange(ref objectState, notificationDisabled, notificationEnabled) != notificationEnabled)
            {
                throw new InvalidOperationException(Error_SuspendNotification);
            }
        }

        /// <summary>
        /// Resume the property changed notification.
        /// </summary>
        /// <remarks>
        /// After this call, the property changed notification is re-enabled.
        /// </remarks>
        public void ResumeNotification()
        {
            if (Interlocked.CompareExchange(ref objectState, notificationEnabled, notificationDisabled) != notificationDisabled)
            {
                throw new InvalidOperationException(Error_SuspendNotification);
            }
        }

        public Boolean IsNotificationEnabled
        {
            get { return Thread.VolatileRead(ref objectState) == 1; }
        }

        #endregion

        #region
Protected Members

        ///<summary>
        /// Invoked whenever the value of any property is changed.
        ///</summary>
        ///<remarks>
        /// Derived classes can override this method to include any logic after the property is changed.
        ///</remarks>
        /// <param name="propertyName">
        /// The name of the property which was changed.
        /// </param>
        protected virtual void OnPropertyChanged(String propertyName)
        {
            // Do nothing
        }

        ///<summary>
        ///Raise the property changed event.
        ///</summary>
        /// <param name="propertyName">
        /// The name of the property which was changed.
        /// </param>
        protected void RaisePropertyChangedEvent(String propertyName)
        {
            VerifyProperty(propertyName);
            if (Thread.VolatileRead(ref objectState) == notificationDisabled)
            {
                return;
            }

            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                // Raise the PropertyChanged event.
                handler(this, new PropertyChangedEventArgs(propertyName));
            }

            OnPropertyChanged(propertyName);
        }

        #endregion

        #region
Private Helpers

        [Conditional("DEBUG")]
        private void VerifyProperty(String propertyName)
        {
            Type type = GetType();
            PropertyInfo propertyInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);

            Debug.Assert(propertyInfo != null, String.Format(Error_Msg, propertyName, type.FullName));
        }

        #endregion
    }
}

Note that I don't cache the PropertyChangedEventArgs objects as Josh Smith did, properly his cache scheme can improve this performance a little bit, but I think in any interactive UI programming, that kinda performance gain doesn't make any signficant difference. as a bonus, I add two pair methods called SuspendNotification and ResumeNotification, this two methods can be pretty useful when you first initialize the business objects, at this instance, you don't want the UI or the change notification subscribers to be notified about the property change.

I've attached a sample project of how to use ObservableObject base class, you can download it from here:

Attachment: ObservableObjectDemo.zip

0 comments: