Click here to Skip to main content
15,868,005 members
Articles / Programming Languages / C#

The Simplest AOP Scenario in C#

Rate me:
Please Sign up or sign in to vote.
4.81/5 (27 votes)
29 Sep 200416 min read 171K   2K   117   22
A very simple library that you can use to implement not-so-simple constructs of Aspect Oriented Programming

Introduction

AOP is very interesting, because it addresses some common problems in complex programs. If you just don’t know what AOP is, I suggest you read an article like [1].

Many different AOP implementations already exist, mainly as open source. Since I’ve seen many programmers, particularly in the .NET world, play a little with the existing AOP libraries only to conclude that AOP is too complex to understand and insert in their projects, this article will introduce a simple approach to the base concepts of AOP, sketching the code necessary to build a custom AOP solution.

I don’t want to enter in the flame “AOP != Attributes + Interception” (see for example [2]), but I’m used to thinking about software fragments only in terms of their usefulness, not if they conform to some theory (ok, I just entered the flame! :-)).

What I want to say is that if everyone must agree that the theory of AOP is really rich and complex (and maybe beautiful because of this), very often what the programmer needs in every day working is something light and quick to handle, that can solve a great part of his/her problems.

I’ll talk about this and more all along the article, while commenting the code.

Interception and ContextBoundObject

Every AOP implementation is based, first of all, on some form of interception: it can be static, dynamic, precompiled, marked with attributes, and who knows how. What we know is that we have to intercept the calls to the methods of certain classes.

In .NET platform, this can be achieved mainly with two approaches: injecting the IL code with the desired redirections (precompiling the code with some instrument or modifying the result of a normal compilation), or basing these classes on the .NET library class ContextBoundObject. Handling bytecode is very difficult, not much evolution-resistant (what about the next release of .NET platform?), and available only to programmers with a lot of free time. Using ContextBoundObject as a base for AOP has been already investigated very deeply [3], and I can only add that it is so easy that it’ll be our choice (but certainly limiting ourselves in the capabilities).

The most important concern is performance: while I suggest to read the documentation to understand why it is so slow to use ContextBoundObjects, let’s find some number. It will be very simple: just create a new project and insert for example, the following code:

C#
using System;
using System.Windows.Forms;

public class Test //: ContextBoundObject
{
 public void Go()
 {
  int k = 32;
  k *= 2;
 }

 public static void Start()
 {
  Test t = new Test();

  int starttime = Environment.TickCount;
  for (int i = 0; i < 5000000; i++)
   t.Go();
  int endtime = Environment.TickCount;

  MessageBox.Show("Total time is: " + (endtime - starttime).ToString());
 }
}  

In my test machine, executing Test.Start() gives on average a result of 31. Removing the comment, I obtain on average 109. This is more than 300% slower, that can look as an incredible cost just to use a CBO. But we have also to think of absolute times: in the above test, we made 5,000,000 invocations, that means that each invocation had a call time of 0,022 microseconds. In my opinion, this cost is totally acceptable for the best part of real-world applications: of course, adding services to the interception mechanism will have an impact on these times, and each time I will run again the tests to show you the results. Right now, we can state one important thing: as you can see debugging the code over the line that builds an object of type Test, the reference t points to a TransparentProxy, that represents our chance to intercept the method call to the underlying object.

Interception

This part is the easiest: I just stole the code attached to the article [4], to which I must send my credits! This article is really interesting from the point of view of technology, but takes into account a lot of services that we don’t need at all. So, in the end, it’s not true that I just stole the code: I’ve thrown away what I don’t need here, and what remained is a very little bunch of code. I don’t want to explain again the details of the interception chain, and I will spend just a few words on the actors (the code for each is very simple, see the documentation for the details).

AOPAttribute – We need an attribute to mark the CBO, because we must add a dummy property to its context.

C#
[AttributeUsage(AttributeTargets.Class)]
public class AOPAttribute : ContextAttribute
{
    public AOPAttribute() : base("AOPAttribute") {}

