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

<br />0006:&#160;&#160; call&#160;&#160;&#160; System.Void System.Console::WriteLine(System.String)

<br />000B:&#160;&#160; nop

<br />000C:&#160;&#160; ldstr&#160;&#160; Hello

<br />0011:&#160;&#160; stloc.0

<br />0012:&#160;&#160; br.s&#160;&#160;&#160; 0014

<br />0014:&#160;&#160; ldloc.0

<br />0015:&#160;&#160; ret</font></p>

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)

<br />000A:&#160;&#160; nop

<br />000B:&#160;&#160; ldstr&#160;&#160; Inside Greeting::SayHello() method

<br />0010:&#160;&#160; call&#160;&#160;&#160; System.Void System.Console::WriteLine(System.String)

<br />0015:&#160;&#160; nop

<br />0016:&#160;&#160; ldstr&#160;&#160; Hello

<br />001B:&#160;&#160; stloc.0

<br />001C:&#160;&#160; br.s&#160;&#160;&#160; 001E

<br />001E:&#160;&#160; ldloc.0

<br />001F:&#160;&#160; ldstr&#160;&#160; [TRACE] Finished SayHello

<br />0024:&#160;&#160; call&#160;&#160;&#160; System.Void System.Console::WriteLine(System.String)

<br />0029:&#160;&#160; ret</font></p>

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.

2 Comments

  • 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.

  • @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?

Comments have been disabled for this content.