Monday, September 13, 2010 10:11 PM Sean Feldman

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
0001:   ldstr   Inside Greeting::SayHello() method
0006:   call    System.Void System.Console::WriteLine(System.String)
000B:   nop
000C:   ldstr   Hello
0011:   stloc.0
0012:   br.s    0014
0014:   ldloc.0
0015:   ret

By using reflection (in my case I used Mono.Cecil) the original code is re-written into this:

0000:   ldstr   [TRACE] Started SayHello
0005:   call    System.Void System.Console::WriteLine(System.String)
000A:   nop
000B:   ldstr   Inside Greeting::SayHello() method
0010:   call    System.Void System.Console::WriteLine(System.String)
0015:   nop
0016:   ldstr   Hello
001B:   stloc.0
001C:   br.s    001E
001E:   ldloc.0
001F:   ldstr   [TRACE] Finished SayHello
0024:   call    System.Void System.Console::WriteLine(System.String)
0029:   ret

Which is affectively equivalent to the following C# code:

 

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";
  }
}

The output:

[TRACE] Started SayHello
Inside Greeting::SayHello() method
[TRACE] Finished SayHello

 

The quick and dirty code to re-write the original assembly:

var 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);

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.

Filed under: , , ,

Comments

# re: AOP

Tuesday, September 14, 2010 9:11 AM by AndrewSeven

I worked on a project where post sharp was used and it made the compilation process really long, so we used some regex to do the transformations by hand.

# re: AOP

Tuesday, September 14, 2010 9:52 AM by Sean Feldman

@Andrew,

Yes, the post processing slows things down. Yet the new version of PostSharp, version 2 (I used 1.5) is supposed to be faster. Also, I would re-evaluate the the speed approach: compilers are getting faster, hardware is faster, parallel stuff kicks in. Is that a really concern? It also depends a lot on the project size.

Interesting information about RegEx and manual transformation. What did you manipulate with regex?