AOP Implementation of INotifyPropertyChanged
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.
/// <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
'.
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.
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
/// <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:
[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.
[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.
[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
[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
/// <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.
/// <summary>
/// Aspect to except Setters of Properties of Classes marked with [Notifying]
/// </summary>
[Serializable]
public class NotifyingIgnoreAttribute : Attribute { }
HelpClass
public class PropertyHelper
{
public static bool PropertyExists(Type t, string propertyName)
{
return t.GetProperty(propertyName) != null;
}
}