I was in a devLINK session yesterday where the presenter was going through the very tedious implementation of INotifyOnPropertyChanged. I just had to go and tweet a plug for PostSharp and how this is the perfect application for AOP. Well, someone saw my tweet and asked me for examples.
There’s some aspects in Nvigorate that, as a bonus, happened to implement INotifiyOnPropertyChanged and INotifyOnPropertyChanging. Since that code does a lot of other things, I felt like I should strip the code down to just what it takes to implement INotifyOnPropertyChanged and INotifyOnPropertyChanging interfaces through AOP.
The end result of using this code will be that you can add support for both these interfaces by simply adding the attribute for the aspect in question to the top of any class:
[NotifierAspect]
public class SomeClassOfMine
{
...
}
Simple right? Definitely worlds better than putting that same tedious code into every stinking setter you care about. Now, for the disclaimer. I did some simple unit tests on this code and made a drop-dead simple WPF window to see if this all seems to work without a lot of additional casting or overhead on the consumers part. It all appears to work just fine without casting to the interfaces as you’re setting the data context. The other half of the disclaimer is you're using this code at your own risk and it’s absolutely not perfect or even as nice as it could be (i.e. you could test to see if the value was actually changing before firing off the events?) Think of this code as more of a starting point than your destination and you should be just fine. I hope it helps.
So here’s the code. if you’re unfamiliar with PostSharp, this is definitely going to look odd, but you should check out the forums and learn what you can about AOP … and also support PostSharp anyway you can. Gael Fraiteur is a genius : ) If you see room for improvements, please share them, I’m definitely interested in better ways to solve this problem.
// this interface is required for the PostSharp composition
// we will dynamically add the concrete implementation
// through the use of an aspect
public interface INotifyOnChange : INotifyPropertyChanged, INotifyPropertyChanging
{
void RaisePropertyChanged(object instance, string property);
void RaisePropertyChanging(object instance, string property);
}
// this is the concrete implementation of the INotifyOnChange
// interface which will be used in the actual aspect
[Serializable]
public class NotifierCompositeAdvice : INotifyOnChange
{
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangingEventHandler PropertyChanging;
public virtual void RaisePropertyChanging(object instance, string property)
{
if (PropertyChanging != null)
PropertyChanging(instance, new PropertyChangingEventArgs(property));
}
public virtual void RaisePropertyChanged(object instance, string property)
{
if (PropertyChanged != null)
PropertyChanged(instance, new PropertyChangedEventArgs(property));
}
}
[MulticastAttributeUsage(MulticastTargets.Class)]
public class NotifierAspect : CompoundAspect
{
public override void ProvideAspects(object element, LaosReflectionAspectCollection collection)
{
Type targetType = element as Type;
AddPropertyChangeAdvice(collection, targetType);
AddINotifyOnPropertyChangeAdvice(collection, targetType);
}
protected void AddPropertyChangeAdvice(LaosReflectionAspectCollection collection, Type targetType)
{
var advice = new OnPropertySetAspect();
foreach (PropertyInfo property in targetType.GetProperties())
{
MethodInfo setter = property.GetSetMethod();
if (setter != null)
{
if (!setter.IsStatic)
{
//Writes the property and class names that the notify aspect is being applied to to the console
//during build to make it simpler to determine which properties are being tracked.
Console.WriteLine("Applying Notifier Aspect to property {0} on type {1}", property.Name, targetType.FullName);
collection.AddAspect(setter, advice);
}
}
}
}
protected void AddINotifyOnPropertyChangeAdvice(LaosReflectionAspectCollection collection, Type targetType)
{
var aspect = new AddINotifyOnChangeAspect();
collection.AddAspect(targetType, aspect);
}
public NotifierAspect()
{
}
[Serializable]
internal class AddINotifyOnChangeAspect : CompositionAspect
{
public override Type GetPublicInterface(Type containerType)
{
return typeof(INotifyOnChange);
}
public override object CreateImplementationObject(InstanceBoundLaosEventArgs eventArgs)
{
return new NotifierCompositeAdvice();
}
public override CompositionAspectOptions GetOptions()
{
return
CompositionAspectOptions.GenerateImplementationAccessor |
CompositionAspectOptions.IgnoreIfAlreadyImplemented;
}
}
[Serializable]
internal class OnPropertySetAspect : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
try
{
INotifyOnChange notifier = eventArgs.Instance as INotifyOnChange;
string propertyName = eventArgs.Method.Name.Replace("set_", "");
notifier.RaisePropertyChanging(eventArgs.Instance, propertyName);
base.OnEntry(eventArgs);
notifier.RaisePropertyChanged(eventArgs.Instance, propertyName);
}
catch (Exception ex)
{
base.OnEntry(eventArgs); // we want to make certain our aspect doesn't break setters
}
}
}
}
Tags: