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.

Ads

Thought of the evening

MSBuild + SqlCompare + DataContext == FluidDatabaseDesign

Ads

The tale of the 6.4 Megapixel desktop

Just upgraded my development environment, here's the picture.

013

The laptop looks tiny next to the screen, but it is in fact a high res MacBook Pro, 1920x1200, driving as a secondary screen an Apple Cinema Display 30" for an added 2560 x 1600.

That's freakin' 6.4 megapixels of vista goodness! Surprisingly enough, Aero runs without a hitch.

Couple of other interesting points: new apple keyboard as I fell in love with the feel of the MBA keyboard, the mouse is not the one i usually have (but ran out of batteries). And Prada sunglasses for the one day of sun we've had in London so far.

As for drinks, the coffee mug is the property of the client at which I keep this equipment, and the can of Redbull kinda explains the name behind my company name: caffeine IT :)

Ads