Monday, June 30, 2014

Implementing notify property changed with SheepAspect

Introduction

I spend some time looking for AOP options for .NET.  All references point to PostSharp the coolest option - even when you have to pay for use it - due by its features, starting from the way it works – in build time and static – plus the lot of build-in “advices” and extensions points.

There are also several run time and dynamic options such as Castle, Unity, etc., but I prefer the build time and static approach.  Indeed I use Fody - as alternative to PostSharp - even when I have to write down custom plugins directly in IL.

But all of these AOP like libraries and tools for .NET do not allow you handle exactly the all the AOP concepts such as pointcut, join-point and advice.

But recently I found a project in codeplex, named SheepAspect with an introduction statement that include the following words “…was inspired in AspectJ”. So, I just installed the package from NuGet and started to write in C# a notify property changed proof of concept with a friend of mine (Leandro).

Introducing NotifyPropertyChanged aspect

The very first step is write a NotifyPropertyChangedAspect class. It’s important get related with SAQL in order to query the right properties from the right types, and with the SheepAspect attributes usage. For this particular example the pointcut includes “all public property setters of types that implements System.ComponentModel.INotifyPropertyChanged” and look like this:
[Aspect]
public class NotifyPropertyChangedAspect
{
    [SelectTypes("ImplementsType:'System.ComponentModel.INotifyPropertyChanged'")]
    public void NotifiedPropertyChangedTypes()
    {
    }

    [SelectPropertyMethods("Public & Setter & InType:AssignableToType:@NotifiedPropertyChangedTypes")]
    public void PublicPropertiesOfTypesThatImplementsINotifyPropertyChangedInterfacePointCut()
    {
    }
}

Now, I just needed to add the notify property changed behavior as around advice just as follow:

[Around("PublicPropertiesOfTypesThatImplementsINotifyPropertyChangedInterfacePointCut")]
public void AdviceForPublicPropertiesOfTypesThatImplementsINotifyPropertyChangedInterface(PropertySetJointPoint jp)
{
    object value = jp.Property.GetValue(jp.This);
    if (!object.Equals(value, jp.Value))
    {
        jp.Proceed();
        jp.This.RaiseNotifyPropertyChanged(jp.Property);
    }
}

As you should notice, to implement this, I also introduce a couple of extension methods TryGetPropertyChangedField and of course the RaiseNotifyPropertyChanged itself:

public static bool TryGetPropertyChangedField(this Type type, out FieldInfo propertyChangedEvent)
{
    propertyChangedEvent = null;
    while (propertyChangedEvent == null && type != null && type != typeof(object))
    {
        propertyChangedEvent = type.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
        if (propertyChangedEvent == null)
        {
            type = type.BaseType;
        }
        else if (!typeof(MulticastDelegate).IsAssignableFrom(propertyChangedEvent.FieldType))
        {
            propertyChangedEvent = null;
        }
    }

    return propertyChangedEvent != null;
}

/*...*/

public static void RaiseNotifyPropertyChanged(this object instance, PropertyInfo property)
{
    FieldInfo propertyChangedEvent;
    if (instance.GetType().TryGetPropertyChangedField(out propertyChangedEvent))
    {
        var propertyChangedEventMulticastDelegate = (MulticastDelegate)propertyChangedEvent.GetValue(instance);
        var invocationList = propertyChangedEventMulticastDelegate.GetInvocationList();
                
        foreach (var handler in invocationList)
        {
            MethodInfo methodInfo = handler.GetMethodInfo();
            methodInfo.Invoke(handler.Target, new[] { instance, new PropertyChangedEventArgs(property.Name) });
        }
    } 
}

Now, if a class that implements or inherits from a class that implements INotifyPropertyChanged interface is added, just like this one:

public class Person : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string FullName
        {
            get
            {
                return string.Format(CultureInfo.InvariantCulture, "{0} {1}", this.FirstName, this.LastName).Trim();
            }
        }
}

and a program like this one is written:

this.person.PropertyChanged += (sender, args) =>
                {
                    object value = sender.GetType().GetProperty(args.PropertyName).GetValue(sender);
                    Console.WriteLine("Property Changed => '{0}' = '{1}'", args.PropertyName, value);
                };

this.person.FirstName = "Igr Alexánder";
this.person.LastName = "Fernández Saúco";

then the output will be:

Property Changed => 'FirstName' = 'Igr Alexánder'
Property Changed => 'LastName' = 'Fernández Saúco'

