Click here to Skip to main content
Click here to Skip to main content

Have Fun Again With Custom Attributes (Part 1)

, 17 Sep 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
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.

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() 
{ 
   // Method body.

}

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:

Image

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)
{
  // Add trace code here. 

}

public override void OnExit(MethodExecutionEventArgs eventArgs) 
{
  // Add trace code here. 

}

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!

Image

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.

Image

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:

Image

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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Gael Fraiteur
Web Developer
Czech Republic Czech Republic
Gael Fraiteur is a software engineer specializing in application development for large and medium accounts. He has 5 years of professional experience and more than 15 years of hobby experience in software development. Gael Fraiteur has an advanced knowledge of the .NET Framework, Oracle Server, Microsoft SQL Server and TIBCO.
 
Gael Fraiteur is the lead developer of PostSharp, a post-compiler for .NET that makes AOP on .NET easy both for business and system developers.

Comments and Discussions

 
GeneralCorrection to link for part two of the article PinmemberGreek Coder26-Feb-08 9:45 
Generalperformance penalty PinmemberStephan Depoorter23-Nov-07 2:34 
GeneralRe: performance penalty PinmemberGael Fraiteur23-Nov-07 2:49 
GeneralExperiences in real world projects? [modified] PinmemberUrs Enzler25-Sep-07 0:14 
GeneralRe: Experiences in real world projects? PinmemberGael Fraiteur25-Sep-07 4:45 
GeneralRe: Experiences in real world projects? PinmemberUrs Enzler25-Sep-07 5:23 
GeneralRe: Experiences in real world projects? PinmemberGael Fraiteur25-Sep-07 5:30 
Questionpostsharp.org not responding? Pinmemberanithri19-Sep-07 8:42 
AnswerRe: postsharp.org not responding? PinmemberGael Fraiteur19-Sep-07 9:21 
GeneralRe: postsharp.org not responding? PinmemberEduard Gomolyako21-Sep-07 1:36 
QuestionRe: postsharp.org not responding? Pinmemberpatnsnaudy25-Sep-07 5:01 
AnswerRe: postsharp.org not responding? PinmemberGael Fraiteur25-Sep-07 5:06 
GeneralRe: postsharp.org not responding? Pinmemberpatnsnaudy25-Sep-07 5:11 
GeneralRe: postsharp.org not responding? PinmemberGael Fraiteur25-Sep-07 5:23 
NewsRe: postsharp.org not responding? PinmemberGael Fraiteur27-Sep-07 6:13 
Generaldude...very sweet Pinmemberjconwell19-Sep-07 6:36 
GeneralVS 2005 integration Pinmemberleeloo99917-Sep-07 22:57 
GeneralRe: VS 2005 integration PinmemberGael Fraiteur17-Sep-07 23:26 
GeneralRight Expectations PinmemberRajAhan17-Sep-07 17:00 
GeneralRe: Right Expectations PinmemberGael Fraiteur17-Sep-07 21:53 
GeneralRe: Right Expectations PinmemberBoneSoft25-Sep-07 12:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411023.1 | Last Updated 17 Sep 2007
Article Copyright 2007 by Gael Fraiteur
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid