Validating attribute usage with PostSharp Aspects

This post expands on my previous article on PostSharp. If you haven’t already, read it here.

Most of January has flown by with just one post, effectively shooting down my resolution to blog more often. In my defence, I have to state that I’ve just moved on to newer pastures, with all the re-settling in that involves.

Apart from that, I also spent a few hours twiddling with the Jasema sources, so I do have something to post about at least. 🙂

The .net attribute architecture supports an Attribute Usage definition, which is itself an attribute that can be applied to attributes. AttributeUsageAttribute determines whether said attribute can be applied to fields, methods, classes, assemblies, or any combination thereof. This allows you to identify, at compilation time, whether the attributes you have specified are used correctly, which is great.

There are situations, however, where you might want to enforce some more detailed constraints on an attribute; for example, you might want to make sure that it is only used by classes that implement a given interface, or check what parameters have been defined for it.

It is possible to implement this as a runtime check within the attribute itself, however this is not always a very elegant solution. If you check the constraints every time the attribute is invoked in some way, you’re incurring a performance hit, however slight. If one considers that PostSharp aspects may be called every time data changes in a given field, or every time a method (or even any method in an assembly is called), this may add up to quite a bit, depending on what sort of checking you’re doing.

By making this sort of check at run time, you’re also depriving yourself of compile time checking. Even if you have unit tests in place to make sure you catch incorrect behaviour. it can still take time to run the suite; wouldn’t it be a lot nicer if the compiler could be instructed to raise an error when an attribute is given the wrong parameters, or is used with the wrong sort of class?

While PostSharp aspects don’t allow you to give such instructions to the compiler, they do allow you to do something that’s so similar that the difference, to someone like me, is purely academic (so, flame me). These aspects define an OnCompileValidate method, which can be overridden in your custom aspects.

The PostSharp compile-time weaver calls these methods while weaving your aspects into the code, so you can place your validation there; this gives you the advantage of having checks run once only (hence, without impact on executing code). Consider the source below:

        
public override bool CompileTimeValidate(System.Reflection.MethodBase method)
{
    if (string.IsNullOrEmpty(fieldName))
        throw new InvalidOperationException(
            string.Format(
                "ListContentChangeRecorderAttribute requires the name of a field which implements IList. Failed on {0}.{1}",
                method.DeclaringType.FullName,
                method.Name));

    MemberInfo[] matchingFields = method.DeclaringType
        .FindMembers(
            MemberTypes.Field,
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static,
            this.MemberFilter,
            fieldName);

    if (matchingFields.Length < 1)
        throw new InvalidOperationException(
            string.Format(
                "ListContentChangeRecorderAttribute could not find field {0}.{1}",
                method.DeclaringType.FullName,
                fieldName));

    // Determine whether the type that defines the field implements the requested interface.
    if (!AttributeHelper.ImplementsInterface(((FieldInfo)matchingFields[0]).FieldType, typeof(IList)))
        throw new NotSupportedException(string.Format(
            "{0}.{1} does not implement IList.",
            matchingFields[0].DeclaringType.FullName,
            fieldName));

    return base.CompileTimeValidate(method);
}

This code, from the custom attributes controlling undo/redo functionality in Jasema v2 (coming soon), runs a few checks on the arguments provided for the attribute at construction: first, it makes sure that the field name argument is not empty, and that the field specified there is an actual existing field in the class which declares the method decorated with this attribute. Finally, using some helper code, it makes sure that the field implements IList, a requirement imposed on it by the logic of the aspect. This sort of checking would take some performance away from the application if it was to run repeatedly, but in this way, it only ever needs to run once – since attribute arguments must be constants, you can safely assume they won’t change after compilation.

Note that although CompileTimeValidate returns a bool type, I’m throwing an exception rather than returning false if the check decides there is something wrong. This is a matter of personal preference. The default behaviour for the weaver appears to be a silent failure; that is, if an aspect fails its validation check by returning false, it is simply omitted. Since I used aspects for a specific reason, I’d much rather have them ring all sorts of bells and raise all sorts of alarms if something is wrong, rather than failing silently and leaving you to scratch your head wondering why your masterfully crafted, world conquering AOP application is not doing what you thought it was supposed to do.

Mind you, the silent failure behaviour was probably introduced to allow the use of PostSharp‘s multicast aspect functionality (like applying an aspect to all methods in an assembly starting with an “F”). This would have been rather unwieldy if it broke the build every time it encountered a situation it didn’t quite support.

So in my case here, for example, I can’t multicast this aspect, but that’s perfectly ok by me because I only intend to use it explicitly. If you think you need to have an attribute capable of multicasting safely, then simply have it return false on failure. You may also be able to get the best of both worlds by making sure that all the classes that can support your attribute have a similar name pattern, but personally that’s where I start going glassy eyed and start drifting off into massively-overlong-classname induced nightmares.