Click here to Skip to main content
15,878,871 members
Articles / Programming Languages / C#
Article

Have Fun Again With Custom Attributes (Part 2)

Rate me:
Please Sign up or sign in to vote.
4.83/5 (30 votes)
20 Sep 2007CPOL7 min read 87.1K   94   16
This is the second article in a series of two, with this one going into the details of the real-world implementation of custom attributes that actually add behaviors to your code, namely performance instrumentation and field validation.

Introduction

This is the second article in a series of two, with this one going into the details of the real-world implementation of custom attributes that actually add behaviors to your code, namely performance instrumentation and field validation.

The first article of this series introduced some key concepts of PostSharp, based on a simple example: a tracing custom attribute derived from OnMethodBoundaryAspect. This article presents two more types of custom attributes, with two examples: a performance counter based on OnMethodInvocationAspect, and a field validation framework based on OnFieldAccessAspect.

The Two Lives of a Custom Attribute

Before jumping into the implementation of new custom attributes, let's delve into the internal workings of PostSharp Laos. PostSharp is an MSIL enhancer: it inserts itself in the build process, and modifies the output of the compiler (C#, VB.NET, J#, …). It looks for PostSharp Laos custom attributes, and modifies the methods, types, and fields to which they are applied.

Image 1

To derive the maximum advantage from PostSharp Laos, it is necessary to fully understand the lifecycle of its custom attributes. They really have two lives: a first one at compile-time, inside PostSharp; and a second one at runtime.

The life cycle of a PostSharp Laos custom attribute is as follows:

  • At compile-time:
  1. For each application of the custom attribute, a new instance is created. Therefore, an instance of a custom attribute is always assigned to one and only one method, field, or type. Then, instances are initialized (method CompileTimeInitialize) and validated (method CompileTimeValidate).
  2. Custom attributes are serialized to a binary blob.
  3. They are stored in a managed resource of the output assembly.
  • At runtime:
  1. Custom attributes are deserialized from the managed resource, and each instance is initialized a second time (method RuntimeInitialize),
  2. Runtime methods (OnEntry, OnExit, …) are invoked as the method or field to which it is applied is invoked or accessed.

Okay, that's enough about the theory for now. Let's move on to the code!

Performance-Counter Custom Attribute

How do you monitor the performance of an application in a production environment where you cannot use your favorite profiler? Answer: you can use performance counters, but it requires a lot of plumbing code to instrument existing code. The solution is to encapsulate this plumbing code as an aspect, i.e., to make a custom attribute out of it, and then apply this custom attribute to every relevant method.

And what if we want to instrument a method located outside the current assembly? It's not a problem for PostSharp to apply a custom attribute to external declarations. However, since we can't modify the method, we have to intercept its call. So, instead of the OnMethodBoundary aspect, we will use the OnMethodInvocation:

C#
[Serializable]
public class PerformanceCounterAttribute : OnMethodInvocationAspect
{

    public override void OnInvocation( MethodInvocationEventArgs eventArgs )
    {
       // Our implementation goes here.
    }
}

In the eventArgs parameter, we get information about the method actually being called: eventArgs.Delegate is a delegate to the intercepted method, and eventArgs.GetArguments() gives the arguments passed to the method. PostSharp Laos expects us to put the return value in eventArgs.ReturnValue. Therefore, we can call the intercepted method as follows:

C#
eventArgs.ReturnValue = eventArgs.Delegate.DynamicInvoke(
                                             eventArgs.GetArguments() );

1. Measuring Performance

Our performance counter should count the number of invocations and should measure the time spent in the method. Since every instance of the custom attribute is associated with one and only one intercepted method, we can store the hit count and the elapsed time as instance fields of the custom attribute.

Based on these principles, here is our first working implementation:

C#
[Serializable]
public class PerformanceCounterAttribute : OnMethodInvocationAspect
{
    private long elapsedTicks;
    private long hits;

    public override void OnInvocation( MethodInvocationEventArgs eventArgs )
    {
        Stopwatch stopwatch = Stopwatch.StartNew();
         
        try
        {
            
            eventArgs.ReturnValue = eventArgs.Delegate.DynamicInvoke(
                                                  eventArgs.GetArguments() );
        }
        finally
        {
            stopwatch.Stop();
            Interlocked.Add( ref this.elapsedTicks, stopwatch.ElapsedTicks );
            Interlocked.Increment( ref this.hits );
        }
    }
}

2. Discovering Performance Counter Instances

It works, but how do we read the values? We simply have to expose fields in read-only public properties and build a repository of performance counter instances. We have to build this repository at runtime; therefore, we cannot do this registration in the instance constructor (which is called at compile-time), but must do it in the RuntimeInitialize method. One last thing: we have to expose the identity of the instrumented method; otherwise, how would we know to which method the performance counter relates? So, we must store the target method in a field and expose it in a read-only property. The RuntimeInitialize method is the right place to initialize this field.

Here is the code we have to add to the class:

C#
[NonSerialized] private MethodBase method;

private static readonly List<performancecounterattribute /> instances =
              new List<performancecounterattribute />();

public override void RuntimeInitialize( MethodBase method )
{
   base.RuntimeInitialize( method );
   this.method = method;
   instances.Add( this );
}
public MethodBase Method { get { return this.method; } }

public double ElapsedMilliseconds 
{ 
  get { return this.elapsedTicks/( Stopwatch.Frequency/1000d ); } 
}

public long Hits { get { return this.hits; } }

public static ICollection<performancecounterattribute /> Instances 
{ 
  get 
  { 
    return new ReadOnlyCollection<performancecounterattribute />( instances ); 
  } 
}

3. Done. Use it.

And that's all! We can now apply our custom attribute to the methods we want to instrument. Suppose we want to measure the time spent in the System.IO namespace. We will add a performance counter to these methods, using the following piece of code:

C#
[assembly: PerformanceCounter(AttributeTargetAssemblies = "mscorlib", 
           AttributeTargetTypes = "System.IO.*")]

And, if we instrument a small program listing the content of a folder, we get the following output:

Image 2

One of the things you should be aware of is that this aspect intercepts only calls made from the current assembly. So, if you call an external method that indirectly calls an instrumented method, this indirect call won't be monitored. This limitation is inherent to the technology used by PostSharp: MSIL rewriting.

Validating Fields With Custom Attributes

So far, we've seen how to modify method bodies and how to intercept method calls. PostSharp makes it possible to intercept "get" and "set" operations on fields. One of the applications of this technique involves the validation of fields: we could make a field non-nullable or enforce a regular expression just by decorating the field with a custom attribute.

Aspects that need to intercept field accesses should derive from the OnFieldAccessAspect class. They may override the OnGetValue() and OnSetValue() methods. For field validation, we are only interested in the second method. All we have to do is perform the validation that is specific to the validator.

1. Designing an Abstract Framework

The class design of a validation framework is simple: we have basically an abstract class FieldValidationAttribute exposing an abstract method Validate() called from the overridden OnSetValue() method. By contract, the implementation of Validate() should throw an ad-hoc exception if the method is invalid. The exception will typically include the field name. So, it would be fine for the FieldValidationAttribute class to expose the name of the field to which the custom attribute instance is applied. Since this information is known at compile-time, it is initialized in the CompileTimeInitialize() method and stored in a serializable field.

One thing we have to remember is that, just like OnMethodInvocationAspect, OnFieldAccessAspect intercepts accesses to a field; it is therefore limited to the current assembly. If you follow Microsoft's recommendation and have only private fields, this is not a problem. But if you prefer to have public fields, you should ask PostSharp Laos to encapsulate fields into properties. Just override the GetOptions() method and return GenerateProperty.

Here is the complete code for the FieldValidationAttribute class:

C#
[Serializable]
[AttributeUsage( AttributeTargets.Field, AllowMultiple = false )]
public abstract class FieldValidationAttribute : OnFieldAccessAspect
{
    private string fieldName;

    public override void CompileTimeInitialize( FieldInfo field )
    {
        base.CompileTimeInitialize( field );

        this.fieldName = field.DeclaringType.Name + "." + field.Name;
    }

    public string FieldName { get { return this.fieldName; } }

    protected abstract void Validate( object value );

    public override sealed void OnSetValue( FieldAccessEventArgs eventArgs )
    {
        this.Validate( eventArgs.ExposedFieldValue );

        base.OnSetValue( eventArgs );
    }

    public override OnFieldAccessAspectOptions GetOptions()  
    {
        return OnFieldAccessAspectOptions.GenerateProperty;
    }

}

2. Checking Non-Null Fields

The "non-nullable" field aspect is the most trivial:

C#
[Serializable]
public sealed class FieldNotNullAttribute : FieldValidationAttribute
{
    protected override void Validate( object value )
    {
        if ( value == null )
            throw new ArgumentNullException( "field " + this.FieldName );
    }
}

Defining a non-nullable field is as easy as this:

C#
class MyClass
{
  [FieldNotNull] 
  public string Name = "DefaultName";
}

3. Checking with Regular Expressions

A more challenging case is to design a custom attribute that checks regular expressions. The constructor of the custom attribute should accept the matching pattern as a parameter, as well as, optionally, a value indicating whether null values are acceptable for this field. If we want to avoid having to recompile the regular expression at each field assignment, we can store it as an instance field that we should initialize at runtime, in the RuntimeInitialize() method.

Here is a basic but working implementation of the pattern-matching field validator:

C#
[Serializable]
public sealed class FieldRegexAttribute : FieldValidationAttribute
{
    private readonly string pattern;
    private readonly bool nullable;
    private RegexOptions regexOptions = RegexOptions.Compiled;

    [NonSerialized]
    private Regex regex;

