Archive

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
About Enterprise Library With the new release of the Enterprise Library of Microsoft patterns & practices team, it seems like AOP makes a great and sudden come back in the .NET community. The latest release comes indeed with the Policy Injection Application Block, which is a kind of AOP framework that allows to "inject" the other Enterprise Library application blocks (logging, exception handling, ...) into user code, and using a configuration file. The implementation use remoting proxies to intercept method calls and inject policies, which has the major drawbacks to:
  • Be limited to methods derived from MarshalByRefObject or exposing their semantics on an interface.
  • Require instances to be created through a factory.
This is a great opportunity to demonstrate how PostSharp can be used, not only to overcome these issues, but also to offer new benefits. Hopefully, the p&p team had this in mind when they designed this application block: the framework can be used even if we choose not to go through factories. About PostSharp PostSharp is a post-compiler for the .NET Framework. It modifies .NET assemblies after compilation. PostSharp works at MSIL level. PostSharp can be used as an Aspect Oriented Programming (AOP) framework, but it is only one possible use. PostSharp Laos is a true AOP framework that does not require any knowledge of MSIL. Developers can react to "event" that happens in the code and can use the standard System.Reflection API. Introducing PostSharp4EntLib The first release of PostSharp4EntLib provides three custom attributes that 'bind' Policy Injection to user code. The InjectPoliciesFromConfiguration custom attribute This attribute can be applied to an assembly. It means that the policy injection settings will be completely processed at compile time. Here is how it is used: [assembly: InjectPoliciesFromConfiguration("CompileTimePolicyInjection.config")] This custom attribute processes matching rules specified in the configuration file and changes the affected methods so that applied policies are effectively invoked at runtime. This custom attribute serves the scenarios where both matching rules and policies are known at compile time. Benefits are:
  • Matching is performed at compile-time, so that runtime execution is faster.
  • Policy Injection configuration does not have to be deployed.
  • As with anything in PostSharp, remoting proxies and factories are not necessary.
The InjectPolicy custom attribute This attribute indicates that a given policy should be injected to a method. The name of this policy should be known at compile-time, but not the content. This custom attribute may be applied to methods: [InjectPolicy("Exception Handling")] public void Close() { ... } The attribute may be multicasted to many methods using wildcards: [assembly: InjectPolicy("Exception Handling", AttributeTargetTypes="MyProject.BusinessLayer.*")] Benefits of this attribute are:
  • There is no CPU-expensive runtime matching.
  • In some scenarios, make it easier to specify which policies should apply to methods (more flexible than the 'Tag' approach of EntLib).
  • As with anything in PostSharp, remoting proxies and factories are not necessary.
The Interceptable custom attribute This custom attribute simply adds the code to make the method 'interceptable', i.e. to make it a possible target of policy injection, without the use of factories. Policies are matched and applied at runtime. Like the InjectPolicy attribute, this one can be applied to methods, either directly either using multicasting: [assembly: Interceptable("Exception Handling", AttributeTargetTypes="MyProject.BusinessLayer.*")] The benefit is of course that policies can be injected on unchanged existing code:
  • no need for factories,
  • not limited to MarshalByRefObject,
  • works also with static methods.
How does it work? As I said in the beginning, PostSharp achieves this result by modifying the assembly after compilation. The best way to see how the code is modified is to use look at the modified code using Roeder's Reflector. Say we have the following user code (in C#): public static void Transfer(BankAccount origin, BankAccount destination, decimal amount) { origin.Withdraw(amount); destination.Deposit(amount); } We make this method interceptable by one of the three custom attributes described above. After compilation and post-processing by PostSharp, the method looks like that: [DebuggerNonUserCode] public static void Transfer(BankAccount origin, BankAccount destination, decimal amount) { Delegate delegate2 = new ~PostSharp~Laos~Implementation.~delegate~3(BankAccountProcess.~Transfer); object[] arguments = new object[] { origin, destination, amount }; MethodInvocationEventArgs eventArgs = new MethodInvocationEventArgs(delegate2, arguments); ~PostSharp~Laos~Implementation.~aspect~9.OnInvocation(eventArgs); } The old method body has been moved to a new (private) method called ~Transfer. The new implementation basically creates a delegate to the old implementation, create event arguments and call a OnInvocation method. This is where the PostSharp4EntLib code lays. Basically, this method invokes the EntLib pipeline of handlers. The implementation of PostSharp4EntLib has two parts:
  • A compile-time part whose principal role is to detect which methods needs to be 'interceptable', and adds kinds of 'event handlers' (OnMethodInvocation in our case) on these methods.
  • A runtime part which is basically the implementation of the event handlers.
I repeat, PostSharp4EntLib does not generate MSIL instructions itself. It relies on PostSharp Laos, which defines this system of "code-driven event handlers" whose OnMethodInvocation is the only used here. Other "handlers" (called aspects or advices) are OnMethodBoundary, OnFieldAccess or OnException. They are not currently used but may be in the future of this project. Waiting for a CodePlex project... I would like to release this project on CodePlex but I am still waiting for Microsoft to upgrade their servers. Would you have any influence on the acceptance on my project... ;-) Before I get the CodePlex space, you can access the project from the PostSharp4EntLib Project Dashboard. Many thanks to Olaf Conijn for its online support.
According to Ohloh, it would cost 15 man-years to develop PostSharp from scratch by a team of professionals and the source code would have a value of 800 000 $. Amazing! I've started PostSharp in September 2004 and have programmed mainly in my spare time. Right, that's a lot of time. Ask my wife how it is to live with a passionated developer. But anyway, it hardly reaches 15 hours per week in the average. How to understand this difference in order of magnitude of 1 to 10? First, notice that it is not exceptional in open-source projects. Compare with other projects and you will see. What is maybe uncommon in PostSharp, it is the complexity of the problem. But even this statement may be pure vanity. So why a lot of open-source projects are 10 times more productive than commercial ones? I see a lot of factors. When working on PostSharp:
  • I don't take long coffee pauses neither read online newspapers.
  • I don't attend meaningless meetings.
  • I don't have to negotiate every design detail with doubtfully competent colleagues.
  • Business stakeholder don't change their mind every Monday.
  • I don't care writing analysis and design documents before coding.
  • ...
So if these estimates are really relevant in business context, the real question is why is software development so little productive in business environment?