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.
public class Person : INotifyPropertyChanged
{
private string firstName;
private string lastName;
public string FirstName
{
get { return firstName; }
set
{
firstName = value;
FirePropertyChanged("FirstName");
FirePropertyChanged("Name");
}
}
public string LastName
{
get { return lastName; }
set
{
lastName = value; FirePropertyChanged("LastName");
FirePropertyChanged("Name");
}
}
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
}
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
[Notifying]
public class Person : INotifyPropertyChanged
{
private string firstName;
private string lastName;
public string FirstName
{
get { return firstName; }
[NotifyingDependency(DependencyProperty = "Name")]
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
[NotifyingDependency(DependencyProperty = "Name")]
set { lastName = value; }
}
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;
public override void CompileTimeInitialize(System.Reflection.MethodBase method)
{
Attribute[] atts = Attribute.GetCustomAttributes(method,
typeof(NotifyingDependencyAttribute));
int attCount = atts.Count();
this.propertyNames = new string[attCount + 1];
this.propertyNames[0] = GetPropertyNameFromMethodName(method.Name);
for (int i = 0; i < attCount; i++)
{
string depProp = (atts[i] as NotifyingDependencyAttribute).DependencyProperty;
this.propertyNames[i + 1] = (atts[i] as
NotifyingDependencyAttribute).DependencyProperty;
}
}
public override bool CompileTimeValidate(System.Reflection.MethodBase method)
{
if (method.DeclaringType.GetInterface(typeof(INotifyPropertyChanged).Name) != null)
{
if (method.Name.StartsWith("set_") && method.IsSpecialName)
{
Attribute[] atts = Attribute.GetCustomAttributes(method,
typeof(NotifyingIgnoreAttribute));
return atts.Count() == 0;
}
}
else
{
PostSharp.Extensibility.Message error =
new PostSharp.Extensibility.Message(SeverityType.Error,
"AOP0001", method.DeclaringType.ToString() +
" has to implement INotifyPropertyChanged", "#");
MessageSource.MessageSink.Write(error);
}
return false;
}
public override void OnExit(MethodExecutionEventArgs eventArgs)
{
this.typeFieldInfo = eventArgs.Instance.GetType()
.GetFields(BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public)
.Where(f => f.FieldType ==
typeof(PropertyChangedEventHandler))
.FirstOrDefault();
FireNotify(eventArgs.Instance);
}
private void FireNotify(object Target)
{
if (this.typeFieldInfo != null)
{
PropertyChangedEventHandler evHandler =
typeFieldInfo.GetValue(Target) as PropertyChangedEventHandler;
if (evHandler != null)
{
foreach (string prop in propertyNames)
{
evHandler.Invoke(Target, new PropertyChangedEventArgs(prop));
}
}
}
}
private string GetPropertyNameFromMethodName(string methodName)
{
return methodName.Substring(4);
}
}
NotifyingDependencyAttribute
[Serializable]
public class NotifyingDependencyAttribute : OnMethodBoundaryAspect
{
public string DependencyProperty { get; set; }
public override bool CompileTimeValidate(MethodBase method)
{
if (!PropertyHelper.PropertyExists(method.DeclaringType, DependencyProperty))
{
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.
[Serializable]
public class NotifyingIgnoreAttribute : Attribute { }
HelpClass
public class PropertyHelper
{
public static bool PropertyExists(Type t, string propertyName)
{
return t.GetProperty(propertyName) != null;
}
}