    public FieldRegexAttribute(string pattern, bool nullable)
    {
        this.pattern = pattern;
        this.nullable = nullable;
    }

    public FieldRegexAttribute(string pattern) : this(pattern, false)
    {
    }

    public RegexOptions RegexOptions
    {
        get { return regexOptions; } 
        set { regexOptions = value; }
    }

    public override void RuntimeInitialize(FieldInfo field)
    {
        base.RuntimeInitialize(field);
        this.regex = new Regex( this.pattern, this.regexOptions);
    }

    protected override void Validate( object value )
    {
        if ( value == null )
        {
            if ( !nullable )
            {
                throw new ArgumentNullException("field " + this.FieldName);
            }
        }
        else
        {
            string str = (string) value;
            if ( !this.regex.IsMatch( str ))
            {
                throw new ArgumentException( 
                    "The value does not match the expected pattern.");
            }
        }
    }
}

4. Done. Use it.

We're done! We have developed custom attributes that allow us to validate fields at runtime.

Their use is very straightforward:

C#
class MyClass
{
    [FieldNotNull] 
    public string Name = "DefaultName";
    
    [FieldRegex(@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|
                 (([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$")] 
    public string EmailAddress;
}

Look at the resulting assembly, using Lutz Roeder's Reflector:

Image 3

Fields have been encapsulated into properties, and if you look at the implementation of accessors, you'll see that our validating custom attributes have been invoked.

Simple. Powerful. What more could you want?

Summary

While the first article of this series introduced most of the key concepts of PostSharp Laos using a simple example based on OnMethodBoundaryAspect, the current article introduced two more aspects: OnMethodInvocation and OnFieldAccess.

We have seen the difference between OnMethodBoundaryAspect and OnMethodInvocationAspect: whereas the first actually adds a try-catch block on the target method, the second one intercepts method calls and does not modify the target method. This makes it possible to apply OnMethodInvocationAspect even on methods defined outside the current assembly. The first example exploited this feature to measure the time spent in the namespace System.IO.

The second example illustrated how to add behaviors to field behaviors. We have also seen how to generate a property around a field so that the behavior is also invoked from assemblies other than the current one.

Principally, I hope that you are now convinced that we can revisit the way we currently solve cross-cutting problems: Aspect-Oriented Programming is an elegant solution to most of these, and PostSharp Laos provides a simple and powerful technology.

Now, look at the three last projects you've worked on, and think about how much effort you could have saved with PostSharp…

License

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


Written By
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

 
QuestionCalling advised methods from outside of assembly Pin
jnoody3-Apr-09 8:07
jnoody3-Apr-09 8:07 
QuestionIssue with 3.5 and assembly entries Pin
Damon Overboe9-Mar-09 5:40
Damon Overboe9-Mar-09 5:40 
AnswerRe: Issue with 3.5 and assembly entries Pin
Gael Fraiteur9-Mar-09 5:45
Gael Fraiteur9-Mar-09 5:45 
QuestionRuntime Instrumentation Pin
Hendry Luk28-Aug-08 17:18
Hendry Luk28-Aug-08 17:18 
AnswerRe: Runtime Instrumentation Pin
Gael Fraiteur28-Aug-08 21:02
Gael Fraiteur28-Aug-08 21:02 
GeneralFantastic & Cacheable function attribute Pin
Tom Janssens7-Dec-07 3:40
Tom Janssens7-Dec-07 3:40 
GeneralRe: Fantastic & Cacheable function attribute Pin
Gael Fraiteur14-Dec-07 11:34
Gael Fraiteur14-Dec-07 11:34 
GeneralWow -- this is great Pin
Tom Hawkins12-Oct-07 4:43
Tom Hawkins12-Oct-07 4:43 
GeneralRe: Wow -- this is great Pin
Gael Fraiteur12-Oct-07 5:04
Gael Fraiteur12-Oct-07 5:04 
GeneralRe: Wow -- this is great Pin
Tom Hawkins12-Oct-07 5:14
Tom Hawkins12-Oct-07 5:14 
QuestionWhat about properties validation? Pin
Eduard Gomolyako21-Sep-07 2:29
Eduard Gomolyako21-Sep-07 2:29 
AnswerRe: What about properties validation? Pin
Gael Fraiteur21-Sep-07 2:39
Gael Fraiteur21-Sep-07 2:39 
GeneralGood Article...But Pin
P.Adityanand20-Sep-07 4:31
P.Adityanand20-Sep-07 4:31 
GeneralRe: Good Article...But Pin
Gael Fraiteur20-Sep-07 4:47
Gael Fraiteur20-Sep-07 4:47 
GeneralRe: Good Article...But Pin
mcory220-Sep-07 5:38
mcory220-Sep-07 5:38 
GeneralRe: Good Article...But Pin
mintxelas24-Sep-07 20:20
mintxelas24-Sep-07 20:20 

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

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