What really changed between 1.0 and 1.5 #1: Targeting Silverlight and the Compact Framework

by Gael Fraiteur on 28 Oct 2008

Setting aside the "exciting new features" of 1.5, there is some fundamental difference between 1.0 and 1.5. Although this difference will hardly be detected by beginners, advanced (PostSharp Core) and even intermediate (advanced PostSharp Laos) ones will surely hit it soon: PostSharp 1.5 is completely independent from System.Reflection.

It has been written that PostSharp Core is built on the top of System.Reflection: this is not true. PostSharp has from the very beginning its own reflection engine, for the good reason that System.Reflection is not good enough for the job. But PostSharp 1.0 relied on reflection for some operations. Indeed, it required every processed assembly to be loaded in the CLR. PostSharp 1.0 read the mapped PE file from memory after it has been loaded loaded by the CLR. A great benefit of this approach is that we don't need to load the assembly twice in memory.

So it was not possible, in PostSharp 1.0, to load an assembly in PostSharp without loading it in the application domain.

The first reason I had to redesign this was Mono: memory mapping of modules was not as clearly defined as in the Microsoft implementation (where the origin of the PE file is simply the HMODULE of module), so it was necessary to read the assembly from disk. But assemblies could still be loaded in the CLR besides, so we could still rely on System.Reflection.

The second and most compelling reason was the support of other variants of the framework, namely Silverlight and Compact Framework. Shortly speaking, it is not possible to load the modules into the CLR, and therefore neither possible to use System.Reflection. It was consequently necessary to make PostSharp completely independent from System.Reflection.

So remember: when using PostSharp for Silverlight (SL) or the Compact Framework (CF), the assembly being transformed is never loaded in the CLR.

Instantiation of aspects in SL and CF

Remember that, when you target the full .NET Framework using PostSharp, instances of aspects are created at build time; they are then serialized and stored as a binary blob as a managed resource.

But how can we instantiate aspects if we cannot load their type? No magic here: we cannot. So we don't. If you target SL or CF, your aspects will be instantiated at runtime and no serialization or deserialization will occur. Period.

You may be curious how aspects are actually instantiated at runtime. Very easily: the PostSharp Laos weaver emits instruction that invoke the aspect constructor with the correct parameters and sets named fields and properties. So finally you get pure MSIL code -- and it is even much faster than deserialization.

Compile-time semantics in SL and CF

A second and more subtle problem is how will work compile-time semantics (like CompileTimeValidate, CompileTimeInitialize and GetOptions). Simply: they cannot be implemented in a SL or CF assembly. CF and SL variants of PostSharp.Laos simply don't contain these methods. There are two kind of workaround:

Custom Attribute

Custom attributes configuring aspects, for instance classes derived from OnExceptionAspect should be annotated with OnExceptionAspectConfigurationAttribute to specify which exception should be caught (this replaces a build-time call to the GetExceptionType method).

using PostSharp.Laos;

namespace PostSharp.Samples.Silverlight.Aspects
{
    [OnExceptionAspectConfiguration(ExceptionType = "System.SystemException, mscorlib")]
    public class ExceptionHandlerAttribute : OnExceptionAspect
    {
        public override void OnException(MethodExecutionEventArgs eventArgs)
        {
            // Show a dialog box.
        }
    }
}

External Aspect

More complex logic can be implemented in an assembly built for the normal .NET Framework. So instead of CompoundAspect, you should create a class inheriting ExternalAspect and annotate the class with the ExternalAspectConfigurationAttribute custom attribute. This custom attribute specifies the class implementing the aspect:

using PostSharp.Extensibility;
using PostSharp.Laos;

namespace PostSharp.Samples.Silverlight.Aspects
{
    [ExternalAspectConfiguration(
        "PostSharp.Samples.Silverlight.Impl.NotifyPropertyChangedImpl, PostSharp.Samples.Silverlight.Impl")]
    [MulticastAttributeUsage(MulticastTargets.Class | MulticastTargets.Struct, PersistMetaData = false)]
    public class NotifyPropertyChangedAttribute : ExternalAspect
    {
    }
}

The implementation class should implement the interface IExternalAspectImplementation, defined in PostSharp.Laos.dll (not PostSharp.Laos.SL.dll or PostSharp.Laos.CF.dll):

 

You see the similarity with CompoundAspect. There are however some important differences: You don't get the aspect instance because we are not able to create an aspect instance. But you get the object construction (IObjectConstruction), which specifies the aspect type, the constructor arguments, and the named arguments. The second thing is that, as you cannot receive an aspect instance, you cannot create an aspect instance to add it to the collection. So you have to add an ObjectConstruction instead.

Here is the body of ImplementAspect:

public void ImplementAspect(object target, IObjectConstruction aspectData,
                            LaosReflectionAspectCollection collection)
{
    // Get the target type.
    Type targetType = (Type) target;

    // On the type, add a Composition aspect to implement the INotifyPropertyChanged interface.

    collection.AddAspectConstruction(
        targetType,
        new ObjectConstruction(
            "PostSharp.Samples.Silverlight.Aspects.Impl.NotifyPropertyChangedCompositionAdvice, PostSharp.Samples.Silverlight.Aspects"),
        null);

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

            if (!method.IsStatic)
            {
                collection.AddAspectConstruction(
                    method,
                    new ObjectConstruction(
                        "PostSharp.Samples.Silverlight.Aspects.Impl.NotifyPropertyChangedSetterAdvice, PostSharp.Samples.Silverlight.Aspects",
                        property.Name),
                    null);
            }
        }
    }
}

So writing aspects of intermediate complexity is much less convenient for CF/SL than for the full framework. Good news is that is still possible to do complex things, and that this complexity can be encapsulate so that aspect users actually don't care.