    public override void GetPropertiesForNewContext(IConstructionCallMessage ctor)
    {
        if (AOPConfig.Enabled)
            ctor.ContextProperties.Add(new AOPProperty());
    }

    public override bool IsContextOK(Context ctx,
        IConstructionCallMessage ctorMsg)
    {return false;}
}

AOPProperty – The property is needed only to add a message sink.

C#
public class AOPProperty : IContextProperty, IContributeServerContextSink
{
    public AOPProperty() {}

    public IMessageSink GetServerContextSink(IMessageSink nextSink)
    {
        IMessageSink logSink = new AOPSink(nextSink);
        return logSink;
    }

    public string Name {get {return "AOP";}}

    public bool IsNewContextOK(Context ctx)
    {
        AOPProperty newContextLogProperty =
       ctx.GetProperty("AOP") as AOPProperty;
        if (newContextLogProperty == null)
        {
            Debug.Assert(false);
            return false;
        }
        return (true);
    }

    public void Freeze(Context ctx) {}
}

AOPSink – It’s our custom interceptor. Here, we’ll have the chance to process method invocations.

C#
public class AOPSink : IMessageSink
{
    private IMessageSink m_NextSink;
    private ObjRef realobject = null;

    public AOPSink(IMessageSink nextSink)
    {this.m_NextSink = nextSink;}

    public IMessageSink NextSink {get {return m_NextSink;}}

    public IMessage SyncProcessMessage(IMessage msg)
    {
        return m_NextSink.SyncProcessMessage(msg);
    }

    public IMessageCtrl AsyncProcessMessage(IMessage msg,
         IMessageSink replySink)
    {
        throw new NotSupportedException();
    }
}

AOPBaseClass – Lastly, we don’t want our users to remember the right derivation and the right attribute marking, so we create a base class for all the classes that will be intercepted, and where we group a common service (more on this later, now don’t worry for the SyncProcessMessage function).

C#
[AOP]
public class AOPBaseClass : ContextBoundObject
{
    public AOPBaseClass() {}

    public virtual IMessage SyncProcessMessage(
        IMessageSink sink, IMessage msg)
    {
        AOPControllerInfo cinfo = AOPConfig.GetAssoc(GetType());
        if (cinfo != null && cinfo.controller != null)
        {
            if (cinfo.matcher.IsMatch(msg))
                return cinfo.controller.SyncProcessMessage(
                    this, sink, msg);
        }

        return sink.SyncProcessMessage(msg);
    }
}

Why is it so complex to set up an interception infrastructure, if CBOs exist just to be intercepted? Because the service is primarily intended for .NET Remoting and for cross-AppDomain calls. Basically, each invocation is translated in an IMessage object, that represents the original request. It’s hence possible to serialize the message to send it for example to another machine, and so on. We don’t need all of this, but since there is no other simplified support, I’ve tried to reduce at a minimum the needed code.

SyncProcessMessage()

The key function in all this part of code is AOPSink.SyncProcessMessage(). Once we set up a class marked with an AOPAttribute, the above code intercepts all method calls translating them in messages and passing them to this function. In the test code above, you can see that the implementation doesn’t do anything but pass the message to the next sink. The result is that all of this code is absolutely invariant: it’s only slower because of the translations in messages. It’s interesting to look at the performance. This is the test class we’ll use from here on for our tests:

C#
public class TestClass : AOPBaseClass
{
    private void Output(string s)
    {
        //Console.WriteLine(s);
        MessageBox.Show(s);
    }

    public void Test1() { Output("Test1"); }
    public void Test2() { Output("Test2"); }
    public void Test3() { Output("Test3"); }
    public void Go() { Output("Go"); }
}

Ok, we have some doubly useful classes, but at least the test class is straightforward: just derive it from AOPBaseClass and you’re done!

