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.
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:
- 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
).
- Custom attributes are serialized to a binary blob.
- They are stored in a managed resource of the output assembly.
- Custom attributes are deserialized from the managed resource, and each instance is initialized a second time (method
RuntimeInitialize
),
- 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
:
[Serializable]
public class PerformanceCounterAttribute : OnMethodInvocationAspect
{
public override void OnInvocation( MethodInvocationEventArgs eventArgs )
{
}
}
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:
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:
[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:
[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:
[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:
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:
[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:
[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:
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:
[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:
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:
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…