Archive

One of the only thoughts I captured this Tuesday (at least in the subset of thoughts implementable using PostSharp): a graphical language to express matching patterns in source.

Second day at ECOOP 2007 and still the unpleasant feeling I am a dumb. Oh my god, where are the times, six years ago when I finished university, where I was as brilliant as them? Is this collapse the result of six years in industry? Or just of the last months spent in drawing TIBCO business processes?

Anyway, I understood enough to catch a good idea: a graphical way to express "matching rules". Matching rules are expressions that identify points in code where aspects should be applied. In the AspectJ world, one say that matching rules are pointcuts and matched points are join points. While designing PostSharp Laos, I have chosen not to refer to these concepts and I am happy to see that Microsoft did actually the same. There are actually matching rules in PostSharp Laos, but you do not see them. They are hidden behind the "attribute multicasting" mechanism: actually, when you define a filter on a multicast attribute, you define a matching rule.

Of course, the expressiveness of matching rules of multicast attributes is limited. Microsoft Enterprise Library has a richer model for that, even if its usability is questionable.

The idea is to use the same graphical language to express matching rules (or patterns) than to express the model. That means that we could use a similar language than UML. Suppose we want to match all classes having a method Dispose(). We would draw a class and add a Dispose() method to this. And if we want to restrict the classes to belong to a given namespace, we would for instance specify the name of the class with wildcards. That is, what we specify in UML elements become constraints. What we don't specify are free variables that do not play any role in the pattern matching mechanism.

Some thoughts about my first day at the European Conference on Object-Oriented Programming: how to cope with conflicts when different aspects apply to the same method?

Yesterday was my first day in Berlin at ECOOP 2007. I have attempted the 2nd International Workshop on Aspects, Dependencies and Interactions which studies, as its name indicates, what happens when you put many aspects together. Will there be a conflict? Or, contrarily, does one aspect depend of another?

My first impression was how far I've come from the academic world during these 6 years since I've left university. Academic and developers' communities differ radically by their way of thinking and their way of communication. I'm happy to have chosen my way three years ago, when I started to develop PostSharp: deliberately refuse to look at AspectJ and design a tool as a developer would design it and use it, and not according to the framework of thoughts of the academic community. It indeed was impressing to realize how AspectJ has become the horizon of AOP research, how all problems are stated in terms of AspectJ. However, the way how AspectJ is designed causes problems in itself and it is not always easy, in the academic sphere, to realize that these problems are caused by AspectJ design and might be stated in a different and more fruitful way if only they were abstracted from AspectJ.

And another thought: it's incredible how Microsoft has lost attention from researchers, at least in the AO academic community, and maybe even more generally in the OO academic community. What I'll be trying to say during my speech on Thursday is "now, thanks to PostSharp, you can use the Microsoft .NET Platform for your research work". I guess Microsoft should have sponsored me to participate in this conference and in this evangelization campaign.

It was however very interesting to me to attend this workshop, because it made me face problem that have already emerged in the AOP community, but not yet in the PostSharp one. So let's go back to these problems:

How to make sure that aspects are not applied in a conflicting way, or in a way that damages semantics of the base code?

Let's take an example: suppose I have an operation GetCustomerInfo, returning private information about customers. We want to apply two non-functional requirements to this operation: we want to cache the method and ensure that only authorized employees access this information. So we apply two aspects: caching and authorization.

The normal behavior of the aspected operation is the following:

Check the current user is authorized to retrieve the requested the piece of information. If not, throw an exception.

If the requested piece of information is in cache, return it immediately without continuing the chain of behaviors.

Retrieve the piece of information.

Store it in cache.

So now, what would happen if the cache aspect was applied before the authorization check? Well, you would have bad surprises. If the requested piece information is present in cache, authorization is simply not performed.

We can state this concrete problem: "caching cannot occur before authorization". And more abstractly: "there cannot be an aspect that interrupts the normal control flow before an authorization aspect".

Here is how the constraint could be expressed:

[AspectConstraint( ConstraintType.Forbid, AspectPosition.Before, AspectBehaviors.ChangeControlFlow )]

public class AuthorizationAspect : OnMethodBoundaryAspect

{

}

The effect of the AspectConstraint custom attribute would be that the weaver should emit code that throws a runtime exception if an aspect tries to change the normal control flow. Ideally, this kind of situation should be discovered at compile-time, but it would require PostSharp to make a static analysis of the aspect implementation to discover if the aspect tries to modify the control flow. It is theoretically possible (and fully described in academic literature), but practically much more demanding than the runtime check.

A similar interesting feature would be to allow these constraints to be applied on methods also. Suppose I have a method that performs authorization imperatively (not using an aspect). I could also want to forbid the method to be transformed by aspects that could bypass it. So it should be possible to apply this custom attribute on method as well.

Needs for dependencies check are of course much larger than this. For instance, there could be a known incompatibility between two aspects. The architect of a development team could write an XML file containing all known incompatibilities, and it would result in compile-time errors if developers try to apply aspects in an incompatible way. This is relatively easy to implement and could be useful in a probable (near?) future when PostSharp will be used in large projects...

Normally, using the standard .NET Framework, custom attributes apply to the element onto which they are defined. This element can be the assembly, a type, a property, an event, a field, a method, a method parameter or the method return value.

The multicasting feature enables to apply the same custom attribute to many elements using a single declaration of the custom attribute. The benefit is obvious: write one line of code where you should write one hundred.

Let's see how it works.

Using a custom attribute

A basic example

Let's take inspiration from the sample PostSharp.Sample.Trace, which is shipped with PostSharp. We have a custom attribute [Trace] that allows to write something to the console. We can set up the trace category.

The most natural way is to add the [Trace] attribute to each method:
namespace BusinessLayer
{
public class CustomerProcesses
{
[Trace("Business")]
public Customer CreateCustomer(string firstName, string lastName) { ... }

[Trace("Business")]
public void DeleteCustomer(long customerId) { ... }
}

public class LoanProcesses
{
[Trace("Business")]
public Loan CreateLoan(long customerId, decimal amount, int periods, DateTime firstPaymentDate ) { ... }

[Trace("Business")]
public void DeleteLoan(long loanId) { ... }
}
}
This seems classical but it forces us to duplicate code. And what if each class has tenths of methods? We can do the same with less code. The following snippet is exactly equivalent to the previous one:
namespace BusinessLayer
{

[Trace("Business")]
public class CustomerProcesses
{
public Customer CreateCustomer(string firstName, string lastName) { ... }
public void DeleteCustomer(long customerId) { ... }
}

[Trace("Business")]
public class LoanProcesses
{
public Loan CreateLoan(long customerId, decimal amount, int periods, DateTime firstPaymentDate ) { ... }
public void DeleteLoan(long loanId) { ... }
}
}
Why does it work? Because PostSharp 'knows' that the trace attribute applies to method.

When the custom attribute is applied to a container (a type, in this case, is a container of methods), the custom attribute is multicasted recursively to all methods it contains.

So why not applying the custom attribute to the assembly? The assembly is a container of types, which are containers of methods, so all methods would be traced. But it's not what we want: we want to trace only the business layer. Fortunately, we can use wildcards to solve this problem.

Again, the following snipset is equivalent to the ones above:
[assembly: Trace("Business", AttributeTargetTypes="BusinessLayer.*")]

namespace BusinessLayer
{
public class CustomerProcesses
{
public Customer CreateCustomer(string firstName, string lastName) { ... }
public void DeleteCustomer(long customerId) { ... }
}

public class LoanProcesses
{
public Loan CreateLoan(long customerId, decimal amount, int periods, DateTime firstPaymentDate ) { ... }
public void DeleteLoan(long loanId) { ... }
}
}
We have used the AttributeTargetTypes property to limit the set of methods to which the custom attribute is applied. Without this property, the custom attribute would have been applied to each method of the whole assembly.

Thanks to the properties AttributeTargetTypes, AttributeTargetMembers and AttributeTargetElements and AttributeTargetMemberAttributes you can restrict the set of elements to which custom attributes are multicasted. When they are not specified, a 'match all' is assumed.


Priority of attributes

Now say that our business process classes implement the interface IDisposable. We do not want to trace the Dispose method.

We can use the following code:
[assembly: Trace("Business", AttributeTargetTypes="BusinessLayer.*", AttributePriority = 1)]
[assembly: Trace(AttributeTargetMembers="Dispose", AttributeExclude = true, AttributePriority = 2)]
The first line includes all types and methods of the business layer, the second line excludes the methods named "Dispose".

The AttributeExclude property tells that set of custom attributes multicasted on an element should be cleared. So it actually removes custom attributes.

Note the AttributePriority property in both lines. It tells that the first line should be processed first, then the second one. So first we include all, then we exclude. If you think it's stupid to add it, you are right (it would be so nicer without!), but the C# compiler does not guaranty that the custom attributes are written (into the binary) in the same order as in the source file. That is, the order between lines could (and will) be lost during compilation. So if you have to make an inclusion-exclusion and not an exclusion-inclusion, you have to specify the AttributePriority property.