Uhmm! But what about with the computed read-only properties like FullName?

Notifying property changes of computed readonly properties.

In order to notify changes of computed read-only properties an inspection of the IL code is required. The .NET native reflection API is limited. From the PropertyInfo is only possible get the IL byte array from of the get method body, and nothing more. Therefore, I just switched to the Mono.Cecil reflection API to be able to complement the existing RaiseNotifyPropertyChanged extension method with the following extension methods:

public static bool ExistPropertyDependencyBetween(this Type type, PropertyInfo dependentProperty, PropertyInfo propertyInfo)
{
    AssemblyDefinition assemblyDefinition = new DefaultAssemblyResolver().Resolve(type.Assembly.FullName);
    TypeDefinition typeDefinition = assemblyDefinition.MainModule.GetType(type.FullName);
    PropertyDefinition dependentPropertyDefinition = typeDefinition.Properties.FirstOrDefault(definition => definition.Name == dependentProperty.Name);
    
    bool found = false;
    if (dependentPropertyDefinition != null)
    {
        MethodDefinition definition = dependentPropertyDefinition.GetMethod;
        if (definition.HasBody)
        {
            ILProcessor processor = definition.Body.GetILProcessor();

            int idx = 0;
            while (!found && idx < processor.Body.Instructions.Count)
            {
                Instruction instruction = processor.Body.Instructions[idx];
                
                MethodDefinition methodDefinition;
                if (instruction.OpCode == OpCodes.Call && (methodDefinition = instruction.Operand as MethodDefinition) != null && methodDefinition.DeclaringType.IsAssignableFrom(typeDefinition) && methodDefinition.Name == string.Format(CultureInfo.InvariantCulture, "get_{0}", propertyInfo.Name))
                {
                    found = true;
                }
                else
                {
                    idx++;
                }
            }
        }
    }

    return found;
}

/*...*/

public static IEnumerable<PropertyInfo> GetDependentPropertiesFrom(this Type type, PropertyInfo property)
{
    List<PropertyInfo> dependentPropertyInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(dependentProperty => property != dependentProperty && dependentProperty.CanRead && type.ExistPropertyDependencyBetween(dependentProperty, property)).ToList();
    for (int i = 0; i < dependentPropertyInfos.Count; i++)
    {
        foreach (PropertyInfo info in type.GetDependentPropertiesFrom(dependentPropertyInfos[i]))
        {
            if (!dependentPropertyInfos.Contains(info))
            {
                dependentPropertyInfos.Add(info);
            }
        }
    }

    return dependentPropertyInfos;
}
just like this:
public static void RaiseNotifyPropertyChanged(this object instance, PropertyInfo property)
{
    FieldInfo propertyChangedEvent;
    if (instance.GetType().TryGetPropertyChangedField(out propertyChangedEvent))
    {
        var propertyChangedEventMulticastDelegate = (MulticastDelegate)propertyChangedEvent.GetValue(@this);
        var invocationList = propertyChangedEventMulticastDelegate.GetInvocationList();
                
        foreach (var handler in invocationList)
        {
            MethodInfo methodInfo = handler.GetMethodInfo();
            methodInfo.Invoke(handler.Target, new[] { instance, new PropertyChangedEventArgs(property.Name) });
        }

        foreach (PropertyInfo propertyInfo in instance.GetType().GetDependentPropertiesFrom(property))
        {
            foreach (var handler in invocationList)
            {
                MethodInfo methodInfo = handler.GetMethodInfo();
                methodInfo.Invoke(handler.Target, new[] { instance, new PropertyChangedEventArgs(propertyInfo.Name) });
            }
        }
    }
}

So, now the program output is:

Property Changed => 'FirstName' = 'Igr Alexánder'
Property Changed => 'FullName' = ' Igr Alexánder' 
Property Changed => 'LastName' = 'Fernández Saúco'
Property Changed => 'FullName' = ' Igr Alexánder Fernández Saúco'

Conclusion

At this point you should be worry about the performance and the lot of reflection API calls. I'm pretty sure that this issue could be handle with a right caching approach ;).

I didn’t know why I never heard about SheepAspect before. Probably no one trust in something called “Sheep”, but believe me SheepAspect rocks!

Wait a second! Right now I'm reading about something called mixing. Probably I can get a more declarative approach to implement this, just like the Fody or PostSharp home page examples. But I'm not sure right now, so, you have to wait for my next post to know about it ;).

No comments:

Post a Comment