Implementing INotifyPropertyChanged with DynamicProxy2
22:30 and still working on various bits and bobs, so I thought I'd take a well deserved break to tell you about one nice way to implement INotifyPropertyChanged, the one and only interface any class that will be bound in WPF should be implemented.
I used to (circa .net 2) not see a bit issue in this kind of code.
public class MyClass : INotifyPropertyChanged
{
private string _myValue;
public event PropertyChangedEventHandler PropertyChanged;
public string MyValue
{
get
{
return _myValue;
}
set
{
_myValue = value;
RaisePropertyChanged("MyValue");
}
}
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
But I've been badly spoiled by automatic properties in C# 3.0, and this all sounds like a lot of noise. How great would it be if we could instead simply have the following.
public class MyDreamClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string MyValue { get; set; }
}
Definitly much cleaner. We're going to use DynamicProxy2, which is part of the Castle framework, and is used amongst other things in nhibernate.
The process is quite simple. You ask a ProxyGenerator to create a type for you, and pass it an interceptor object that will... Intercept any call!
First, let's clear up our class. Because the object generated by DP2 inherits from the original type, it needs to have a virtual modifier on the property. Furthermore, I wanted to add an attribute to specify which properties were to trigger the notification.
public class MyBetterClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[Notify]
public virtual string MyValue { get; private set; }
}
Now, let's see how we create the object.
static void Main(string[] args)
{
var proxy = new ProxyGenerator();
MyBetterClass myClass = proxy.CreateClassProxy<MyBetterClass>(new NotifyPropertyChangedInterceptor());
myClass.PropertyChanged += (src,prop) => Console.WriteLine(prop.PropertyName);
myClass.MyValue = "testValue";
}
Fairly simple so far. Now let's see the meat of the code, the interceptor.
public class NotifyAttribute : Attribute { }
public class NotifyPropertyChangedInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
// let the original call go through first, so we can notify *after*
invocation.Proceed();
if (invocation.Method.Name.StartsWith("set_"))
{
string propertyName = invocation.Method.Name.Substring(4);
var pi = invocation.TargetType.GetProperty(propertyName);
// check that we have the attribute defined
if (Attribute.GetCustomAttribute(pi, typeof(NotifyAttribute)) == null)
return;
// get the field storing the delegate list that are stored by the event.
FieldInfo info = invocation.TargetType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(f => f.FieldType == typeof(PropertyChangedEventHandler))
.FirstOrDefault();
if (info != null)
{
// get the value of the field
PropertyChangedEventHandler evHandler = info.GetValue(invocation.InvocationTarget) as PropertyChangedEventHandler;
// invoke the delegate if it's not null (aka empty)
if (evHandler != null)
evHandler.Invoke(invocation.TargetType, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And that's it. Get the field in which the event is stored and invoke it whenever a property has been modified.