Click here to Skip to main content
13,055,408 members (50,446 online)
Click here to Skip to main content
Add your own
alternative version


31 bookmarked
Posted 26 Aug 2009

Almost-automatic INotifyPropertyChanged, automatic IsDirty, and automatic ChangeTracking

, 26 Aug 2009
Rate this:
Please Sign up or sign in to vote.
A refactor friendly implementation of INotifyProperty changed, without using Reflection.


WPF… it looks like I need to build workarounds for everything I’m trying to do. Prior to WPF, I rarely needed INotifyPropertyChanged.

But since we need it, why not make the best of it. Since I want a base class for all my future objects, in all my future projects I want something super clean.

  • Implement INotifyPropertyChanged in the best way possible (refactor friendly and optimal performance)
  • Automatically set the IsDirty flag
  • Automatic change tracking to be able to log what has been changed (like in SharePoint when you get alerts, I love that!)
  • The change tracking should also be exported as text so I can write it to SQL

Of course, I don’t have to tell you that the code must always ensure a minimal amount of work. And, it should be refactor friendly etc… I want perfect code.

The code

/// <summary>
/// This object automatically implements notify property changed
/// </summary>
public class NotifyPropertyChangeObject : INotifyPropertyChanged
    /// <summary>
    /// Track changes or not.
    /// If we're working with DTOs and we fill up the DTO
    /// in the DAL we should not be tracking changes.
    /// </summary>
    private bool trackChanges = false;
    /// <summary>
    /// Changes to the object
    /// </summary>
    public Dictionary<string, object> Changes { get; private set; }
    /// <summary>
    /// Is the object dirty or not?
    /// </summary>
    public bool IsDirty
        get { return Changes.Count > 0; }
        set { ; }
    /// <summary>
    /// Event required for INotifyPropertyChanged
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// This constructor will initialize the change tracking
    /// </summary>
    public NotifyPropertyChangeObject()
        // Change tracking default
        trackChanges = true;
        // New change tracking dictionary
        Changes = new Dictionary<string, object>();
    /// <summary>
    /// Reset the object to non-dirty
    /// </summary>
    public void Reset()
    /// <summary>
    /// Start tracking changes
    /// </summary>
    public void StartTracking()
        trackChanges = true;
    /// <summary>
    /// Stop tracking changes
    /// </summary>
    public void StopTracking()
        trackChanges = false;
    /// <summary>
    /// Change the property if required and throw event
    /// </summary>
    /// <param name="variable"></param>
    /// <param name="property"></param>
    /// <param name="value"></param>
    public void ApplyPropertyChange<T, F>(ref F field, 
                Expression<Func<T, object>> property, F value)
        // Only do this if the value changes
        if (field == null || !field.Equals(value))
            // Get the property
            var propertyExpression = GetMemberExpression(property);
            if (propertyExpression == null)
                throw new InvalidOperationException("You must specify a property");
            // Property name
            string propertyName = propertyExpression.Member.Name;
            // Set the value
            field = value;
            // If change tracking is enabled, we can track the changes...
            if (trackChanges)
                // Change tracking
                Changes[propertyName] = value;
                // Notify change
    /// <summary>
    /// Get member expression
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expression"></param>
    /// <returns></returns>
    public MemberExpression GetMemberExpression<T>(Expression<Func<T, 
                            object>> expression)
        // Default expression
        MemberExpression memberExpression = null;
        // Convert
        if (expression.Body.NodeType == ExpressionType.Convert)
            var body = (UnaryExpression)expression.Body;
            memberExpression = body.Operand as MemberExpression;
        // Member access
        else if (expression.Body.NodeType == ExpressionType.MemberAccess)
            memberExpression = expression.Body as MemberExpression;
        // Not a member access
        if (memberExpression == null)
            throw new ArgumentException("Not a member access", 
        // Return the member expression
        return memberExpression;
    /// <summary>
    /// The property has changed
    /// </summary>
    /// <param name="propertyName"></param>
    private void NotifyPropertyChanged(string propertyName)
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    /// <summary>
    /// Convert the changes to an XML string
    /// </summary>
    /// <returns></returns>
    public string ChangesToXml()
        // Prepare base objects
        XDeclaration declaration = new XDeclaration("1.0", 
                                       Encoding.UTF8.HeaderName, String.Empty);
        XElement root = new XElement("Changes");
        // Create document
        XDocument document = new XDocument(declaration, root);
        // Add changes to the document
        // TODO: If it's an object, maybe do some other things
        foreach (KeyValuePair<string, object> change in Changes)
            root.Add(new XElement(change.Key, change.Value));
        // Get the XML
        return document.Document.ToString();

Using the code

public class Person : NotifyPropertyChangeObject
    private string firstName;
    private string lastName;
    private int age;
    /// <summary>
    /// FirstName 
    /// </summary>
    public string FirstName
        get { return firstName; }
        set { ApplyPropertyChange<Person, 
              string>(ref firstName, o => o.FirstName, value); }
    /// <summary>
    /// LastName 
    /// </summary>
    public string LastName
        get { return lastName; }
        set { ApplyPropertyChange<Person, 
              string>(ref lastName, o => o.LastName, value); }
    /// <summary>
    /// Age 
    /// </summary>
    public int Age
        get { return age; }
        set { ApplyPropertyChange<Person, int>(ref age, o => o.Age, value); }

That’s it…

A little word about the code

  • Our class Person inherits from NotifyPropertyChangeObject.
  • When we change the value of a property, the magic happens.
  • We forward the private variable, the (refactor friendly) property, and the new value.
  • After that, we check if the ‘new’ value matches the old one. If this is the case, nothing should happen.
  • If the values do not match, we want to change the value and notify that a change has happened.
  • In the meanwhile, we also add that change to the Changes dictionary.
  • It’s safe to presume that if the dictionary contains changes, the object is dirty.

Finally, there are the methods Reset, StartTracking, and StopTracking. These have to do with the change tracking.

If I’m filling up a DTO in my DAL, I don’t want it to be marked as dirty. So before I start, I call StopTracking, and when I’m done, I call StartTracking.

Later on, if I save my object, I want it to be clean (not dirty), so I call the Reset method.

Finally, you could call ChangesToXml to get a string representation of all the changes. This could the be written to SQL or so...

Example application

At the top of the article, you can download a working project. I’ve also added an example for the cases where you cannot inherit from NotifyPropertyChangeObject, when you’re working with Entity Framework or LINQ to SQL for example…


This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


About the Author

Sandrino Di Mattia
Technical Lead RealDolmen
Belgium Belgium
I'm a Technical Consultant at RealDolmen, one of the largest players on the Belgian IT market:

All posts also appear on my blogs: and

You may also be interested in...

Comments and Discussions

Generalload This approach is not working for a list of 1 million objects with tracking enabled. And this approach doesn't support concurrent change of same property. Pin
Schizofreindly8110-Jun-15 5:07
memberSchizofreindly8110-Jun-15 5:07 
QuestionWrap a POCO instead Pin
AmazingFactory10-May-13 2:06
memberAmazingFactory10-May-13 2:06 
GeneralMy vote of 1 Pin
Member 466870517-Nov-09 9:45
memberMember 466870517-Nov-09 9:45 
QuestionPost Sharp? Pin
WillemToerien26-Aug-09 22:36
memberWillemToerien26-Aug-09 22:36 
AnswerRe: Post Sharp? [modified] Pin
Edgar Frank31-Aug-09 21:20
memberEdgar Frank31-Aug-09 21:20 
GeneralRe: Post Sharp? Pin
Sandrino Di Mattia1-Sep-09 7:10
memberSandrino Di Mattia1-Sep-09 7:10 
GeneralRe: Post Sharp? Pin
Edgar Frank1-Sep-09 22:07
memberEdgar Frank1-Sep-09 22:07 
GeneralRe: Post Sharp? Pin
Sandrino Di Mattia2-Sep-09 4:00
memberSandrino Di Mattia2-Sep-09 4:00 
GeneralGeneric base class Pin
Steve Hansen26-Aug-09 22:07
memberSteve Hansen26-Aug-09 22:07 
GeneralRe: Generic base class [modified] Pin
Sandrino Di Mattia1-Sep-09 7:12
memberSandrino Di Mattia1-Sep-09 7:12 
GeneralRe: Generic base class Pin
Steve Hansen1-Sep-09 7:17
memberSteve Hansen1-Sep-09 7:17 
GeneralAOP Pin
seesharper26-Aug-09 21:02
memberseesharper26-Aug-09 21:02 
GeneralAnti-patterns Pin
Dmitri Nesteruk26-Aug-09 19:20
memberDmitri Nesteruk26-Aug-09 19:20 
GeneralRe: Anti-patterns Pin
Sandrino Di Mattia1-Sep-09 7:19
memberSandrino Di Mattia1-Sep-09 7:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170713.1 | Last Updated 26 Aug 2009
Article Copyright 2009 by Sandrino Di Mattia
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid