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;
Logger.LogMessage("Withdrew {0}", amount);
}
public void Deposit(double amount)
{
if (Logger == null)
return;
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:
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()
{
Console.WriteLine("Hello, CodeProject");
Console.WriteLine("Hello, World!");
}
}
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:
public interface IAroundInvoke
{
void AfterInvoke(IInvocationContext context, object returnValue);
void BeforeInvoke(IInvocationContext context);
}
public interface IAroundInvokeProvider
{
IAroundInvoke GetSurroundingImplementation(IInvocationContext context);
}
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;
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)
{
}
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();
IModifiableType modified = person as IModifiableType;
if (modified != null)
{
IAroundInvokeProvider provider =
new SimpleAroundInvokeProvider(new AroundSpeakMethod(),
c => c.TargetMethod.Name == "Speak");
modified.IsInterceptionEnabled = true;
modified.AroundInvokeProvider = provider;
}
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:
var provider = new SimpleAroundInvokeProvider(new AroundSpeakMethod(),
c => c.TargetMethod.Name == "Speak");
AroundInvokeRegistry.Providers.Add(provider);
var first = new Person();
first.EnableInterception();
Console.WriteLine("First Person: ");
first.Speak();
Console.WriteLine("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:
var provider = new SimpleAroundInvokeProvider(new AroundSpeakMethod(),
c => c.TargetMethod.Name == "Speak");
AroundInvokeRegistry.Providers.Add(provider);
Person.Speak();
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
instance
will 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:
public class Person : IModifiableType
{
public void Speak()
{
IAroundInvokeProvider provider = this.AroundInvokeProvider;
IAroundInvoke aroundInvoke = null;
InvocationContext context = new InvocationContext(...);
IAroundInvoke classAroundInvoke =
AroundInvokeRegistry.GetSurroundingImplementation(context);
if (provider != null)
aroundInvoke = provider.GetSurroundingImplementation(context);
if (aroundInvoke != null)
aroundInvoke.BeforeInvoke(context);
if (classAroundInvoke != null)
classAroundInvoke.BeforeInvoke(context);
IMethodReplacementProvider localProvider = this.MethodReplacementProvider;
IMethodReplacement instanceMethodReplacement =
localProvider != null ? localProvider.GetMethodReplacement(context) : null;
IMethodReplacement classMethodReplacement =
MethodReplacementRegistry.GetMethodReplacement(context);
IMethodReplacement targetReplacement = classMethodReplacement;
targetReplacement =
instanceMethodReplacement !=
null ? instanceMethodReplacement : classMethodReplacement;
if (targetReplacement != null)
{
targetReplacement.Invoke(context);
}
else
{
Console.WriteLine("Hello, World!");
}
object returnValue = null;
if (aroundInvoke != null)
aroundInvoke.AfterInvoke(context, returnValue);
if (classAroundInvoke != null)
classAroundInvoke.AfterInvoke(context, returnValue);
}
}
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:
IAroundInvokeProvider provider = this.AroundInvokeProvider;
IAroundInvoke aroundInvoke = null;
InvocationContext context = new InvocationContext(...);
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:
IMethodReplacementProvider localProvider = this.MethodReplacementProvider;
IMethodReplacement instanceMethodReplacement =
localProvider != null ? localProvider.GetMethodReplacement(context) : null;
IMethodReplacement classMethodReplacement =
MethodReplacementRegistry.GetMethodReplacement(context);
IMethodReplacement targetReplacement = classMethodReplacement;
targetReplacement =
instanceMethodReplacement !=
null ? instanceMethodReplacement : classMethodReplacement;
if (targetReplacement != null)
{
targetReplacement.Invoke(context);
}
else
{
Console.WriteLine("Hello, World!");
}
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)
{
Console.WriteLine("Hello, CodeProject!");
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)
{
return context.TargetMethod.Name == "Speak";
}
protected override IMethodReplacement GetReplacement(IInvocationContext context)
{
return new SpeakMethodReplacement();
}
}
public class SpeakMethodReplacement : IMethodReplacement
{
public object Invoke(IInvocationContext context)
{
Console.WriteLine("Hello, CodeProject!");
return context.TargetMethod.Invoke(context.Target, context.Arguments);
}
}
public static class Program
{
public static void Main()
{
Person person = new Person();
IModifiableType modified = null;
object personRef = person;
if (personRef is IModifiableType)
modified = personRef as IModifiableType;
if (modified != null)
{
modified.IsInterceptionEnabled = true;
modified.MethodReplacementProvider = new SpeakProvider();
}
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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.