The comparison, as usual, is done calling a lot of times a function (say, Test1), commenting and uncommenting the derivation in TestClass. Calling 100,000 times the ‘straight’ class gives us as usual a result of 31, while removing the comment, the time goes up to 1,188, always on average. Things are getting a little worse, that’s a big difference; but the absolute time of a method invocation, on average, is 0.012 milliseconds, or about 12 microseconds. While this can start to be a problem for computationally intensive applications, it should still be absolutely bearable for the average application (say 80%?)

Now we have a complete interception mechanism: if you want, you can stop reading this article! You know how much it will cost, and what you do inside the SyncProcessMessage is completely up to your fantasy. For example, you can invoke the underlying method twice, just writing:

C#
m_NextSink.SyncProcessMessage(msg);
return m_NextSink.SyncProcessMessage(msg);

Maybe you will lose the first result, but if you can imagine a scenario in which this can be useful (I could give you a hint: suppose you want to modify a database application to write your data towards two different sources at the same time...), you can go for it: I don’t know if this could be possible with available AOP libraries (can someone let me know about this?)

AOP Basics

Once the technological problems have been solved, we can go further on to build a useful infrastructure to provide AOP services. The first, classical, steps must deal with pre-processing and post-processing of method invocations.

Here, we can leverage our experience and fantasy, and the design I present is of course only one of the possible.

I start by saying that I don’t like attributes that much. They are pretty, easy to write and use but they are intrinsically static: once you mark with an attribute, you don’t have any chance to modify it. I’ve seen many architectures based on attributes, and each time I wonder if they can be handled properly. Some quick examples, based on the typical AOP examples, are the following: when AOP is used for logging, often we need to log certain classes and not others, maybe sometimes we want to log only starting from some point in time on, and obviously sometimes we need to log for a certain subset of methods and sometimes another, but in the same run of the same application.

From the AOP point of view, we need some sort of control over all of these aspects, and because of this, many AOP implementations offer feature-rich languages which express some or all of these semantics. Of course, these implementations must offer some sort of processing of such constructs, such as compilers, precompilers, attribute processers and so on. This seems to me a little overkill, because it’s very difficult to provide a language that can cover all of the needs of heterogeneous applications, or attributes that can be customized in every direction. Lastly, I’ve read a lot of documents in which it is stated that is an advantage to be able to group an aspect in a single code point (typically a class), but none points out that even aspects could need to be architectured: for example, I could want to log method invocations of a class in a manner, and others in others, or even log to a file until a certain condition is met, and from there on throwing exceptions.

I find it difficult, in theory and in reality, to be able to create a complex architecture that can react to all of this, without even considering the time that programmers have to spend to learn and practice with such an architecture: remember, we all have deadlines!

When facing the problem of the design, I always start with a simple concept: I think that the best tool with which to model any behaviour is code. Simple as that! This is true for those who provide a service AND for those who use that service. Any other kind of solution has the risk (not the certainty, but the risk) of limiting the possible uses and variants of that service.

Having said all of this, that in some way explains my approach, my goal is to provide a very simple base service, written in a manner that can be immediately comprehensible and above all totally customizable. At the same time, I don’t want to fill original classes with any sort of code, apart from the derivation from the base class, as seen.

The Controller

Central to my vision is the controller: simply stated, this is a class that implements the message processing representing method invocations. Its form is very understandable:

C#
public class AOPBaseController
{
    public virtual void Begin(object o, IMessage msg) {}

    public virtual void End(object o, IMessage msg) {}

    public virtual IMessage SyncProcessMessage(object o,
         IMessageSink sink, IMessage msg)
    {
        Begin(o, msg);
        IMethodReturnMessage returnedMessage = (IMethodReturnMessage)sink.SyncProcessMessage(msg);
        if (returnedMessage.Exception != null)
            End(o, msg);
        return returnedMessage;
    }
}

As we can see from the code, it has a virtual function that processes messages in a standard form: before and after the original method invocation, it offers the chance to intercept such events (by calling two other, void, virtual functions). Any of these functions is virtual. Maybe usually, you will want to implement in a derived class only the Begin() and End() functions, or maybe you want to reimplement the whole interceptions totally, depending on your needs (maybe because you want to handle some sort of return type in the Begin() and End() functions).

As usual, I pay a lot of attention to not being obstrusive at all: any service offered can be tailored or even substituted at pleasure. We also need a way to associate a controller to a class: the simple solution could be the classical attribute, but I’ve already said that I consider it a simplified solution. What if I want to exploit different interception controls in two different runs of the application? We need to separate even this service, and I provide another very simple class just for this.

C#
public class AOPConfig
{
    // <Type, AOPControllerInfo>
    private static Hashtable associations = new Hashtable();

    public static bool Enabled = true;

    public static void SetAssoc(Type classtype,
         Type controllertype, IMessageMatcher matcher)
    {
        ConstructorInfo ci = controllertype.GetConstructor(Type.EmptyTypes);
        AOPBaseController controller = (AOPBaseController)ci.Invoke(null);
        associations[classtype] = new AOPControllerInfo(controller, matcher);
    }

    public static AOPControllerInfo GetAssoc(Type classtype)
    {
        return (AOPControllerInfo)associations[classtype];
    }
}

(I do not delve now with the AOPControllerInfo class, the IMessageMatcher interface and the exception handling.)

With these static functions, we are free to read the associations from an XML file, or ask them to another service, or even, like we do in the sample code, set them in code at the start of the application.

C#
AOPConfig.SetAssoc(typeof(TestClass), typeof(TestClassController), null);

With these base classes, it’s very easy to write a simple controller:

C#
public class TestClassController : AOPBaseController
{
    private void Output(string s)
    {
        //Console.WriteLine(s);
        MessageBox.Show(s);
    }

    public override void Begin(object o, IMessage msg) { Output("Begin"); }
    public override void End(object o, IMessage msg) { Output("End"); }
}

This sample doesn’t redefine SyncProcessMessage, but intercepts every call to the underlying class to provide pre and post processing. You will notice that every function receives as parameters an object and a message: you can correctly imagine that the first is the underlying object and the second the message representing the method invocation. In the first draft, I tried to translate the message in a more readable structure, but in the end, I left it untouched: it’ll be enough to read the documentation to understand what you can find in it. The important thing is that in the controller we have all the possible information about the current method invocation, that lets us be able to customize totally the behaviour of the interception itself.

Sink Revisited

Passing the underlying object around has requested some investigation, but it could be possible with a little variant of the custom sink, that is:

C#
public class AOPSink : IMessageSink
{
    private IMessageSink m_NextSink;
    private ObjRef realobject = null;

    public AOPSink(IMessageSink nextSink)
    {this.m_NextSink = nextSink;}

    public IMessageSink NextSink {get {return m_NextSink;}}

    public IMessage SyncProcessMessage(IMessage msg)
    {
IMethodMessage methodMessage = (IMethodMessage)msg;

        if (methodMessage.MethodName == ".ctor")
        {
            IMethodReturnMessage ret = (IMethodReturnMessage)m_NextSink.SyncProcessMessage(msg);
            realobject = (ObjRef)ret.ReturnValue;
            return ret;
        }
        else
        {
            AOPBaseClass obj =
 (AOPBaseClass)RemotingServices.Unmarshal(realobject);
            return obj.SyncProcessMessage(m_NextSink, msg);
        }
    }

    public IMessageCtrl AsyncProcessMessage(IMessage msg,
           IMessageSink replySink)
    {
        throw new NotSupportedException();
    }
}

In the message processing, we have to intercept the constructor invocation, and save for future use in the sink itself the reference to the just constructed object. See ‘Limits’ below for (evident) limitations of this solution. From now on, every time the sink intercepts an invocation, it passes this reference down the chain. This also implies that for each underlying object, there is a different attribute, a different property and a different sink. I consider the situation at this point another ‘good’ solution: from now on, the programmer is free to create his/her own solution, building on these base classes and redefining every aspect of the provided services.

Message Filtering

We can provide another base service that’s common to AOP implementations: the chance to select which method invocation to filter. This can be done in many ways, even if our test code considers only a pattern matching on the method name. What is important, as always, is to clearly separate this further aspect and to provide the chance to modify dynamically the filtering. I created a simple interface that doesn’t need to be explained:

C#
public interface IMessageMatcher
{
    bool IsMatch(IMessage msg);
}

An object implementing this interface can be registered with the controller, and at runtime every intercepted message, apart from the constructor, will be matched against this function in the AOPBaseClass. This is the example matcher in the code:

C#
public class RegExMethNameMatcher : IMessageMatcher
{
    private Regex r = null;

    public RegExMethNameMatcher() : this("") {}
    public RegExMethNameMatcher(string filter)
    {r = new Regex(filter);}

    public bool IsMatch(IMessage msg)
    {
        if (msg is IMethodMessage)
            return r.IsMatch(MethodMessageUtil.GetMethodName(
                     (IMethodMessage)msg));
        return false;
    }
}

I've put this class in the base library because it is of common use: it's very typical to filter methods by their name. A regular expression is used to control if the current method invocation is to be filtered in or out. If you build it with an empty string, it will always return true.

Again, feel free to let run your fantasy, imagining for example that you can implement a matcher that can react dynamically to external conditions to filter different kind of functions (for example, only properties or only methods). Of course, this is a false filter, in the sense that the sink will in every case intercept all method invocations, but this can be very useful to make code more readable, relieving pre and post processing code from looking for the right methods to process.

Minimalia

There are other little details I didn’t explain until now. For example, AOPConfig offers a simple boolean (Enabled) that you can use to block the interception service. Again, this is a false blocking because I tried to give the application the original performance, but it looks impossible from code. Moreover, the sink is able to react even to asynchronous method invocations, but this is beyond the scope of this article. To conclude, I added a class obtained from the source code already credited [4] that provides a utility to obtain useful information from the message, MethodMessageUtil. For the rest, browse and debug at least once the code line by line and you will understand better than words how all of this behaves. If you have any kind of problems, feel free to ask me: I don’t know if I’ll be able to answer, but I promise I’ll try!

Sample Code

I wrote and actually use a version of this code in C# 2.0 Beta, so using generics and other new characteristic of the language. I rewrote it in C# 1.X (I hope not too simplified!) version so that more users can run and quickly understand it. There is no error and exception handling, only code core. The only point that needed to be considered, and it is in the sample code, is that the underlying method invocation can raise an exception. The base controller version of the interception framework can see it, and in the sample code simply doesn’t call the End() processing. In real world applications, you will want to design a reasonable handling code for this case.

Limitations

I'm not trying to fool you: this is not the perfect solution. It may have some good points, but also bad points. A real AOP library is still by far a solution much much better than mine, no point in this. Here comes the bad part:

Performance

In the end, my tests show that in typical scenarios, you can lose at worst 0,030 microseconds for each intercepted call (if you use a typical nowadays PC). I don't take into account what I do in the interception code: this cost is only for the infrastructure, and you spend it for each method invocation in an intercepted class, even if you don't do anything or filter out the call. It means also that you need some 33,000 calls to lose one whole second. If it's too much for you, only you know. There is a little trick to commute an application between intercepted mode and non intercepted: adding an intermediate class in the chain from your classes to AOPBaseClass.

C#
public class AOPCommutingClass : AOPBaseClass
{
}

If you derive all of your classes from this one, it'll be enough to comment out the derivation here to have a 'normal' program again (only a bit cluttered by the AOP classes you don't use anymore).

Memory

Sincerely, I don't bother for this problem anymore, but you have to know that ContextBoundObjects consume memory; the property, the sink, and so on, each one consumes memory.

Capabilities

While it can be reasonable enough that you are not able to intercept static functions, maybe you didn't notice you can't intercept constructors, or better, you have to change my design. But usually this shouldn't be a problem, because your application objects (natural candidates for interception) should be instantiated by some class factory with some method (that can be intercepted): try to think about this...

