When working on a project recently, the data saved back to the database was huge. We are talking well over 100 properties. After talking to my Java buddy, he said that one way is to create an IsDirty method that returns if the object has changed state.

The IsDirty() method is used to query the state of an object - if it has been modified or not. The implementation is up to the developer, but ultimately it's use can save you a trip to the data source.

I have split this example into 3 stages:

  1. First attempt
  2. Refactored
  3. Event Handled

N.B. This article helps someone in (archaic) .NET 2.0 - but if you've looking to develop for .NET 3.5, it might be worth looking into DependencyProperty's, which caters for this sort of problem.

First Attempt

The problem I found when prototyping this is that it can be quite impractical at first glance:

class Customer
{
    private string firstName, surname;
    public Customer(string firstName, string surname)
    {
        this.firstName = firstName;
        this.surname = surname;
    }

    public string FirstName
    {
        get { return firstName; }
        set 
        { 
            if( firstName != value )
            {
                IsDirty = true;
                firstName = value;
            }
        }
    }
    public string Surname
    {
        get { return surname; }
        set 
        { 
            if( surname != value )
            {
                IsDirty = true;
                surname = value;
            }
        }
    }

    public bool IsDirty { get; private set; }
}

 

This is a bit bloated. Also, it relies on every property having to check for itself whether it is equal or not, setting the IsDirty property and also assigning the value.

After writing this and implementing a few more properties I found this to be a bit too impractical. Instead, I realised that they are all basically doing 3 steps:

  1. Checking the previous value to the new value.
  2. Setting IsDirty.
  3. Assigning the new value.

This example needs simplifying - or refactoring to be more precise.

Refactoring

Instead, I decided to implement this behaviour in a method called SetValue<T>() , which would raise events when a property has changed. This allows the IsDirty property to be changed centrally and debugging to be centralized.

Firsty I created the method:

private void SetValue<T>(ref T varName, T value)
{
    if (varName.Equals(value))
        return;
    
    IsDirty = true;
    varName = value;
}

 

Now, we can change our properties to:

public string FirstName
{
    get {return firstName; }
    set
    {
        SetValue<string>(ref firstName,value);
    }
}

public string Surname
{
    get {return surname; }
    set
    {
        SetValue<string>(ref surname,value);
    }
}

This is one implementation of it, but I decided to extend it to event handling as well, so that even the outside world can be notified about what is changing.

Event Handling

I decided to also implement this is in a typical Microsoft fashion: OnPropertyChanging and OnPropertyChanged.

In order to turn this into an event driven model I need to do a few things:

  1. Create some event arguments describing what is being changed.
  2. Add some event handlers to trigger a notification that they have been changed.
  3. Trigger the events from within your code
  4. (Optionally) wire up the events.
  5. Trigger the events from within your code

Create event arguments

Since I am using events, I want to create event arguments detailing what has changed. I have implemented this in 2 identical classes (and 1 base class). Here I have implemented a base class, and 2 classes which improve the clarity of the base class. I have chosen clarity over code bloat:

//Base class implementation
public abstract class PropertyChangeEventArgs : EventArgs
{
    public string PropertyName { get; protected set; }
    protected object Value { get; set; }

    public PropertyChangeEventArgs(string propertyName, object before)
    {
        this.PropertyName = propertyName;
        this.Value = before;
    }
}

//Holds arguments before the proerty has changed
public class PropertyChangingEventArgs : PropertyChangeEventArgs
{
    public object ValueBeforeChange
    {
        get
        {
            return this.Value;
        }
    }

    public PropertyChangingEventArgs(string propertyName, object before) 
        : base(propertyName,before)
    {
    }
}

//Holds arguments after the proerty has changed
public class PropertyChangedEventArgs : PropertyChangeEventArgs
{
    public object ValueAfterChange
    { 
        get 
        { 
            return this.Value; 
        } 
    }

    public PropertyChangedEventArgs(string propertyName, object after) 
        : base(propertyName, after)
    {
    }
}

Create event handlers

Now implement some event handlers:

public event PropertyChangingEventHandler PropertyChanging;
public delegate void PropertyChangingEventHandler(object o, PropertyChangingEventArgs e);

public event PropertyChangedEventHandler PropertyChanged;
public delegate void PropertyChangedEventHandler(object o,PropertyChangedEventArgs e);

Trigger the events from within your code

Now I need to trigger these events from the SetValue<T>() method. I will also need to find out what property was called by this SetValue<T>() method.

private void SetValue<T>(T varName, T value)
{
       if (varName.Equals(value))
           return;

       //TODO: Find the property's name that is calling this method
       string propertyName = "MyProperty";

       if (PropertyChanging != null)
           OnPropertyChanging(this, new PropertyChangingEventArgs(propertyName, varName));

       varName = value;

       if (PropertyChanged != null)
           OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName, varName));
   }

