Introduction
If you think you know everything there is to know about custom attributes, read this article. PostSharp will let you to take your custom attributes to the next level and let them actually add new behaviors to your code. Learn how to encapsulate logging, performance instrumentation, or field validation into custom attributes. And turn fun into serious advantages!
Logging Is Painful
There are a lot of powerful logging frameworks, but every one of them requires some painful work: the addition of boilerplate code to every method. And what if, in the middle of the project, you decide to switch from one framework to another? You'll have to modify all the logged methods, and there may be thousands!
Logging is painful because, like most non-functional requirements (security, transactions, caching …), it crosscuts all functional requirements. If you have one hundred business processes that require logging, security, and transactions, you will probably have logging-, security- and transactions-related instructions in each of these business processes. Therefore, we need a better way to encapsulate crosscutting concerns, a way that would not force us to modify each method to which it applies.
Custom attributes are a great way to solve this problem.
A Trivial Logging Custom Attribute
What we want is simple: a custom attribute that logs a message before and after the execution of the methods to which it is applied. We would also like to specify the trace category, using the custom attribute constructor.
So, ideally, we would like to use the custom attribute like this:
[Trace("MyCategory")]
void SomeTracedMethod()
{
}
That's what we want, now let's implement it! We can start by declaring the custom attribute as we are used to. All we need is a field named category
and a constructor initializing this field:
public sealed class TraceAttribute : Attribute
{
private readonly string category;
public TraceAttribute( string category )
{
this.category = category;
}
public string Category { get { return category; } }
}
Then, we can add the things that will make this custom attribute actually change the method to which it is applied. As stated in the introduction, we will use PostSharp for this job, so let's add it to the project:
All we have to do is make our custom attribute inherit PostSharp.Laos.OnMethodBoundaryAspect
instead of System.Attribute
. This class defines new methods that will be called at runtime when targeted methods are executed:
OnEntry
– Before the method is executed.
OnSuccess
– When the method returns successfully (without exception).
OnException
– When the method exits with an exception.
OnExit
– When the method exits, successfully or not.
We just implement the methods we are interested in: OnEntry
and OnExit
.
public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
}
public override void OnExit(MethodExecutionEventArgs eventArgs)
{
}
The implementation of these methods should call System.Diagnostics.Trace.WriteLine
. But, how can we know the name of the method we are actually in? No problem at all; all the necessary information is contained in the MethodExecutionEventArgs
object, which is passed to OnEntry
and OnExit
. We are interested in the eventArgs.Method
property, but if we also wanted to log the parameter values, we could retrieve them using the GetArguments()
method.
Here is the final implementation of our tracing custom attribute:
[Serializable]
public sealed class TraceAttribute : OnMethodBoundaryAspect
{
private readonly string category;
public TraceAttribute( string category )
{
this.category = category;
}
public string Category { get { return category; } }
public override void OnEntry( MethodExecutionEventArgs eventArgs )
{
Trace.WriteLine(
string.Format( "Entering {0}.{1}.",
eventArgs.Method.DeclaringType.Name,
eventArgs.Method.Name ),
this.category );
}
public override void OnExit( MethodExecutionEventArgs eventArgs )
{
Trace.WriteLine(
string.Format( "Leaving {0}.{1}.",
eventArgs.Method.DeclaringType.Name,
eventArgs.Method.Name ),
this.category );
}
}
Did you notice? The class is decorated with the Serializable
custom attribute. This is required for each custom attribute built from PostSharp Laos.
Let's now try this custom attribute on some sample code:
internal static class Program
{
private static void Main()
{
Trace.Listeners.Add(new TextWriterTraceListener( Console.Out));
SayHello();
SayGoodBye();
}
[Trace( "MyCategory" )]
private static void SayHello()
{
Console.WriteLine("Hello, world." );
}
[Trace("MyCategory")]
private static void SayGoodBye()
{
Console.WriteLine("Good bye, world.");
}
}
Now, execute the program and… magic, method calls are logged!
How did it work? If you look at the Output window of Visual Studio, you will see that PostSharp has been invoked during the build process.
PostSharp has actually modified the output of the C# compiler and enhanced the assembly so that the methods of our tracing custom attribute are invoked during program execution.
Looking at the resulting assembly using Lutz Roeder's Reflector is very informative:
As you can see, our originally tiny method is now much more complex, because PostSharp has added instructions to invoke our custom attribute at runtime.
Déjà vu?
If you think that this is very similar to Aspect-Oriented Programming (AOP), you're right - PostSharp Laos is actually nothing but an AOP framework.
An aspect is defined in Wikipedia as a "part of a program that cross-cuts its core concerns, therefore violating its separation of concerns". Most of the time, in business applications, an aspect is a non-functional requirement, like logging, security, transaction management, exception handling, or caching. The separation of concerns is one of the principle design principles in software engineering. It states that fragments of code implementing the same concern should be grouped together in components. A measure of the design quality is the high cohesion but low coupling of components.
AOP frameworks make it possible to encapsulate aspects into modular entities; in PostSharp Laos, these entities are custom attributes. The principal advantage of this approach is its simplicity: you get AOP without the typical learning curve. Additionally, PostSharp Laos is language-independent, and its integration with Visual Studio (Intellisense, debugger …) is excellent.
Another feature that makes PostSharp different is that it operates at the MSIL level after compilation. It does not have the limitations of solutions based on proxies: you can add aspects to private methods, you don't have to derive your classes from MarshalByRefObject
, and so on.
If you are interested in AOP, you may want to have a look at the Policy Injection Application Block of the Microsoft Enterprise Library or the Spring .NET Framework, two other viable AOP solutions for .NET.
A Step Further: Custom Attribute Multicasting
Nice, we have a tracing custom attribute! But, what if we have hundreds of methods to trace? Do we have to add this custom attribute to each of them? Of course, not! Thanks to a feature called attribute multicasting, we can apply a custom attribute to many methods in a single line.
For instance, adding TraceAttribute
to the class Program
actually applies the attribute to each method of this class:
[Trace( "MyCategory" )]
internal static class Program
{
…
And, if we don't want the custom attribute to be applied to the Main
method, we can restrict the set of methods and name the methods to which it applies:
[Trace( "MyCategory", AttributeTargetMembers = "Say*")]
internal static class Program
{
…
Alternatively, we can add the custom attribute at the assembly-level to trace all the methods defined in the assembly:
[assembly: Trace("MyCategory")]
It is necessary, however, to exclude the attribute from being applied to the TraceAttribute
class itself, because an aspect cannot itself be aspect-ed:
[Trace( null, AttributeExclude = true )]
[Serializable]
public sealed class TraceAttribute : OnMethodBoundaryAspect
{
…
Summary
In this article, I've shown how to develop custom attributes that actually add new behaviors to your .NET programs. For this demonstration, I've used PostSharp Laos, an Aspect-Oriented solution for the .NET Framework.
Three years after its creation, PostSharp is already a mature project. At the time of writing, PostSharp has been downloaded thousands of times. It's presently in version 1.0, and is entering the release candidate phase. PostSharp is reportedly used by many companies, both ISV and system integrators. There is a full-time developer behind the project, offering support, consultancy, and sponsored development. Bugs are typically corrected in a week.
As I tried to illustrate in this article, PostSharp actually modifies MSIL instructions so that additional behaviors are invoked at runtime. One can see how it works, using Lutz Roeder's Reflector. This article only presented custom attributes that add a try
-catch
block over the methods, but it is also possible to intercept calls done in another assembly, to intercept field accesses, or to inject interfaces in types.
In the second part of the article, I will show how to develop two new custom attributes: a performance counter, and a field validator. Until then, have a good time with PostSharp!