References

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below. A list of licenses authors might use can be found here.


Written By
Web Developer
Italy Italy
Frog Software srl is an Italy based training and consulting company, specialized in .NET technologies. It's available in-house and in-place all over southern Europe.
Andrea Bioli is the founder, and has more than ten years of experience in C/C++ design and programming on large projects.
Since .NET launch, Frog Software has focused exclusively on this technology.
http://www.frogsoftware.it (in italian)

Comments and Discussions

 
QuestionOut Of Memory Exception Pin
Member 1365053519-Feb-18 12:52
Member 1365053519-Feb-18 12:52 
Suggestionnice explaination Pin
Puresharper12-Dec-16 2:47
professionalPuresharper12-Dec-16 2:47 
QuestionCalls from one method to another is not getting intercepted Pin
Joy George K23-Aug-11 21:06
professionalJoy George K23-Aug-11 21:06 
GeneralSystem.Runtime.Remoting.RemotingException Pin
HaroldBalta11-May-10 6:33
HaroldBalta11-May-10 6:33 
GeneralVery nice Pin
mikeperetz5-Sep-09 7:18
mikeperetz5-Sep-09 7:18 
GeneralGood Job ! Pin
gpgemini4-Jul-09 4:21
gpgemini4-Jul-09 4:21 
Questionhow to intercept recursive calls? Pin
Saeed Alg28-Apr-08 4:35
Saeed Alg28-Apr-08 4:35 
QuestionCalling a method from within a AOPBaseController Pin
Kevin Kinnett31-Jul-07 7:17
Kevin Kinnett31-Jul-07 7:17 
AnswerRe: Calling a method from within a AOPBaseController Pin
Member 1200335023-Sep-15 5:45
Member 1200335023-Sep-15 5:45 
QuestionContextBoundObject with Generics Throws Exception Pin
ivantorresjmz30-Aug-06 17:00
ivantorresjmz30-Aug-06 17:00 
AnswerRe: ContextBoundObject with Generics Throws Exception Pin
MReza J2-Feb-08 23:21
MReza J2-Feb-08 23:21 
Generalaborting method execution Pin
cre_Active26-Feb-06 2:34
cre_Active26-Feb-06 2:34 
GeneralNot in the web Pin
Ista2-Dec-05 6:04
Ista2-Dec-05 6:04 
GeneralAccess to calling object Pin
DewyDewhirst24-Aug-05 13:00
DewyDewhirst24-Aug-05 13:00 
GeneralRe: Access to calling object Pin
A. Bioli28-Aug-05 4:24
A. Bioli28-Aug-05 4:24 
Of course yes! Smile | :) Look (and step into) the code, you will see that Begin/End functions ALREADY contain a parameter (the first) that represents the current object the function is called into.
Apart from this, what happens if you call whatever function on that object is that you start another (recursive) call interception on the same object: maybe this is just what you want, maybe not... Smile | :)
Andrea Bioli
QuestionRe: Access to calling object Pin
Saeed Alg28-Apr-08 4:18
Saeed Alg28-Apr-08 4:18 
GeneralStrange, I have quite differnet performance test results Pin
Alexander Pinsker22-Aug-05 0:15
sussAlexander Pinsker22-Aug-05 0:15 
GeneralRe: Strange, I have quite differnet performance test results Pin
A. Bioli28-Aug-05 4:17
A. Bioli28-Aug-05 4:17 
GeneralCastle.DynamicProxy Pin
S3an7-Apr-05 4:58
S3an7-Apr-05 4:58 
GeneralVery interesting Pin
Nemanja Trifunovic30-Sep-04 5:15
Nemanja Trifunovic30-Sep-04 5:15 
GeneralRe: Very interesting Pin
Andrea Bioli30-Sep-04 5:34
Andrea Bioli30-Sep-04 5:34 
GeneralRe: Very interesting Pin
Anonymous7-Mar-05 13:31
Anonymous7-Mar-05 13:31 

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

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