The last thing to do is to find the property that is calling this method. This can be done using Reflection, by looking up the call stack. By getting the previous "frame", we can deduce where this is being called from. In our case, this is called "set_FirstName". Remove the "set_" and we have our property name:

StackTrace st = new StackTrace();
StackFrame current = st.GetFrame(1);
string propertyName = current.GetMethod().Name.Replace("set_", "");

(Optionally) wire up the events

I want to subscribe to the OnPropertyChanged event, so that I can set the IsDirty property within it. I have done this within a private constructor:

private Customer()
{
   PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}

public Customer(string firstName, string surname) : this()
{
   this.firstName = firstName;
   this.surname = surname;
}

(Optionally) trigger the events from within your code

Now, I want to move the IsDirty assignment out of the SetValue() method because although overkill, it is not related to the setting of the value. It is related to the event but not the assignment. It also means that you have just wasted 15 minutes reading this article! :-) :

private void OnPropertyChanged(object sender, PropertyChangedEventArgs ea)
{
   IsDirty = true;
}

Summary

So what have we done? We've looked at ways in which we can implement notification within a class, so that we can save a database access. When getting to a large number of properties, we can bypass each save and also allow the outside world to be notified of its changes.

In the first example,we saw a cheap and nasty way of doing it, but impractical for larger classes. In the second example we saw a better implementation and in the third example we added event handling to extend the functionality further.

I hope this article helps someone - but if you've looking to develop for .NET 3.5, it might be worth looking into DependencyProperty's, which caters for this sort of problem.

Here is the full source code to have a play with:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer c = new Customer("Dominic", "Zukiewicz");
            c.FirstName = "Dommy dom dom";

            Console.WriteLine("Customer.IsDirty = " + c.IsDirty);

            Customer c2 = new Customer("Dominic", "Zukiewicz");
            c.Surname = "Zukiewicz";

            Console.WriteLine("Customer2.IsDirty = " + c2.IsDirty);
        }
    }

    class Customer
    {
        private string firstName;
        private string surname;

        private Customer()
        {
            PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
        }

        public Customer(string firstName, string surname) : this()
        {
            this.firstName = firstName;
            this.surname = surname;
        }

        public string FirstName
        {
            get
            {
                return firstName;
            }
            set
            {
                SetValue(ref firstName, value);
            }
        }

        public string Surname
        {
            get 
            { 
                return surname; 
            }
            set
            {
                SetValue(ref surname,value);
            }
        }

        public bool IsDirty { get; private set; }


        private void SetValue(T varName, T value)
        {
            if (varName.Equals(value))
                return;

            StackTrace st = new StackTrace();
            StackFrame current = st.GetFrame(1);
            string propertyName = current.GetMethod().Name.Replace("set_", "");

            if (PropertyChanging != null)
                OnPropertyChanging(this, new PropertyChangingEventArgs(propertyName, varName));

            varName = value;

            if (PropertyChanged != null)
                OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName, varName));
        }

        public event PropertyChangingEventHandler PropertyChanging;
        public delegate void PropertyChangingEventHandler(object o, PropertyChangingEventArgs e);

        public event PropertyChangedEventHandler PropertyChanged;
        public delegate void PropertyChangedEventHandler(object o,PropertyChangedEventArgs e);

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs ea)
        {
            Debug.Print("Property: {0}, After: {1}", ea.PropertyName, ea.ValueAfterChange);
            IsDirty = true;
        }

        private void OnPropertyChanging(object sender, PropertyChangingEventArgs ea)
        {
            Debug.Print("Property: {0}, Before: {1}", ea.PropertyName, ea.ValueBeforeChange);
        }
        
    }

    //Base class implementation
    public abstract class PropertyChangeEventArgs : EventArgs
    {
        public string PropertyName { get; protected set; }
        protected object Value { get; set; }

        public PropertyChangeEventArgs(string propertyName, object before)
        {
            this.PropertyName = propertyName;
            this.Value = before;
        }
    }

    //Holds arguments before the proerty has changed
    public class PropertyChangingEventArgs : PropertyChangeEventArgs
    {
        public object ValueBeforeChange
        {
            get
            {
                return this.Value;
            }
        }

        public PropertyChangingEventArgs(string propertyName, object before) 
            : base(propertyName,before)
        {
        }
    }

    //Holds arguments after the proerty has changed
    public class PropertyChangedEventArgs : PropertyChangeEventArgs
    {
        public object ValueAfterChange
        { 
            get 
            { 
                return this.Value; 
            } 
        }

        public PropertyChangedEventArgs(string propertyName, object after) 
            : base(propertyName, after)
        {
        }
    }
}

Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist
posted @ Thursday, October 09, 2008 11:57 AM | in Framework

Comments

No comments posted yet.

Post Comment

Title *
Name *
Email
Url
Comment *  


Please add 6 and 1 and type the answer here: