Click here to Skip to main content
Click here to Skip to main content
Go to top

Introducing LinFu, Part VI: LinFu.AOP – Pervasive Method Interception and Replacement for Sealed Types in Any .NET Language

, 8 Feb 2008
Rate this:
Please Sign up or sign in to vote.
Using MSBuild and Mono.Cecil to dynamically intercept static and final methods of nearly any type, including sealed types. No proxies required.

Note: to access the latest sources for the LinFu Framework directly from the SVN, click here.

Introduction

In this part of the series, I’ll show how you can use LinFu.AOP to dynamically intercept any method on any type, regardless of whether or not the method is declared virtual or non-virtual. You’ll also be able to dynamically intercept (and even replace) methods declared on sealed types, in addition to any static methods that are declared on that type.

Background

In Part I, I mentioned that one of LinFu.DynamicProxy’s limitations is that it doesn’t allow you to intercept methods which are non-virtual, static or declared on a sealed type. Unfortunately, that was one of the limits of using System.Reflection.Emit: it would only extend a method at runtime with your own custom behavior if and only if the target method is a virtual method declared on a non-sealed type. For example, the following class cannot be intercepted by LinFu.DynamicProxy:

public sealed class Person
{
   public void Greet()
   {
      Console.WriteLine(“HA! You can’t override me!”);
   }
}

For most IL developers, that’s pretty much where the story ends. There doesn’t seem to be any way to get around this limitation — unless, of course, you’re working with Mono.Cecil. Cecil allows you to modify the instructions of any method in any class within a given .NET assembly, regardless of whether the type is sealed or not. In this article, I’ll show you exactly how LinFu.AOP uses Cecil to get around LinFu.DynamicProxy’s limitations.

Before I begin, however, there are a few basic questions that I need to answer, namely this: “What exactly is Aspect-Oriented Programming?”

Death by a Thousand Crosscuts

In order to understand exactly what AOP is, we first need to examine the problem that it’s trying to solve. In any given object-oriented application, there could be a multitude of dependencies that exist between each one of its classes. For example, let’s suppose that I had a BankAccount class that needed to log all of its transactions to an external file:

public class BankAccount
{
    public ILogger Logger { get; set; }
    public void Withdraw(double amount)
    {
       // ...

       if (Logger == null)
          return;
       // Log the transaction
       Logger.LogMessage(“Withdrew {0}”, amount);
    }
    public void Deposit(double amount)
    {
       // ...

       if (Logger == null)
          return;
       // Log the deposit
       Logger.LogMessage(“Deposited {0}”, amount);
    }
}

…and for the sake of reference, let’s suppose that the ILogger interface looks like this:

public interface ILogger
{
   void LogMessage(string format, params object[] args);
}

As you can see, the BankAccount class heavily depends on the ILogger instance to record every transaction that occurs during the BankAccount class’ lifetime. The problem is that as the application grows, more and more of its classes will come to depend on the same ILogger instance, making it more difficult to maintain the application. It’s trivial if the logger is used only in two methods in your application, but what if that same logger were embedded in two thousand methods?

Crosscutting Concerns

When you have two thousand instances of a single dependency scattered across different parts of your application, it’s safe to say that there is a huge dependency problem that we need to fix. There has to be a way to eliminate those dependencies and still be able to use the ILogger instance to log the results of the application. This is the crux of the dilemma that AOP aims to solve. On one hand, we have a dependency that is so pervasive that it poses a maintenance problem. On the other hand, we need to have that same dependency in place in order to meet a particular business requirement (which is logging, in this case). This is a classic example of a crosscutting concern and this is exactly the problem that we’re trying to fix.

Catch-22

Using traditional structural and object-oriented programming techniques will only solve one of the two problems posed in this dilemma. We can either remove the dependency to make it easier to maintain or we can simply leave it in place so that it can fulfill its business requirement. No amount of refactoring or design pattern trickery will fix this problem, because any code used to replace the ILogger dependency will simply swap one code dependency for another one, leaving us back at the same place we started. If conventional techniques fail to solve this problem, then how would AOP go about fixing the maintenance nightmare?

Three Questions

There are three essential questions that we have to ask ourselves when examining this dependency problem: what, where and when. We need to know what the dependency is, where it should be placed and when it should be executed. In the previous example, the ILogger instance is the pervasive dependency (or crosscutting concern) that we’re trying to solve. To use the ILogger dependency, we need to be able to dynamically define what should execute in place of the ILogger instance, as well as where and when the same logger should execute. To make the application easier to maintain, we need to remove all two thousand of the repetitive calls to the logger from the application itself. Once these two issues have been solved, the dilemma effectively solves itself.

Spaghetti, Anyone?

At first, this task might seem impossible. After all, how does one use code in two thousand places if it isn’t there to be called? If the bank account itself is redefined as:

public class BankAccount
{
    public void Withdraw(double amount)
    {
       // ...
    }
    public void Deposit(double amount)
    {
       // ...
    }
}

…then how can it possibly use the logger?

Defining the Three

The first thing that we need to do is define the behavior that will be pervasively used throughout the application itself. In this case, we need to isolate the ILogger dependency into something that AOP pundits call an advice. An advice is simply behavior that is executed in response to whatever events might occur in your application, such as a method call or a change in a property value. In this case, we need to log the results of the Deposit() and Withdraw() methods. In order to do that, we need to have the logger execute itself every time those two methods are called. AOP languages (such as AspectJ) allow you to define advices in this manner and they allow these same advices to be placed at specific predefined points in any given program. These points are known as join points. Some examples of join points are:

Method Calls
Field Reads/Writes
Property Getters/Setters
Object Initialization 
Type Initialization
The Join Point Model

LinFu.AOP, in particular, supports the following join points:

Object Initialization
Type Initialization
Method Calls
Property Getters/Setters

Note: this article will only focus on adding advices to method-based join points such as property getters/setters and standard method calls. Since discussing method join points is a fairly comprehensive subject, I’ll handle type and object initialization in another subsequent article in this series.

Before, Around and After

Typically, AOP allows you to decide not only where (read: join points) you should place the advices. It also allows you to decide when these advices should execute in relation to a join point. In general, AOP lets you execute your advices before, after and around (a combination of both before and after) a particular join point. In our case, we need to log each transaction in the ILogger instance after each BankAccount completes a successful transaction.

Placing the Logger

In order to execute the logger after each one of its two thousand hypothetical uses without the burden of having two thousand repetitive dependencies in the BankAccount class, there has to be some form of syntax (in VB.NET or C#) that allows us to dynamically add or remove the logger calls to those specific join points at will. In theory, if we were somehow able to introduce those two thousand dependencies dynamically at runtime, this would automatically save us the burden of having to maintain it in the source code. It would also still help us meet the business requirement of being able to log each transaction as it occurs in the BankAccount class.

Unfortunately, there is currently no native language feature available in the mainstream .NET languages (such as C# and VB.NET) that allows you to introduce (or "weave") advices into your code at runtime. There are numerous third-party AOP libraries out there (such as PostSharp) that attempt to alleviate this problem, but in my opinion, they either didn’t meet my needs for simplicity or they simply didn’t provide me with the runtime weaving capabilities that I needed for my applications.

Dynamic Proxies to the Rescue?

There were, of course, various dynamic proxy libraries available (such as LinFu.DynamicProxy or even Castle’s DynamicProxy) that provided some of the weaving functionality that I needed, but they could only modify virtual methods. Non-virtual and static methods could not be modified or intercepted, and sealed types (by definition) were completely off-limits.

The Premise

I needed a library that was so flexible that it could dynamically intercept nearly any method at runtime, regardless of whether that method was non-virtual, static or declared on a sealed type. I also needed the ability to dynamically "surround" any method with my own custom advices (or behavior) as the application was running. In addition, I needed to be able to dynamically replace any method implementation defined on any one of my classes at will, all at runtime. Lastly, it had to be be simple enough that nearly anyone could learn how to use it within a matter of minutes. With those ideas in mind, I set out to create my own library and thus, LinFu.AOP was born.

Features and Usage

A Simple Demonstration

Let’s suppose that I have the following class definition located in SampleLibrary.dll:

// Notice that we’re actually going to modify a sealed type 
public sealed class Person
{
    public void Speak()
    {
       Console.WriteLine(“Hello, World!”);
    }
}

...and let’s suppose that I wanted to dynamically modify the Speak()method at runtime to display a custom message, in addition to the original implementation, so that it would look something like this:

public sealed class Person
{
    public void Speak()
    {
       // Add the additional Console.WriteLine() call
       Console.WriteLine(“Hello, CodeProject”);

       // --Begin Original Implementation--
       Console.WriteLine(“Hello, World!”);
       // --End Original Implementation--
    }
}

The first thing that we need to do is use LinFu.AOP to modify SampleLibrary.dll so that all of the Person class’ methods can be dynamically intercepted (or replaced) at runtime. In general, LinFu.AOP gives you two options for modifying your assembly for interception. You can either use it as a custom MSBuild task as part of your projects or you can use PostWeaver.exe to manually modify it using the command line. I’ll go over each option in the following sections:

Using LinFu.AOP with MSBuild

In order to use LinFu.AOP with MSBuild, we need to modify SampleLibrary.csproj to run LinFu.AOP after each build. The first step is to unload the project, as shown in the following image:

Once the project has been unloaded, the next thing that we need to do is manually edit the MSBuild project file by clicking on "Edit SampleLibrary.csproj":

At this point, you should see the SampleLibrary.csproj file open up in the Visual Studio 2008 code window. Next, we need to add the following lines to SampleLibrary.csproj:

<PropertyGroup>
<PostWeaveTaskLocation>
    C:\TheDirectoryWhereLinFuAOPIsLocated\LinFu.Aop.Tasks.dll</PostWeaveTaskLocation>
</PropertyGroup>
<UsingTask TaskName="PostWeaveTask" AssemblyFile="$(PostWeaveTaskLocation)" />
<Target Name="AfterBuild">
<PostWeaveTask TargetFile=
    "$(MSBuildProjectDirectory)\$(OutputPath)$(MSBuildProjectName).dll" 
    InjectConstructors="true" />
</Target>

The only thing that you need to change in the listing above is to make the PostWeaveTaskLocation element point to the directory where you have LinFu.AOP installed. Next, we need to save the file and reload the project:

Once the project has been reloaded, all we have to do is rebuild the solution. LinFu.AOP will silently make all the necessary changes to your project at post-build time, making the entire process completely transparent.

Using PostWeaver.exe

If (for some reason) you don’t have direct access to the source code for your target library, LinFu.AOP also allows you to modify your libraries using PostWeaver.exe. The syntax for PostWeaver.exe is:

PostWeaver.exe [filename]
One at a Time

For simplicity’s sake, PostWeaver.exe only modifies a single assembly at a time. In this case, we need to modify SampleLibrary.dll, so the command for modifying SampleLibrary.dll is:

PostWeaver.exe c:\YourPath\SampleLibrary.dll

Additional Dependencies

Once PostWeaver.exe (or the LinFu.AOP custom MSBuild task) has modified the target assembly, we’ll need to copy LinFu.AOP.Interfaces.dll to the target directory. This part of the process is pretty self-explanatory. If you used the LinFu.AOP MSBuild task in your project, however, you can skip this extra step by adding a reference to LinFu.AOP.Interfaces.dll. Once the project has been rebuilt, the interface assembly will be automatically copied to your target directory.

Adding Your Own Custom Behavior at Runtime

LinFu.AOP supports two styles of method interception. You can either replace an entire method implementation at runtime or you can surround almost any method with your own custom behavior. Depending on which option you choose, there is a certain set of interfaces that you need to implement in order to customize the behavior of a particular class or set of objects. These interfaces (located in LinFu.AOP.Interfaces.dll) are:

// Implement these two interfaces if you want to ‘surround’ 
// a particular method with custom behavior
public interface IAroundInvoke
{
    void AfterInvoke(IInvocationContext context, object returnValue);
    void BeforeInvoke(IInvocationContext context);
}

public interface IAroundInvokeProvider
{        
    IAroundInvoke GetSurroundingImplementation(IInvocationContext context);        
}

// Implement these two interfaces if you want to replace a particular method
public interface IMethodReplacement
{
    object Invoke(IInvocationContext context);
}

public interface IMethodReplacementProvider
{
    bool CanReplace(IInvocationContext context);
    IMethodReplacement GetMethodReplacement(IInvocationContext context);
}

As the name suggests, the IAroundInvoke interface is responsible for adding behavior "around" a particular method implementation, while an IMethodReplacement instance is responsible for replacing a particular method body. Before we get into the details of implementing each interface, however, we need to understand the IInvocationContext interface.

Contexts and Interfaces of Interest

The IInvocationContext interface captures all of the details of the method call at the call site. It is simply defined as:

public interface IInvocationContext
{
    object Target { get; }
    MethodInfo TargetMethod { get; }
    MethodInfo CallingMethod { get; }
    StackTrace StackTrace { get; }
    Type[] TypeArguments { get; }
    Type[] ParameterTypes { get; }
    Type ReturnType { get; }
    object[] Arguments { get; }
}

Most of the properties here are fairly self-explanatory, except for the ParameterTypes property. The ParameterTypes property is simply an array of System.Type objects that describe the type for each one of the parameters for the currently executing method. This is handy if the target method contains any parameters that are generic type arguments. Once a target method with generic parameter types is called, LinFu.AOP will resolve each one of the method’s generic parameter types and place them into the ParameterTypes property.

Default Provider Implementations

Both the IAroundInvokeProvider and IMethodReplacementProvider interfaces allow you to dynamically decide which instances of IAroundInvoke or IMethodReplacement should be injected into the currently executing method, using an IInvocationContext instance. Fortunately, LinFu.AOP provides default implementations for both types of providers with the SimpleAroundInvokeProvider and the SimpleMethodReplacementProvider classes:

public class SimpleAroundInvokeProvider : IAroundInvokeProvider 
{
    private IAroundInvoke _around;
    private Predicate<IInvocationContext> _predicate;
    public SimpleAroundInvokeProvider(IAroundInvoke around)
    {
        _around = around;
    }
    public SimpleAroundInvokeProvider(IAroundInvoke around, 
        Predicate<IInvocationContext> predicate)
    {
        _around = around;
        _predicate = predicate;
    }

    public Predicate<IInvocationContext> Predicate
    {
        get { return _predicate;  }
        set { _predicate = value; }
    }

    #region IAroundInvokeProvider Members

    public IAroundInvoke GetSurroundingImplementation(IInvocationContext context)
    {
        if (_predicate == null)
            return _around;

        // Apply the predicate, if possible
        if (_predicate(context))
            return _around;

        return null;
    }

    #endregion
}

public class SimpleMethodReplacementProvider : BaseMethodReplacementProvider
{
    public SimpleMethodReplacementProvider(IMethodReplacement replacement)
    {
        MethodReplacement = replacement;
    }
    public Predicate<IInvocationContext> MethodReplacementPredicate
    {
        get;
        set;
    }
    public IMethodReplacement MethodReplacement
    {
        get;
        set;
    }
    protected override bool ShouldReplace(IInvocationContext context)
    {
        if (MethodReplacementPredicate == null)
            return true;

        return MethodReplacementPredicate(context);
    }
    protected override IMethodReplacement GetReplacement(IInvocationContext context)
    {
        return MethodReplacement;
    }
}

Both providers should be able to handle eighty percent of all cases. However, should you run into a case that falls into the twenty percent category, it’s practically trivial to provide your own implementation of each interface.

Surrounding the Speak Method

Since we’re only going to add a simple Console.WriteLine() call to the beginning of the Person.Speak() method, all we need to do is provide an implementation of IAroundInvoke with the matching Console.WriteLine() call:

public class AroundSpeakMethod : IAroundInvoke     
{
    #region IAroundInvoke Members

    public void AfterInvoke(IInvocationContext context, object returnValue)
    {
        // Do nothing
    }

    public void BeforeInvoke(IInvocationContext context)
    {
        Console.WriteLine("Hello, CodeProject!");
    }

    #endregion
}

LinFu.AOP allows you to "surround" nearly any given method with the IAroundInvoke implementations that you supply to the modified method itself. At this point, we can either apply the AroundSpeakMethod class to a particular instance of the Person class, or we can change the method implementation so that AroundSpeakMethod will apply to all instances of the Person class. I’ll discuss each option in the following sections:

Instance-based Custom Behaviors

In order to intercept the Speak() method on a particular instance of the Person class, we first need to bind IAroundInvokeProvider to the target instance that we’re going to intercept by using the IModifiableType interface:

public interface IModifiableType
{
    bool IsInterceptionEnabled { get; set; }
    IAroundInvokeProvider AroundInvokeProvider { get; set; }
    IMethodReplacementProvider MethodReplacementProvider { get; set; }
}

This interface allows LinFu.AOP users to customize the behavior of nearly any class on a per-instance basis. All types modified by LinFu.AOP will automatically implement this interface. In our case, we’re going to surround the Speak() method of a particular Person instance by attaching our AroundSpeakMethod instance to a particular Person instance in the following example:

Person person = new Person();

// Determine if the type has been modified
IModifiableType modified = person as IModifiableType;
if (modified != null)
{
    // Create the provider that will determine which methods need to be intercepted
    IAroundInvokeProvider provider = 
        new SimpleAroundInvokeProvider(new AroundSpeakMethod(), 
        c => c.TargetMethod.Name == "Speak");

    // Enable interception on this instance since it’s disabled by default
    modified.IsInterceptionEnabled = true;

    // Assign the provider to this particular instance
    // so we can intercept the Speak() method
    modified.AroundInvokeProvider = provider;
}
// Say hello to CP using an IAroundInvoke instance
person.Speak();

As you can see in the example above, most of the code is self-explanatory. SimpleAroundInvokeProvider will decide which methods need to be surrounded, and the AroundSpeakMethod instance will add the custom “Hello, CodeProject!” message every time the Speak() method is called on that particular person instance. But what if we need to surround the Speak() method on all instances of the Person class?

Adding Class-wide Behaviors

The process for intercepting the Speak() method across all Person instances is similar to intercepting individual instances of the Person class:

// Note: The differences are highlighted in bold
var provider = new SimpleAroundInvokeProvider(new AroundSpeakMethod(),
        c => c.TargetMethod.Name == "Speak");

AroundInvokeRegistry.Providers.Add(provider);

// Create the first person
var first = new Person();
first.EnableInterception();
Console.WriteLine("First Person: ");
first.Speak();

Console.WriteLine("Second Person: ");

// Create the second person
var second = new Person();
second.EnableInterception();
second.Speak();

The first thing that you might notice in the above example is the static method call to the AroundInvokeRegistry.Providers property getter. In the example above, we needed to register SimpleAroundInvokeProvider with AroundInvokeRegistry in order to intercept all instances of the Person class. Once an IAroundInvokeProvider instance has been registered with AroundInvokeRegistry, the newly-registered provider will have the ability to inject any custom IAroundInvoke instance into any method of any type that has been modified by LinFu.AOP.

Think about that for a moment. LinFu.AOP allows you to change nearly any method in your program at runtime with only a few lines of code. Needless to say, the kind of power this affords is quite staggering and, again, I’ll leave it in the capable hands of the readers to decide what to do with it.

Enabling Interception?

The next thing that you might be wondering about is the call to the EnableInterception() extension method. It is simply defined as:

public static class ObjectExtensions
{
    public static T As<T>(this object target)
        where T : class
    {
        T result = null;

        if (target is T)
            result = target as T;

        return result;
    }
    public static void EnableInterception(this object target)
    {
        IModifiableType modified = target.As<IModifiableType>();
        if (modified == null)
            return;

        modified.IsInterceptionEnabled = true;
    }
}
Caveat Emptor

Given the power of pervasive method interception that LinFu.AOP affords, I took the least obtrusive option and left interception disabled by default. This means that (for the most part) LinFu.AOP will only intercept methods on instances where the IsInterceptionEnabled property is set to true. When the IsInterceptionEnabled property on a particular class instance is set to false, the modified type will behave as if the method interception does not exist. If you need to enable instance-level interception, then the extension methods above will do that for you. Again, the choice is yours.

Intercepting Static Methods

In addition to intercepting instance methods, LinFu.AOP also supports static method interception. For example, let’s suppose that I wrote a static version of the Speak() method:

public sealed class Person
{
   // ...
   public static void Speak()
   {
      Console.WriteLine(“Static Speak Method: Hello, World!”);
   }
}

To add your own custom behavior to the static Speak() method, all you have to do is something like this:

// Create the provider, and register it
var provider = new SimpleAroundInvokeProvider(new AroundSpeakMethod(),
        c => c.TargetMethod.Name == "Speak");

AroundInvokeRegistry.Providers.Add(provider);

// Call the static version
Person.Speak();

// The output will be:
// “Hello, CodeProject!”
// “Static Speak Method: Hello, World!”

As you can see, the process for registering IAroundInvokeProvider with AroundInvokeRegistry is the same as the one given in previous examples. In fact, the only difference in the example above is that I actually called the static Speak() method rather than creating a new instance of the Person class and calling its instance-based Speak() method. Other than that, intercepting static methods is as easy as intercepting instance methods, given that the process for intercepting both types of methods is one and the same.

What about IMethodReplacement?

At this point, you might be wondering why I didn’t mention how to replace methods using the IMethodReplacement and the IMethodReplacementProvider interfaces. Suffice to say, the process is similar to what we did with the IAroundInvoke and the IAroundInvokeProvider interfaces. Not to worry, though — once we dive into the actual implementation in the next section, I’ll show you how to replace method bodies with the IMethodReplacement interface.

Pure Transparency

What makes this interesting is that clients that use this particular Person instance won’t even be aware that the Person class has been modified at all. Normally, this sort of custom behavior can only be done using dynamic proxies, but dynamic proxies can’t override sealed types. LinFu.AOP is (to some extent) a very powerful form of pervasive method interception because it isn’t subject to the type restrictions associated with dynamic sub-classing (aka dynamic proxies). It can intercept both instance and static methods on sealed types because (unlike the traditional dynamic proxy approach) it actually modifies those methods to support pervasive method interception. If you’re interested in learning how this works, then read on and I’ll show you the gory details without diving into the IL.

Under the Hood

Like other AOP frameworks such as PostSharp, LinFu.AOP modifies your compiled assemblies at post-build time. However, unlike those other frameworks, LinFu’s approach is more flexible because the actual code injection occurs at runtime. It uses Mono.Cecil to modify the method body of each public method in your target assembly so that each method can be intercepted or modified at will, all at runtime, as shown in the following diagram:

Before and After

The first thing that you might notice in the above diagram is that LinFu adds a method prolog and epilog as part of the changes to the modified method. When a modified method executes, it searches for an instance of the following interface:

public interface IAroundInvoke
{
    void AfterInvoke(IInvocationContext context, object returnValue);
    void BeforeInvoke(IInvocationContext context);
}

The IAroundInvoke interface instance will be used to add any additional behavior "around" the original method implementation itself. Notice that there are not one, but two levels of IAroundInvoke instances that are invoked every time the method is called. The instance level IAroundInvoke allows you to surround methods belonging to a particular type on a per-instance basis, while the class-level IAroundInvoke instancewill allow you to surround a particular method on a per-type basis.

A Manually-Generated Example

Let’s take a look at how a modified method obtains each IAroundInvoke instance type and executes any additional custom behavior. Suppose that we have the following class defined:

public class Person
{
   public void Speak()
   {
      Console.WriteLine(“Hello, World!”);
   }
}

After using either Postweaver.exe or LinFu.AOP’s custom PostWeaver MSBuild task on the entire Person assembly, the newly modified Speak() method will look something like the following:

// Note: LinFu.AOP will automatically modify the 
// person class to implement IModifiableType
public class Person : IModifiableType
{
   public void Speak()
   {
      // --Begin Method Prolog—

      IAroundInvokeProvider provider = this.AroundInvokeProvider;
      IAroundInvoke aroundInvoke = null;

      // Note: The context will be automatically constructed by the 
      // modified method; for simplicity’s sake,
      // the constructor has been omitted
      InvocationContext context = new InvocationContext(...);

      // Get the class-level IAroundInvoke instance
      IAroundInvoke classAroundInvoke = 
          AroundInvokeRegistry.GetSurroundingImplementation(context);

      // Get the instance-based IAroundInvoke instance
      if (provider != null)
         aroundInvoke = provider.GetSurroundingImplementation(context);
      
      // Invoke any custom ‘around’ behavior attached to the 
      // current instance
      if (aroundInvoke != null)
         aroundInvoke.BeforeInvoke(context);

      // Invoke the custom ‘around’ behavior associated
      // with all instances of this class
      if (classAroundInvoke != null)
         classAroundInvoke.BeforeInvoke(context);



      // Find a replacement for the current method body
      IMethodReplacementProvider localProvider = this.MethodReplacementProvider;
      IMethodReplacement instanceMethodReplacement = 
          localProvider != null ? localProvider.GetMethodReplacement(context) : null;
      IMethodReplacement classMethodReplacement = 
          MethodReplacementRegistry.GetMethodReplacement(context);

      
      // Use the class method replacement by default
      IMethodReplacement targetReplacement = classMethodReplacement;

      // Override the class level method replacement with the 
      // instance method replacement, if it exists
      targetReplacement = 
          instanceMethodReplacement != 
          null ? instanceMethodReplacement : classMethodReplacement;


      if (targetReplacement != null)
      {  
          // Execute the method replacement
          targetReplacement.Invoke(context);
      }

      // --End Method Prolog—

      else
      {
          // Call the original implementation 
   // --Begin Original method body--
   Console.WriteLine(“Hello, World!”);
   // --End Original method body--
      }

      // --Begin Method Epilog—

      // Invoke any post-method call behavior
      // attached to either the class type or
      // any ‘around’ behavior attached to this instance
      object returnValue = null;
      if (aroundInvoke != null)
         aroundInvoke.AfterInvoke(context, returnValue);

      if (classAroundInvoke != null)
         classAroundInvoke.AfterInvoke(context, returnValue);

      // --End Method Epilog--
   }
}

As you can see from the example above, LinFu.AOP adds quite a bit of code to the body of the Speak() method. When the Speak() method is modified, the PostWeaver will surround the original method body with calls to both the IAroundInvoke.BeforeInvoke() and IAroundInvoke.AfterInvoke() methods so clients can add their own custom behavior when the newly modified method executes. For any additional class-level "around" behavior, the Speak() method will call the static method named AroundInvokeRegistry.GetSurroundingImplementation()to determine if a particular IAroundInvoke instance needs to be executed in addition to the original method body. Obtaining a class-level IAroundInvoke implementation seems pretty straightforward, but getting a reference to the instance-based IAroundInvoke needs a bit more explanation. That’s what we’ll examine in the next section.

Instance-based IAroundInvoke

From the client’s perspective, there has to be some way to bind an object reference to a specific IAroundInvoke instance. This is where the IModifiableType interface comes in handy. When the PostWeaver modifies a given type (such as the Person class in the previous example), it automatically provides an implementation for that particular interface. The IModifiableType interface, in turn, is defined as:

public interface IModifiableType
{
    bool IsInterceptionEnabled { get; set; }
    IAroundInvokeProvider AroundInvokeProvider { get; set; }
    IMethodReplacementProvider MethodReplacementProvider { get; set; }
}

The IModifiableType interface allows you to customize the behavior of each modified object instance. The IsInterceptionEnabled property allows you to enable interception for the current object instance, while the other two provider properties allow you to provide custom IAroundInvoke and IMethodReplacement instances that are also specific to the current object instance, as well. The two provider interfaces are defined as:

public interface IAroundInvokeProvider
{        
    IAroundInvoke GetSurroundingImplementation(IInvocationContext context);        
}

public interface IMethodReplacementProvider
{
    bool CanReplace(IInvocationContext context);
    IMethodReplacement GetMethodReplacement(IInvocationContext context);
}

Note: to provide your own custom behavior, all you have to do is provide your own custom implementation for at least one of the interfaces above, and assign it to a particular Person instance. Each modified method will then check for any provider instances and it will use those providers to customize that particular object instance.

Acquiring the Local IAroundInvoke Reference

Since every modified type automatically implements the IModifiableType interface, retrieving a local IAroundInvoke instance is as simple as:

// Get the instance-based IAroundInvoke provider
IAroundInvokeProvider provider = this.AroundInvokeProvider;
IAroundInvoke aroundInvoke = null;

// Note: The context will be automatically constructed by the 
// modified method; for simplicity’s sake,
// the constructor has been omitted
InvocationContext context = new InvocationContext(...);

// ...

// Get the instance-based IAroundInvoke instance
if (provider != null)
 aroundInvoke = provider.GetSurroundingImplementation(context);

Once both IAroundInvoke instances have been obtained by the modified Speak() method, the same method will invoke the BeforeInvoke() and AfterInvoke() methods before and after the actual method body executes. It’s really up to you to decide how to implement IAroundInvoke, and decide which methods you need to surround. The possibilities are endless.

Intercepting and Replacing Method Bodies

If, for some reason, you wanted to dynamically replace the implementation of a method at runtime, you could easily provide your own implementation by implementing the following interfaces:

public interface IMethodReplacementProvider
{
    bool CanReplace(IInvocationContext context);
    IMethodReplacement GetMethodReplacement(IInvocationContext context);
}

public interface IMethodReplacement
{
    object Invoke(IInvocationContext context);
}

The IMethodReplacementProvider interface is responsible for deciding whether or not a method replacement can be provided for the given context using the CanReplace() method. It’s also responsible for providing a IMethodReplacement instance. Once an IMethodReplacement instance has been found, the modified method will, in turn, execute the replacement instead of the original method body, as mentioned in the previous example:

// ...
      // Find a replacement for the current method body
      IMethodReplacementProvider localProvider = this.MethodReplacementProvider;
      IMethodReplacement instanceMethodReplacement = 
          localProvider != null ? localProvider.GetMethodReplacement(context) : null;
      IMethodReplacement classMethodReplacement = 
          MethodReplacementRegistry.GetMethodReplacement(context);

      // Use the class method replacement by default
      IMethodReplacement targetReplacement = classMethodReplacement;

      // Override the class level method replacement 
      // with the instance method replacement, if it exists
      targetReplacement = 
          instanceMethodReplacement != 
          null ? instanceMethodReplacement : classMethodReplacement;


      if (targetReplacement != null)
      {  
          // Execute the method replacement
          targetReplacement.Invoke(context);
      }
      else
      {
          // Call the original implementation 
   // --Begin Original method body--
   Console.WriteLine(“Hello, World!”);
   // --End Original method body--
      }

// ...

What makes this example particularly interesting is the fact that the modified Speak() method will actually look for a IMethodReplacement instance at both the class level and the instance level. As you can see, this process is quite similar to how the method obtains an instance of IAroundInvoke. The difference here, however, is that only one IMethodReplacement instance will be executed in place of the original method body.

The modified method will first search for a class-wide replacement for the Speak() method and, if possible, it will attempt to override that class-level replacement implementation with a local replacement from the current object’s MethodReplacementProvider property. Using this approach effectively allows you to replace any particular method on one or all instances of a given class and, again, I leave it to the reader’s capable hands to decide what to do with the flexibilities that it affords.

Reusing the Existing Implementation

There might be times where you might want to reuse the existing method body instead of replacing it altogether. In such cases, there must be some way to call the original method body even from an IMethodReplacement instance within the same method body itself. At first, this might seem impossible, given that conventional programming wisdom states that any method that calls itself will keep looping until the program ends in a predictable StackOverflowException. Preferably, reusing the existing implementation of a particular method should be easy as:

public class SpeakMethodReplacement : IMethodReplacement
{
     public object Invoke(IInvocationContext context)
     {
  // Say "hi" to CodeProject
  Console.WriteLine("Hello, CodeProject!");
         
         // Say “Hello, World!” by reusing the original implementation
  return context.TargetMethod.Invoke(context.Target, context.Arguments);
     }
}

Unfortunately, in normal situations, the example above will indeed infinitely loop on itself. Fortunately, the BaseMethodReplacementProvider class provides an interesting workaround for this problem, as shown in the following listing:

public abstract class BaseMethodReplacementProvider : IMethodReplacementProvider, 
    IAroundInvoke
{        
    private ICallCounter _counter = new MultiThreadedCallCounter();
    protected BaseMethodReplacementProvider()
    {
    }

    #region IMethodReplacementProvider Members

    public bool CanReplace(IInvocationContext context)
    {
        int pendingCalls = _counter.GetPendingCalls(context);
        if (pendingCalls > 0)
            return false;

        return ShouldReplace(context);
    }

    public IMethodReplacement GetMethodReplacement(IInvocationContext context)
    {
        int pendingCalls = _counter.GetPendingCalls(context);
        if (pendingCalls > 0)
            return null;

        return GetReplacement(context);
    }

    #endregion

    #region IAroundInvoke Members

    public void AfterInvoke(IInvocationContext context, object returnValue)
    {
        _counter.Decrement(context);
    }

    public void BeforeInvoke(IInvocationContext context)
    {
        _counter.Increment(context);
    }

    #endregion
    protected virtual bool ShouldReplace(IInvocationContext context)
    {
        return true;
    }
    protected abstract IMethodReplacement GetReplacement(IInvocationContext context);
}

The BaseMethodReplacementProvider avoids recursive loops in IMethodReplacement calls by invoking the IMethodReplacement instance on the first call and then ignoring any requests for the IMethodReplacement on each subsequent call. In the example above, BaseMethodReplacementProvider can detect whether or not it is being recursively called for each method per instance per running thread. If a call is in progress, it simply returns null for any subsequent requests for that particular method replacement. This effectively allows any IMethodReplacement implementation to reuse the original method body, without having to worry about having a StackOverflowException being thrown.

Creating a Finite Loop

Now that we can use BaseMethodReplacementProvider, using the SpeakMethodReplacement class is as easy as:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SampleLibrary;

using LinFu.AOP;
using LinFu.AOP.Interfaces;
namespace SampleProgram
{
    public class SpeakProvider : BaseMethodReplacementProvider
    {
        protected override bool ShouldReplace(IInvocationContext context)
        {
            // Only replace the Speak() method
            return context.TargetMethod.Name == "Speak";
        }
        protected override IMethodReplacement GetReplacement(IInvocationContext context)
        {
            return new SpeakMethodReplacement();
        }
    }

    public class SpeakMethodReplacement : IMethodReplacement
    {
        public object Invoke(IInvocationContext context)
        {
        // Say "hi" to CodeProject
           Console.WriteLine("Hello, CodeProject!");

           // Say “Hello, World!” by reusing the original implementation
           return context.TargetMethod.Invoke(context.Target, context.Arguments);
        }
    }

    public static class Program
    {
        public static void Main()
        {
            Person person = new Person();
                        
            IModifiableType modified = null;

            // HACK: Prevent the compiler from checking
            // if the person instance implements IModifiedType
            object personRef = person;
            if (personRef is IModifiableType)
                modified = personRef as IModifiableType;

            if (modified != null)
            {
                // Enable interception for this instance
                modified.IsInterceptionEnabled = true;
                modified.MethodReplacementProvider = new SpeakProvider();
            }

            // Say “hello” to both the world, and CodeProject
            person.Speak();

            Console.WriteLine("Press ENTER to continue...");
            Console.ReadLine();
        }
    }

}

In the example above, SpeakProvider will provide its own method implementation by returning a new SpeakMethodReplacement every time the Speak() method is called on that Person instance. The best part about this scenario is that the Person instance (or its clients) doesn’t even know that it’s being modified. It’s all completely transparent, and this is the power that LinFu.AOP ultimately offers.

Points of Interest

DynamicProxy Compatibility

Probably one of the most interesting uses for LinFu.AOP is to have it convert LinFu.DynamicProxy’s IInterceptor instances into an equivalent set of IMethodReplacement instances. In layman’s terms, this means that you can transparently use all of LinFu’s other features (such as Dependency Injection and Design by Contract) by using LinFu.AOP to inject those features into the application at runtime.

The best part about LinFu.AOP’s approach is that it does not rely on proxies to do the actual method interception. When a method is intercepted on a LinFu.AOP-modified type, you’ll always be working with the actual object instance rather than a proxy instance. At first, the difference might seem subtle, but the fact that you’ll always be working with the actual object instance means that you can also inject additional services into your application without worrying about your code interfering with other libraries that could possibly be using a proxy library of their own.

LinFu.AOP is only the first step in making your application more flexible and easier to maintain. The possibilities that it offers are truly endless — and here are some possible uses for LinFu.AOP:

Possible Uses

Logging - The Other "L" Word

Since this example has been used extensively in most AOP texts, I think it’s safe to say that this does not need any more explanation. LinFu.AOP can easily inject a logger at any point in your application with very little effort. I’ll leave it as a small exercise to my readers to figure this one out.

Transparent NHibernate Integration

One of the top ten things that I’ve always wanted to do is integrate LinFu with NHiberate so I could use all of LinFu’s features with my domain objects. Until recently, I used to think that the only way to do this was to supplant Castle’s DynamicProxy with LinFu’s DynamicProxy in NHibernate. With LinFu.AOP, however, that’s no longer necessary. Since LinFu.AOP does method interception from within the type itself rather than through a proxy, I can inject as many services as I want into my applications and NHibernate (or Castle, for that matter) will never even know that those services even exist.

Limitations

Unsigned Assemblies

One limitation that I’ve found with LinFu.AOP (at the time of this writing) is that it can only be used on assemblies that aren’t strongly named. If you need to modify a strongly named assembly with LinFu.AOP, you need to first remove the strong name and only then can you use LinFu.AOP on that assembly. Other than that, LinFu.AOP puts a lot of power in your hands, and I take no responsibility for anything bad that might occur for releasing this beast into the wild.

Ref and Out Parameters

Although LinFu.AOP (in theory) can support intercepting methods with ref and out parameters, I don’t think the current implementation using Cecil is robust enough for general use, so I’ve decided to leave it disabled by default. Methods with ref and out parameters will not be intercepted by default and, if you absolutely need to intercept them, then I’d have to defer you to either LinFu or Castle’s DynamicProxy libraries to do that interception for you.

A Public Safety Announcement

Please code responsibly, and try not to shoot yourself with it. Enjoy!

History

  • 31 January, 2008 -- Original version posted
  • 8 February, 2008 -- Updated binaries

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author

Philip Laureano
Software Developer (Senior) Readify
Australia Australia
No Biography provided
Follow on   Twitter

Comments and Discussions

 
Generaloption to allow all interceptions PinmemberMember 273940521-Apr-08 6:40 
GeneralRe: option to allow all interceptions PinmvpPhilip Laureano21-Apr-08 10:41 
Questionhow to fix PDB debugger files? PinmemberMember 273940520-Apr-08 12:47 
GeneralRe: how to fix PDB debugger files? PinmvpPhilip Laureano20-Apr-08 17:59 
GeneralRe: how to fix PDB debugger files? PinmemberMember 273940521-Apr-08 6:52 
General.NET Reflector crashes decompiling injected property getters PinmemberSqlRanger8-Feb-08 0:15 
GeneralRe: .NET Reflector crashes decompiling injected property getters PinmvpPhilip Laureano8-Feb-08 0:36 
GeneralLinFu.Aop.Tasks.dll PinmemberSqlRanger7-Feb-08 6:58 
GeneralRe: LinFu.Aop.Tasks.dll PinmvpPhilip Laureano7-Feb-08 12:01 
GeneralSuggestion [modified] PinmemberSqlRanger7-Feb-08 4:38 
GeneralRe: Suggestion PinmvpPhilip Laureano7-Feb-08 12:51 
GeneralRe: Suggestion [modified] PinmemberSqlRanger7-Feb-08 22:11 
GeneralRe: Suggestion PinmvpPhilip Laureano8-Feb-08 0:53 
GeneralRe: Suggestion PinmemberMember 273940521-Apr-08 7:05 
GeneralRe: Suggestion PinmvpPhilip Laureano21-Apr-08 11:13 
GeneralLinFu.AOP now supports selective weaving PinmvpPhilip Laureano16-Mar-08 23:03 
GeneralRe: LinFu.AOP now supports selective weaving PinmemberSqlRanger16-Mar-08 23:32 
GeneralEnabling interception by default. PinmemberSqlRanger6-Feb-08 23:40 
GeneralRe: Enabling interception by default. PinmvpPhilip Laureano7-Feb-08 1:03 
GeneralRe: Enabling interception by default. PinmemberSqlRanger7-Feb-08 4:31 
GeneralExcellent... Pinmemberraam_kimi4-Feb-08 2:19 
GeneralMissing part 5 Pinmembersakumira2-Feb-08 14:32 
GeneralRe: Missing part 5 PinmvpPhilip Laureano2-Feb-08 14:45 
GeneralExcellent. PinmvpPete O'Hanlon1-Feb-08 11:07 
GeneralRe: Excellent. PinmvpPhilip Laureano1-Feb-08 11:52 
GeneralRe: Excellent. PinmvpPete O'Hanlon2-Feb-08 23:59 
GeneralPlease code responsibly, and try not to shoot yourself with it PinmemberRugbyLeague1-Feb-08 6:03 
QuestionHow does this work? Pinmember leppie 1-Feb-08 0:03 
AnswerRe: How does this work? PinmemberSteve Hansen1-Feb-08 0:36 
GeneralRe: How does this work? PinmvpPhilip Laureano1-Feb-08 0:54 
GeneralSVN link bad Pinmember leppie 31-Jan-08 20:22 
GeneralRe: SVN link bad PinmvpPhilip Laureano31-Jan-08 21:30 
Generalthis is why you strong name your assemblies Pinmembermeaningoflights31-Jan-08 17:23 
GeneralRe: this is why you strong name your assemblies PinmemberAaron Jackson31-Jan-08 17:42 
GeneralRe: this is why you strong name your assemblies PinmvpPhilip Laureano31-Jan-08 21:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 8 Feb 2008
Article Copyright 2008 by Philip Laureano
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid