Sunday, May 25, 2008

Automatic implementation of INotifyPropertyChanged

Recently I have read a couple of places that are toying with the idea of making property changed events a little bit easier. Serial Seb had some ideas and so has Paul Stovel. My take on it is if we can just decorate the Class or property with an attribute. Either
public class MyClass : INotifyPropertyChanged
{
    [Notify]
    public string MyProperty { get; set;}

    public PropertyChangedEventHandler PropertyChanged
}
or
[Notify]
public class MyClass : INotifyPropertyChanged
{
    public string MyProperty { get; set;}

    public PropertyChangedEventHandler PropertyChanged
}
Now that we have considered the desired outcome we can identify possible options. Both options that come to mind involve something more than just the .Net framework. One option is to use Injectors as Seb showed. My other option is to use PostSharp to inject the code at compile time. I might knock up some tests to see if there is any performance difference. [Update] Here is the code that I shamelessly stole from Seb. It is a post sharp implementation as we don't use Windsor on the project I am on. This code doesn't check if the value actually changed.
[Serializable]
[AttributeUsage(AttributeTargets.Assembly 
    | AttributeTargets.Class 
    | AttributeTargets.Struct 
    | AttributeTargets.Constructor 
    | AttributeTargets.Method 
    | AttributeTargets.Property 
    | AttributeTargets.Event, AllowMultiple = true, Inherited = false)]
public sealed class NotifyAspectAttribute : OnMethodBoundaryAspect
{
    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
        if (eventArgs == null)
            return;

        //Why are property sets not properties? they are methods?
        if (
            (eventArgs.Method.MemberType & System.Reflection.MemberTypes.Method)
            == System.Reflection.MemberTypes.Method
            &&
            eventArgs.Method.Name.StartsWith("set_")
            )
        {
            Type theType = eventArgs.Method.ReflectedType;
            string propertyName = eventArgs.Method.Name.Substring(4);

            // get the field storing the delegate list that are stored by the event.
            FieldInfo[] fields = theType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
            FieldInfo field = null;
            foreach (FieldInfo f in fields)
            {
                if(f.FieldType == typeof(PropertyChangedEventHandler))
                {
                    field = f;
                    break;
                }
            }

            if (field != null)
            {
                // get the value of the field
                PropertyChangedEventHandler evHandler = field.GetValue(eventArgs.Instance) as PropertyChangedEventHandler;

                // invoke the delegate if it's not null (aka empty)
                if (evHandler != null)
                    evHandler.Invoke(eventArgs.Instance, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

No comments: