Compound Aspect Sample

Open this sample in Visual Studio

The PostSharp.Samples.Binding project demonstrates how to combine many sub-aspects in a single custom attribute using the CompoundAttribute mechanism.

As an example, we tried to implement automatically interfaces used in data binding:

The NotifyPropertyChanged Aspect

What we want to achieve is 'simply' to implement automatically the following interface:
public interface INotifyPropertyChanged
{
  event PropertyChangedEventHandler PropertyChanged;
}

The solution is in three steps:

  1. Implementing the INotifyPropertyChanged interface.
  2. Modifying property accessors so that they invoke the PropertyChanged event of INotifyPropertyChanged when the value changes.
  3. Assembling sub-aspects into a compound aspect.

Implementing INotifyPropertyChanged

We will implement the INotifyPropertyChanged interface by composition. That is, we will develop a class (NotifyPropertyChangedImplementation) that will implement INotifyPropertyChanged. This class will be composed into the aspected class (say that we the Customer class to be observable; we call this class the aspected class). The INotifyPropertyChanged interface would be exposed by the aspected class, but the implementation would be delegated to the class NotifyPropertyChangedImplementation. Note that this class is purely runtime. It is never instantiated at compile time.

private class NotifyPropertyChangedImplementation : INotifyPropertyChanged
{
    private object instance;

    public NotifyPropertyChangedImplementation(object instance)
    {
        this.instance = instance;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this.instance, new PropertyChangedEventArgs(propertyName));
        }
    }
}

The code is annoyingly classic.

Class composition is supported in PostSharp Laos by the CompositionAspect class (or the ICompositionAspect interface, if you prefer). We derive from it the new class AddNotifyPropertyChangedInterfaceSubAspect:

[Serializable]
private class AddNotifyPropertyChangedInterfaceSubAspect : CompositionAspect
{
    public override object CreateImplementationObject(InstanceBoundLaosEventArgs eventArgs)
    {
        return new NotifyPropertyChangedImplementation(eventArgs.Instance);
    }

    public override Type GetPublicInterface(Type containerType)
    {
        return typeof(INotifyPropertyChanged);
    }

    public override CompositionAspectOptions GetOptions()
    {
        return CompositionAspectOptions.GenerateImplementationAccessor | CompositionAspectOptions.IgnoreIfAlreadyImplemented;
    }
}

The GetPublicInterface method is called at compile time. It returns which interface should be exposed on the aspected type. The CreateImplementationObject is called at runtime to create the object that will actually implement the interface. We create an instance of our NotifyPropertyChangedImplementation.

Modifying property accessors

In order to modify the write accessor of properties, we derive a new OnPropertySetSubAspect class from OnMethodBoundaryAspect and we simply override the OnSuccess method so that it calls the OnPropertyChanged method of our NotifyPropertyChangedImplementation. Seems pretty easy.

But how to get access to the OnPropertyChanged method? One solution would be to add this method in a public interface derived from INotifyPropertyChanged, and expose this interface. We would just have to cast the property instance and call the OnPropertyChanged method. But it is not very safe: everyone could raise this event.

In order to address this issue, PostSharp Laos is able to expose the implementation of a composed interface. When requested, Laos will implement the IComposed interface (in our case IComposed<INotifyPropertyChanged>) on the aspected type. The way to request it is to return the GenerateImplementationAccessor flag from the GetOptions method of the CompositionAspect (see above). This interface defines the method GetImplementation, which retrieve obviously the object implementing the composed interface, in our case NotifyPropertyChangedImplementation.

This approach is more secure for two reasons. First, the implementation class does not have to be public. It can be nested private or internal. Additionally, this method requires credentials, named instance credentials. If you don't have them, you cannot get the implementation. All aspects related to this instance will get its credentials through the event arguments. That is, the instance and its aspects form a 'family' inside which exists a trust relationship, which justifies instance credentials to be passed to all members of this family.

You will see that the implementation will be shorter than the explanation:

[Serializable]
private class OnPropertySetSubAspect : OnMethodBoundaryAspect
{
string propertyName;

public OnPropertySetSubAspect( string propertyName, NotifyPropertyChangedAttribute parent )
{
this.AspectPriority = parent.AspectPriority;
this.propertyName = propertyName;
}

public override void OnSuccess(MethodExecutionEventArgs eventArgs)
{
NotifyPropertyChangedImplementation implementation =
(NotifyPropertyChangedImplementation) ((IComposed<INotifyPropertyChanged>)eventArgs.Instance). GetImplementation(eventArgs.InstanceCredentials);

implementation.OnPropertyChanged(this.propertyName);
}

}

Assembling sub-aspects into a compound aspects

We now have all code transformations we need to realize the 'notify property changed' functionality, but we need to assemble them in a single custom attribute that could be applied to any class. This is the role of CompoundAspect, from which we derive our NotifyPropertyChangedAttribute.

[MulticastAttributeUsage(MulticastTargets.Class | MulticastTargets.Struct)]
[Serializable]
public sealed class NotifyPropertyChangedAttribute : CompoundAspect
{

[NonSerialized]
private int aspectPriority = 0;

public override void ProvideAspects(object targetElement, LaosReflectionAspectCollection collection)
{
// Get the target type.
Type targetType = (Type) targetElement;

// On the type, add a Composition aspect to implement the INotifyPropertyChanged interface.
collection.AddAspect(targetType, new AddNotifyPropertyChangedInterfaceSubAspect());

// Add a OnMethodBoundaryAspect on each writable non-static property.
foreach (PropertyInfo property in targetType.GetProperties())
{
if (property.DeclaringType == targetType && property.CanWrite )
{
MethodInfo method = property.GetSetMethod();

if (!method.IsStatic)
{
collection.AddAspect(method, new OnPropertySetSubAspect(property.Name, this));
}
}
}
}

public int AspectPriority
{
get { return aspectPriority; }
set { aspectPriority = value; }
}

}

The principal method of this aspect is of course ProvideAspects. It should add sub-aspects into a collection. Here we add AddNotifyPropertyChangedInterfaceSubAspect to the aspected class. Then we select all writable properties and we apply OnPropertySetSubAspect to their write accessor, unless if the property is static.

The EditableObject Aspect

This aspect is structurally similar to the Notify Property Changed Aspect, but it works on field-level and not on accessor-level. 

Although there exists other strategies, we chose here to virtualize the field storage, that is, we will replace the instance fields by something else. Yes, we will remove the fields at compile-time. 

Instead of the instance fields, we will have two dictionaries: one with the working copy of fields, the second with the backup copy. Instead of reading from and writing to instance fields, we will read from and write to the dictionary of working values. The BeginEdit, EndEdit and CancelEdit methods will simply copy the dictionary of working values into the dictionary of backup values or conversely.

Both dictionaries will be instance fields of the EditableImplementation class, which is the composed object implementing IEditableObject. Additionally to this interface, this class will have the semantics SetValue and GetValue.

Please refer to the code for more details. If you have understood the Notify Propetrty Changed sample, EditableObject should not be problematic.