AOP
I am looking at the code for Silverlight application and there’s something that just bugs me… INotifyPropertyChanged. This is not the first time, and yet again I see this interface implemented again, and again, and again. This violates several good principles (Single Responsibility and Duplicated Code). It also pollutes the code with cross cutting concerns (change notification). My choice of solution for this is simple – buy, do not build. Yes, it is possible to write a base class that would scan for a custom attribute and will do the wiring. But why? Why not to look into something like PostSharp and take advantage of the hard work the author(s) put into it to make it work.
For myself a lot of times it was the pride – how come I will buy something that I can build myself. Well, satisfy the ego on a spike, and move on. AOP should solve problems, not introduce new ones. Understanding is important, and I will provide a simple example how AOP for simple tracing can be done. But the goal of the post is to encourage people to use dedicated tools to solve business problems, and not pride issues.
How AOP works? Poor mans’ explanation – IL re-write. Post-processing of the generated IL code. Let’s say I have a code that looks like this:
public sealed class Greeting { public string SayHello() { Console.WriteLine("Inside Greeting::SayHello() method"); return "Hello"; } }
I would like to trace each time the method SayHello is invoked (when we enter and about to leave it). The original MSIL looks like the following:
0000: nop
By using reflection (in my case I used Mono.Cecil) the original code is re-written into this: 0000: ldstr [TRACE] Started SayHello
Which is affectively equivalent to the following C# code: The output: [TRACE] Started SayHello
The quick and dirty code to re-write the original assembly: This is all great, but the true value in leveraging the tools created for this purpose to get the real value – resolve problems unique to your business efficiently. AOP is your friend, leverage it.
0001: ldstr Inside Greeting::SayHello() method
<br />0006:   call    System.Void System.Console::WriteLine(System.String)
<br />000B:   nop
<br />000C:   ldstr   Hello
<br />0011:   stloc.0
<br />0012:   br.s    0014
<br />0014:   ldloc.0
<br />0015:   ret</font></p>
0005: call System.Void System.Console::WriteLine(System.String)
<br />000A:   nop
<br />000B:   ldstr   Inside Greeting::SayHello() method
<br />0010:   call    System.Void System.Console::WriteLine(System.String)
<br />0015:   nop
<br />0016:   ldstr   Hello
<br />001B:   stloc.0
<br />001C:   br.s    001E
<br />001E:   ldloc.0
<br />001F:   ldstr   [TRACE] Finished SayHello
<br />0024:   call    System.Void System.Console::WriteLine(System.String)
<br />0029:   ret</font></p>
public sealed class Greeting
{
public string SayHello()
{
Console.WriteLine("[TRACE] Started SayHello");
Console.WriteLine("Inside Greeting::SayHello() method");
Console.WriteLine("[TRACE] Finished SayHello");
return "Hello";
}
}
Inside Greeting::SayHello() method
[TRACE] Finished SayHellovar var assembly = AssemblyFactory.GetAssembly(assemblyFilename);
var type = assembly.MainModule.Types["Library.Greeting"];
var method = type.Methods.OfType<MethodDefinition>()
.Where(x => x.Name.Equals("SayHello") && x.Parameters.Count.Equals(0))
.Single();
var worker = method.Body.CilWorker;
var trace = worker.Create(OpCodes.Ldstr, "[TRACE] Started " + method.Name);
var writeLineMethod = assembly.MainModule.Import(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }));
worker.InsertBefore(method.Body.Instructions[0], trace);
worker.InsertAfter(trace, worker.Create(OpCodes.Call, writeLineMethod));
trace = worker.Create(OpCodes.Ldstr, "[TRACE] Finished " + method.Name);
worker.InsertAfter(method.Body.Instructions[method.Body.Instructions.Count - 2], trace);
worker.InsertAfter(trace, worker.Create(OpCodes.Call, writeLineMethod));
AssemblyFactory.SaveAssembly(assembly, assemblyFilename);