Click here to Skip to main content
15,868,005 members
Articles / Programming Languages / C#

AOP Implementation of INotifyPropertyChanged

Rate me:
Please Sign up or sign in to vote.
2.25/5 (7 votes)
11 Apr 2009CPOL2 min read 48.3K   245   17   10
How to implement the INotifyPropertyChanged interface, for lazy people :)

Introduction

This article describes how to implement the INotifyPropertyChanged interface with AOP using Postsharp.

Background

On attempting to implement the INotifyPropertyChanged interface, you'll most likely become frustrated within a short time, just because you have to do the same steps for each class.

Possible solutions (from bad to good)

Copy & Paste

OK, copy & paste will work here, but this will produce very high redundancy.

Example

We will take a look at this example at every solution.

C#
/// <summary>
/// A Person-Class
/// </summary>
public class Person : INotifyPropertyChanged
{
    private string firstName;
    private string lastName;

    /// <summary >
    /// Firstname of the Person
    /// </summary >
    public string FirstName
    {
        get { return firstName; }
        set
        {
            firstName = value;
            FirePropertyChanged("FirstName");
            FirePropertyChanged("Name");
        }
    }

    /// <summary >
    /// Lastname of the Person
    /// </summary >
    public string LastName
    {
        get { return lastName; }
        set
        {
            lastName = value; FirePropertyChanged("LastName");
            FirePropertyChanged("Name");
        }
    }

    /// <summary >
    /// Complete Name
    /// </summary >
    public string Name
    {
        get
        {
            return FirstName + " " + LastName;
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    public void FirePropertyChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
        }
    }

    #endregion
}

Here, you can see the standard way of implementing the INotifyPropertyChanged interface.

Remove redundancy

As you can see, the first solution causes a high redundancy. Now, we realize the similarity and implement an abstract class.

Example

Here, we implement the INotifyPropertyChanged members and our Help-function 'FirePropertyChanged' in an abstract class, and inherit our Person class from this base class 'NotifyingObject'.

C#
public abstract class NotifyingObject : INotifyPropertyChanged
{
    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    public void FirePropertyChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged.Invoke(this, 
              new PropertyChangedEventArgs(propName));
        }
    }

    #endregion
}

/// <summary >
/// A Person-Class
/// </summary >
public class Person : NotifyingObject
{ /*...*/ }

Now, you might say "That's it!". Yes, of course. It's a nice way to implement the interface. But in C#, there is no multiple inherency.

C#
public class xyz{}
public class Person : NotifyingObject, xyz {}

The code above will not be compiled!

The AOP way

In this step, we transform our base class 'NotifyingObject' into an Aspect. After this step, your code will look like this:

Example

C#
/// <summary >
/// A Person-Class
/// </summary >
[Notifying]
public class Person : INotifyPropertyChanged
{
    private string firstName;
    private string lastName;

    /// <summary >
    /// Firstname of the Person
    /// </summary >
    public string FirstName
    {
        get { return firstName; }
        [NotifyingDependency(DependencyProperty = "Name")]
        set { firstName = value; }
    }

    /// <summary >
    /// Lastname of the Person
    /// </summary >
    public string LastName
    {
        get { return lastName; }
        [NotifyingDependency(DependencyProperty = "Name")]
        set { lastName = value; }
    }

    /// <summary >
    /// Complete Name
    /// </summary >
    public string Name
    {
        get { return FirstName + " " + LastName; }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

Now, let's take a closer look at a few lines:

C#
[Notifying]
public class Person : INotifyPropertyChanged

This attaches the NotifyingAttribute to the set_ method from every property of the declaring class. The notification will be fired when this method is exited. The NotifyingAttribute can only be attached to classes which implement the INotifyPropertyChanged interface.

C#
[Notifying]
public class Person

The code above will cause a Postsharp error which looks like this: PostSharp: WindowsFormsApplication_3._5.Person has to implement INotifyPropertyChanged.

C#
[NotifyingDependency(DependencyProperty = "Name")]

The Name property depends on FirstName and LastName. The notification will be fired if one of these properties get changed. The property with the passed name has to exist, else you will see a Postshar error which looks like this: PostSharp: WindowsFormsApplication_3._5.Person has no Property 'ThirdName'.

Code listing

NotifyingAttribute

C#
[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method)]
public class NotifyingAttribute : OnMethodBoundaryAspect
{
    private string[] propertyNames;
    private FieldInfo typeFieldInfo;

    /// <summary>
    /// Do as much work as possible at the Compiletime
    /// see: http://doc.postsharp.org/1.5/ClassRef/html/
    //             fbc0e32a-2617-c9ef-cb9e-45cdc171dde0.htm
    /// </summary>
    public override void CompileTimeInitialize(System.Reflection.MethodBase method)
    {
        // get the DependencyAttributes of the method
        Attribute[] atts = Attribute.GetCustomAttributes(method, 
                             typeof(NotifyingDependencyAttribute));

        int attCount = atts.Count();

        this.propertyNames = new string[attCount + 1];

        // first Fired = Property
        this.propertyNames[0] = GetPropertyNameFromMethodName(method.Name);

        // Add the dependency properties
        for (int i = 0; i < attCount; i++)
        {
            string depProp = (atts[i] as NotifyingDependencyAttribute).DependencyProperty;

            this.propertyNames[i + 1] = (atts[i] as 
                 NotifyingDependencyAttribute).DependencyProperty;
        }
    }

