CodePlex Container and Interception

Topics: CodePlexContainer
Coordinator
Jul 10, 2007 at 4:34 AM

A Little Background...

Scott and I did some poking around our virtual method interception to see if we could easily adapt it to be able to wrap interfaces when they're requested, which would eliminate the need for deriving from classes and using virtual methods. Although it was more work than anticipated, it wasn't difficult, and we were able to do it quite successfully, and the code that does the work has been checked in. However, the strategies have not been changed, so it's really not possible (yet) to use it.

The reason we stopped is because it brought up some questions about the way we've been handling interception so far. I think we're really not happy with some of the ways it has shaken out, for the simple reason that we'd like to be able to mix interception styles within a single build-up. You could, in theory, provide all three forms of interception simultaneously: interface-based ILEmit, virtual-method-based ILEmit, and remoting. At first it may not seem really obvious why you might want to mix the forms, but it becomes apparent when you consider that not all forms of interception are about calls from consumers of the objects.

Here is a somewhat simplified but real world example.

public interface IFoo
{
    void Bar(...);
}
 
public class Foo : IFoo
{
    [InterfaceIntercept(typeof(IFoo), typeof(ParameterValidatingHandler))]
    public void Bar(...)
    {
        Baz();
    }
 
    [VirtualMethodIntercept(typeof(InstrumentingHandler))]
    public virtual void Baz(...)
    {
    }
}
If you just allow one style of interception (here, interface-based interception), you're going to lose the instrumentation of all calls to Baz. When we use virtual method interception, all calls to Baz() can be intercepted, including calls from within the class itself (only virtual method interception can really do this; interface-based and remoting-based don't support these semantics). So already you can see we need to be able to mix two kinds of interception. In fact, your need to do multiple intercepts on a single method might cause you to need to mix two types of interception:

    [InterfaceIntercept(typeof(IFoo), typeof(ParameterValidatingHandler))]
    [VirtualMethodIntercept(typeof(InstrumentingHandler))]
    public virtual void Bar(...)
The example attributes above are indicative of the direction we're heading in. We need to get rid of the idea that a class only gets one type of interception, and make the decision on a method by method basis. Of course, combining multiple types of interception at once could multiply the performance penalties, so it's worth seeing how each system works.

Remoting-Based Interception


How it Works
The remoting-based interception works for classes which derive from MarshalByRefObject. (Although it can also work to remote an interface, we believe that interface-based interception is better done with ILEmit for performance reasons). Remoting creates the object and hands back a proxy to the remoted object. The proxy allows you to get in the middle of every method call on the object, even non-virtual methods.

Pros
  • Allows you to intercept non-virtual, non-interface methods.
  • It's the simplest to understand how it works, since it uses built-in features of the .NET platform

Cons
  • Intercepting calls by use of remoting and proxies can be much more time consuming that the ILEmit counterparts.
  • Remoting demands you intercept EVERY method, even the ones you don't intend to intercept
  • It requires that MarshalByRefObject is at the base of the inheritance chain (unless you're only intercepting an interface).
  • While the proxy intercepts all calls between the object and its client, it does not intercept any calls between the object and itself.

ILEmit-Based Interception


How it Works
There are two forms of ILEmit-based interception: interface-based and virtual-method-based. They are fundamentally similar in the process that they use: they create a new class at runtime (by emitting IL) which is used to intercept the desired methods. The class they emit is different based on the type of interception. Interface-based interception creates a new class which implements the interface, delegating calls to an encapsulated instance of the original target class. Virtual-method-based interception creates a new class which derives from the original class, overriding the virtual methods that need to be intercepted, calling the base class methods when appropriate.

Each has pros and cons (unless otherwise noted, the pros and cons here are for both methods):

Pros
  • ILEmit is lighter weight than remoting, so the performance impact should be lower
  • Can selectively intercept only the methods that have handlers
  • (Interface-based) No rules for the class: it can have any inheritance hierarchy, it can be sealed, methods don't need to be virtual

Cons
  • Debugging the emitted interception code can be challenging
  • (Interface-based) Can only intercept the methods of the interface
  • (Virtual-method-based) Can only intercept virtual methods
  • (Virtual-method-based) Intercepted class must be public and not sealed

So, What's Next?


Thankfully, the infrastructure for all three systems can be used in-tact. What needs to change is the way these things are exposed via the policies and strategies (and, subsequently, the methods on DependencyContainer). It will take a little time to redo the strategies and policies; I believe we'll be able to accomplish it all during our next 20% Slack day on Wednesday. This will, of course, change the public API of the container a bit, so anybody who is using the bits already will eventually be broken. I don't think the changes will be too drastic, and we'll post again on this thread when we're done to show the new APIs.

And What's After That?


A very common request is to be able to configure the container externally, so we'll be adding support for configurators to DependencyContainer, and likely implementing both a raw XML-based configurator as well as configuration-based configurator. Whether we can get to that on Wednesday depends on how much we can accomplish on the interception stuff.

We also had a very productive meeting with the p&p team, and they seem to be excited about the prospect of moving these changes forward as a basis for ObjectBuilder 2.0. It's too early to talk about the details (in particular, I don't want to get into discussions about backward compatibility and the impact to existing deliverables, because that's something that's still being discussed). At this point, though, I think we'll all in philosophical alignment to move this forward as OB2. :)
Coordinator
Jul 19, 2007 at 7:30 PM
We finished this interception refactoring in change set 24575. Sorry I didn't post about it sooner.

We also added support for external configurators for the CodePlex Container in changet set 24760, though we don't have any public examples of it yet (we're using it internally to read configuration data from Web.config to set up a container used in a web services project).
Aug 24, 2007 at 6:27 PM
Maybe I'm a little confused, but wouldn't be easier to write a strategy that wraps the object in the PIAB?
Coordinator
Aug 24, 2007 at 6:56 PM

hbatista wrote:
Maybe I'm a little confused, but wouldn't be easier to write a strategy that wraps the object in the PIAB?

Our interception system is more fully featured, and better integrated with ObjectBuilder. PIAB has a lot of stuff that duplicates functionality that was already in ObjectBuilder.

The way you write handlers for both systems is identical, so it should have almost no impact on the development of interception handlers.