The AttributePriority property determines in which order the custom attributes are processed by the multicasting engine, and nothing else.


If you do not like to use named methods (there are good reasons for this), another way to perform the same is to apply the exclusion attribute directly on Dispose methods:

[assembly: Trace("Business", AttributeTargetTypes="BusinessLayer.*")]

namespace BusinessLayer
{
public class CustomerProcesses : IDisposable
{
public Customer CreateCustomer(string firstName, string lastName) { ... }
public void DeleteCustomer(long customerId) { ... }

[Trace(AttributeExclude=true)]
public void Dispose() { ... }
}

public class LoanProcesses : IDisposable
{
public Loan CreateLoan(long customerId, decimal amount, int periods, DateTime firstPaymentDate ) { ... }
public void DeleteLoan(long loanId) { ... }

[Trace(AttributeExclude=true)]
public void Dispose() { ... }
}
}

The rule is:

Custom attributes applied on innermost element have always greater priority (i.e. is always processed after and has greater force) than custom attributes applied on outer elements.

Suppose now that you want to assign a special trace category to the methods of the namespace BusinessLayer.Internals. You can achieve this with the following code:
[assembly: Trace("Business", AttributeTargetTypes="BusinessLayer.*", AttributePriority = 1)]
[assembly: Trace("BusinessInternals", AttributeTargetTypes="BusinessLayer.Internals.*",
AttributeReplace = true, AttributePriority = 2)]

The AttributeReplace property clears the set of custom attributes applied to an element and add a new one. Without AttributeReplace, a new custom attribute instance would be added. AttributeReplace is automatically implied if the custom attribute does not allow multiple instances (see below [MulticastAttributeUsage]).


Developing a multicasting custom attribute

Now that you know how to use multicasting custom attributes, let's see how to develop them.

First, note it works only inside PostSharp and with custom attributes that are derived from MulticastAttribute. All abstract custom attributes defined in PostSharp Laos are derived from MulticastAttribute, so they inherit its compile-time behavior.

Just like normal custom attribute classes should be decorated with the [AttributeUsage] custom attribute, classes derived from MulticastAttribute should be decorated with [MulticastAttributeUsage]. When applied on the TraceAttribute class, the [MulticastAttributeUsage] custom attribute determines how the [Trace] attribute could be used. Since, say, we can trace only methods and constructors, we will use:

[MulticastAttributeUsage( MulticastTargets.Method | MulticastTargets.Constructor )]
public sealed class TraceAttribute : MulticastAttribute { ... }
Using [MulticastAttributeUsage], you can also specify:
  • whether multiple instances are allowed on a single element (property AllowMultiple),
  • whether it can be applied on elements defined in external assemblies (property AllowExternalAssemblies),
  • whether a custom attribute should be emitted in the transformed assembly (after multicasting) -- this makes sense if the custom attribute is also used at runtime.
But this is only information for PostSharp. You should still tell the C# compiler that it is valid to apply the custom attribute at assembly-level, at type-level or at method-level, and that having many instances of the attribute is allowed.

In order to tell the C# compiler that the custom attribute may also be added on method containers (types, assembly, properties, events), you should use the standard [AttributeUsage] custom attribute. You should also tell the compiler that multiple instances of the custom attribute are allowed, even if you forbidded it using [MulticastAttributeUsage]. Why? Otherwise the user of your custom attribute could not use declare many custom attributes on assembly- or type-level, eventually using wildcards. What is important is uniqueness on the target element, and this is enforced by the [MulticastAttributeUsage] custom attribute.

So finally:
[AttributeUsage( AttributeTargets.Assembly | AttributeTargets.Module |
AttributeTargets.Class | AttributeTargets.Struct |
AttributeTargets.Method | AttributeTargets.Constructor,
AllowMultiple = true, Inherited = false )]
[MulticastAttributeUsage( MulticastTargets.Method | MulticastTargets.Constructor, AllowMultiple=true )]
public sealed class TraceAttribute : MulticastAttribute { ... }

You should quality your own multicast custom attributes both by [AttributeUsage] and [MulticastAttributeUsage].


If the previous rule is correct, why is it optional to qualify the aspects we define using PostSharp Laos? Well, because Laos does it for you. Custom attributes are already defined in the base classes.

[MulticastAttributeUsage] is inherited from the base class to derived classes. Usage can be restricted, but not enlarged, in derived classed.

That's nearly all. Of course this mechanism could still be enhanced (it would be nice for instance to be able to specify its own filter). This will be for a further version :).

Happy multicasting!

Gael