    /// <summary>
    /// Validating the Usage of NotifyingAttribute
    /// see: http://doc.postsharp.org/1.5/ClassRef/html/
    ///            aa77e517-2c98-ebec-c0e1-6bb20d013c33.htm
    /// </summary>
    public override bool CompileTimeValidate(System.Reflection.MethodBase method)
    {
        // check on implemented INotifyPropertyChanged
        if (method.DeclaringType.GetInterface(typeof(INotifyPropertyChanged).Name) != null)
        {
            // set_* are of interest
            // IsSpecialName checked to avoid attaching to something
            // like this: public void set_FooBar()
            // Note:
            // If somebody knows a Method, which set IsSpecialName
            // to true (without property setters)
            // -> leve a comment. thanks!
            if (method.Name.StartsWith("set_") && method.IsSpecialName)
            {
                // check if NotifyingIgnoreAttribute is set
                Attribute[] atts = Attribute.GetCustomAttributes(method, 
                                          typeof(NotifyingIgnoreAttribute));
                return atts.Count() == 0;
            }
        }
        else
        {
            // Create Postsharp Error
            PostSharp.Extensibility.Message error = 
              new PostSharp.Extensibility.Message(SeverityType.Error, 
              "AOP0001", method.DeclaringType.ToString() + 
              " has to implement INotifyPropertyChanged", "#");

            MessageSource.MessageSink.Write(error);
        }
        return false;
    }

    /// <summary>
    /// Callback which is called after executing setter-code
    /// see: http://doc.postsharp.org/1.5/ClassRef/html/
    ///            4d72f4ea-11c5-cbce-b56b-b671c98cffaf.htm
    /// </summary>
    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
        // get Fieldinfo of event PropertyChangedEventHandler PropertyChanged 
        this.typeFieldInfo = eventArgs.Instance.GetType()
                                .GetFields(BindingFlags.Instance | 
                                           BindingFlags.NonPublic | 
                                           BindingFlags.Public)
                                .Where(f => f.FieldType == 
                                            typeof(PropertyChangedEventHandler))
                                .FirstOrDefault();

        // fire the Notify on the invoking Instance
        FireNotify(eventArgs.Instance);
    }

    /// <summary>
    /// Fire the Notifies on the invoking Instance
    /// </summary>
    /// <param name="Target">invoking Instance</param>
    private void FireNotify(object Target)
    {
        // Fieldinfo of PropertyChanged found
        if (this.typeFieldInfo != null)
        {
            // get the value of PropertyChanged
            PropertyChangedEventHandler evHandler = 
               typeFieldInfo.GetValue(Target) as PropertyChangedEventHandler;

            if (evHandler != null)
            {
                foreach (string prop in propertyNames)
                {
                    // invoke the event
                    evHandler.Invoke(Target, new PropertyChangedEventArgs(prop));
                }
            }
        }
    }

    /// <summary>
    /// Gets the Property Name from a setter method name
    /// </summary>
    /// <param name="methodName">setter method name</param>
    /// <returns>Property Name</returns>
    private string GetPropertyNameFromMethodName(string methodName)
    {
        return methodName.Substring(4);
    }
}

NotifyingDependencyAttribute

C#
/// <summary>
/// Aspect for defining notify dependencies
/// </summary>
[Serializable]
public class NotifyingDependencyAttribute : OnMethodBoundaryAspect
{
    public string DependencyProperty { get; set; }

    /// <summary>
    /// Validating the Usage of NotifyingDependencyAttribute
    /// see: http://doc.postsharp.org/1.5/ClassRef/html/
    ///           aa77e517-2c98-ebec-c0e1-6bb20d013c33.htm
    /// </summary>
    public override bool CompileTimeValidate(MethodBase method)
    {
        // Dependency Property must exists
        if (!PropertyHelper.PropertyExists(method.DeclaringType, DependencyProperty))
        {
            // Create Postsharp Error
            PostSharp.Extensibility.Message error = 
              new PostSharp.Extensibility.Message(SeverityType.Error, 
              "AOP0002", method.DeclaringType.ToString() + 
              " has no Property '" + DependencyProperty + 
              "'", "#");

            MessageSource.MessageSink.Write(error);
            return false;
        }
        return true;
    }
}

NotifyingDependencyAttribute

This class can be used to accept properties from the notifying object.

C#
/// <summary>
/// Aspect to except Setters of Properties of Classes marked with [Notifying]
/// </summary>
[Serializable]
public class NotifyingIgnoreAttribute : Attribute { }

HelpClass

C#
public class PropertyHelper
{
    public static bool PropertyExists(Type t, string propertyName)
    {
        return t.GetProperty(propertyName) != null;
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
university
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 1 Pin
surajfrommumbai12-Apr-09 18:18
surajfrommumbai12-Apr-09 18:18 
This is a example of postsharp

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.