Interception in .NET – Part 3: Static Interception
Updated: see the final post in the series here.
Introduction
Part three of a series on interception in the .NET world. See the first part here and the second here.
This time I’m going to talk about static interception using PostSharp as an example. Static interceptors use information provided at code writing time – aspects – to do IL weaving. This basically means that after the .NET assembly is compiled, PostSharp rewrites the produced IL to apply the changes intended by the code developer, such as method interception. This is a good thing, as you can intercept types and methods regardless or not if they are virtual, static, implement some interface, etc.
This won’t be a full coverage of PostSharp, of course – which has far more than just interception - , but I include it in this series so that we see how it compares to other techniques.
Example Using PostSharp
Imagine you want to retry a certain method a number of times, this may be because what it does is sensible to network failures, or any other reason. In PostSharp, you would write a method interceptor aspect as this:
[PSerializable]
public class RetryOnExceptionAttribute : MethodInterceptionAspect
{
public int MaxRetries { get; set; }
public override void OnInvoke(MethodInterceptionArgs args)
{
int retriesCounter = 0;
while ( true )
{
try
{
base.OnInvoke(args);
return;
}
catch (Exception e)
{
retriesCounter++;
if (retriesCounter > this.MaxRetries) throw;
Console.WriteLine(
"Exception during attempt {0} of calling method {1}.{2}: {3}",
retriesCounter, args.Method.DeclaringType, args.Method.Name, e.Message);
}
}
}
}
Note that you need to reference the Nuget package PostSharp.Patterns.Diagnostics, or any other that contains the PostSharp core assemblies.
And you could then apply it to some method:
public class CustomerService
{
[RetryOnException(MaxRetries = 5)]
public void Save(Customer customer)
{
// Database or web-service call.
}
}
After Visual Studio (or, rather, MSBuild), builds your assembly, PostSharp pops in: it reads definitions present in the PostSharp.Custom.targets file and modifies that assembly to call the RetryOnExceptionAttribute’s OnInvoke method around the Customer.Save method, because the aspect was applied to it.
The MethodInterceptionAspect is not the only aspect class, there’s also:
-
MethodInterceptionAspect: use this to have full control over the target method invocation, that is, run code before, after or instead of it, and change the return value, if any
-
OnMethodBoundaryAspect: use this to have code run before and/or after a method, or in the case of an exception
-
BackgroundAttribute: to make a method run in the background (different thread)
-
DispatchedAttribute: have a method run in the User Interface thread
-
LogAttribute: trace (log) the method's calls
-
LogExceptionAttribute: logs exceptions that are thrown by the target method
After PostSharp modifies the assembly, it will no longer resemble your code: if you decompile it, you will see other stuff in there, like, a call to RetryOnExceptionAttribute.OnInvoke. Something like this:
[DebuggerTargetMethod(0x600000d), DebuggerBindingMethod(0x6000010)]
public void Save(Customer customer)
{
try
{
Arguments<Customer> arguments = new Arguments<Customer> {
Arg0 = customer
};
MethodInterceptionArgsImpl args = new MethodInterceptionArgsImpl(this, arguments) {
DeclarationIdentifier = new DeclarationIdentifier(0x78acef3000030000L),
Method = <>z__a_1._2,
TypedBinding = <Save>c__Binding.singleton
};
<>z__a_1.a1.OnInvoke(args);
}
finally
{
}
}
Although hard to understand, rest assured that it does what you expect! There are a bunch of additional types generated, but it’s really irrelevant to look at them.
Alternatives to PostSharp
There are some commercial alternatives and then there’s Mono.Cecil. Implemented as part of the Mono project – meaning, free and open source - it allows you to do low level IL weaving, but you should know what you are doing as there are none of the nice wrappers that PostSharp provides.
There are some interesting projects that make Mono.Cecil easier to use, and one of them is Fody. It is basically a wrapper around it, and is also open source. If you want to do things yourself, give it a try!
Conclusion
PostSharp is very powerful, as is IL weaving in general, but sometimes we need more than static interception. Here’s where dynamic interception kicks in. In the next post, I will start describing a framework for dynamic interception with a number of implementations. Stay tuned!