A while back (actually, it's been a long while) I found an interesting question on Stackoverflow.

The question was about adding logging to method calls automatically. Or to rephrase: the question was basically about handling aspect-oriented programming using Mono.Cecil in .NET.

So given this piece of code:

public class Target
  // Target method that needs to be logged
  public void Run(int arg0, string arg1)
     Console.WriteLine("Run method body");

public static class Trace
  // This is the logger method that must be called at the beginning of other methods
  public static void LogEntry(string methodName, object[] parameters)
    Console.WriteLine("******Entered in " + methodName + " method.***********");

This is the piece of code (complete with comments) that achieves the goal:

public class Sample
  private readonly string _targetFileName;
  private readonly ModuleDefinition _module;

  public ModuleDefinition TargetModule { get { return _module; } }

  public Sample(string targetFileName)
    _targetFileName = targetFileName;

    // Read the module with default parameters
    _module = ModuleDefinition.ReadModule(_targetFileName);

  public void Run(string type, string method)

    // Retrive the target class. 
    var targetType = _module.Types.Single(t => t.Name == type);

    // Retrieve the target method.
    var runMethod = targetType.Methods.Single(m => m.Name == method);    

    // Get log entry method ref to create instruction
    var logEntryMethodReference = _module.Types.Single(t => t.Name == "Trace").Methods.Single(m => m.Name == "LogEntry");

    var newInstructions = new List<Instruction>();

    // Create variable to hold the array to be passed to the LogEntry() method       
    var arrayDef = new VariableDefinition(new ArrayType(_module.TypeSystem.Object));
    // Add variable to the method
    // Get a ILProcessor for the Run method
    var processor = runMethod.Body.GetILProcessor();
    // Load to the stack the number of parameters         
    newInstructions.Add(processor.Create(OpCodes.Ldc_I4, runMethod.Parameters.Count));               
    // Create a new object[] with the number loaded to the stack           
    newInstructions.Add(processor.Create(OpCodes.Newarr, _module.TypeSystem.Object)); 
    // Store the array in the local variable
    newInstructions.Add(processor.Create(OpCodes.Stloc, arrayDef)); 

    // Loop through the parameters of the method to run
    for (int i = 0; i < runMethod.Parameters.Count; i++)
      // Load the array from the local variable
      newInstructions.Add(processor.Create(OpCodes.Ldloc, arrayDef)); 
      // Load the index
      newInstructions.Add(processor.Create(OpCodes.Ldc_I4, i)); 
      // Load the argument of the original method (note that parameter 0 is 'this', that's omitted)
      newInstructions.Add(processor.Create(OpCodes.Ldarg, i+1)); 

      if (runMethod.Parameters[i].ParameterType.IsValueType)
        // Boxing is needed for value types
        newInstructions.Add(processor.Create(OpCodes.Box, runMethod.Parameters[i].ParameterType)); 
        // Casting for reference types
        newInstructions.Add(processor.Create(OpCodes.Castclass, _module.TypeSystem.Object)); 
      // Store in the array
    // Load the method name to the stack
    newInstructions.Add(processor.Create(OpCodes.Ldstr, method)); 
    // Load the array to the stack
    newInstructions.Add(processor.Create(OpCodes.Ldloc, arrayDef)); 
    // Call the LogEntry() method
    newInstructions.Add(processor.Create(OpCodes.Call, logEntryMethodReference)); 
    // Add the new instructions in referse order
    foreach (var newInstruction in newInstructions.Reverse<Instruction>()) 
      var firstInstruction = runMethod.Body.Instructions[0];
      processor.InsertBefore(firstInstruction, newInstruction);

    // Write the module with default parameters

Hopefully the comments give you a general idea about how to use Mono.Cecil to implement AOP, but if you have any questions, feel free to comment.

Adding logging using Mono.Cecil
Share this