|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Note: to access the latest sources for the IntroductionIn this part of the series, I’ll show how you can use BackgroundIn Part I, I mentioned that one of 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 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 CrosscutsIn 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 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 public interface ILogger
{
void LogMessage(string format, params object[] args);
}
As you can see, the Crosscutting ConcernsWhen 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 Catch-22Using 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 Three QuestionsThere 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 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 ThreeThe 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 Method Calls
Field Reads/Writes
Property Getters/Setters
Object Initialization
Type Initialization
The Join Point Model
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 AfterTypically, 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 Placing the LoggerIn 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 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 Dynamic Proxies to the Rescue?There were, of course, various dynamic proxy libraries available (such as LinFu.DynamicProxy or even The PremiseI 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, Features and UsageA Simple DemonstrationLet’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 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 Using LinFu.AOP with MSBuildIn order to use
Once the project has been unloaded, the next thing that we need to do is manually edit the
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
Once the project has been reloaded, all we have to do is rebuild the solution. Using PostWeaver.exeIf (for some reason) you don’t have direct access to the source code for your target library, PostWeaver.exe [filename]
One at a TimeFor 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 DependenciesOnce PostWeaver.exe (or the Adding Your Own Custom Behavior at Runtime
// 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 Contexts and Interfaces of InterestThe 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 Default Provider ImplementationsBoth the 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 MethodSince we’re only going to add a simple 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
}
Instance-based Custom BehaviorsIn order to intercept the public interface IModifiableType
{
bool IsInterceptionEnabled { get; set; }
IAroundInvokeProvider AroundInvokeProvider { get; set; }
IMethodReplacementProvider MethodReplacementProvider { get; set; }
}
This interface allows 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. Adding Class-wide BehaviorsThe process for intercepting the // 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 Think about that for a moment. Enabling Interception?The next thing that you might be wondering about is the call to the 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 EmptorGiven the power of pervasive method interception that Intercepting Static MethodsIn addition to intercepting instance methods, public sealed class Person
{
// ...
public static void Speak()
{
Console.WriteLine(“Static Speak Method: Hello, World!”);
}
}
To add your own custom behavior to the static // 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 What about IMethodReplacement?At this point, you might be wondering why I didn’t mention how to replace methods using the Pure TransparencyWhat makes this interesting is that clients that use this particular Under the HoodLike other AOP frameworks such as
Before and AfterThe first thing that you might notice in the above diagram is that public interface IAroundInvoke
{
void AfterInvoke(IInvocationContext context, object returnValue);
void BeforeInvoke(IInvocationContext context);
}
The A Manually-Generated ExampleLet’s take a look at how a modified method obtains each public class Person
{
public void Speak()
{
Console.WriteLine(“Hello, World!”);
}
}
After using either Postweaver.exe or // 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, Instance-based IAroundInvokeFrom the client’s perspective, there has to be some way to bind an object reference to a specific public interface IModifiableType
{
bool IsInterceptionEnabled { get; set; }
IAroundInvokeProvider AroundInvokeProvider { get; set; }
IMethodReplacementProvider MethodReplacementProvider { get; set; }
}
The 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 Acquiring the Local IAroundInvoke ReferenceSince every modified type automatically implements the // 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 Intercepting and Replacing Method BodiesIf, 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 // ...
// 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 The modified method will first search for a class-wide replacement for the Reusing the Existing ImplementationThere 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 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 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 Creating a Finite LoopNow that we can use 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, Points of InterestDynamicProxy CompatibilityProbably one of the most interesting uses for The best part about
Possible UsesLogging - The Other "L" WordSince 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. Transparent NHibernate IntegrationOne of the top ten things that I’ve always wanted to do is integrate LimitationsUnsigned AssembliesOne limitation that I’ve found with Ref and Out ParametersAlthough A Public Safety AnnouncementPlease code responsibly, and try not to shoot yourself with it. Enjoy! History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||