Castle Dynamic Proxy tutorial part IV: breaking hard dependencies

This is part four of my tuto­r­ial on Cas­tle Dynamic Proxy.

In the last part of the tuto­r­ial we cre­ated a method Get­Inter­cept­ed­Meth­od­sCount­For that I promised I’ll talk about soon. While we’re at it, we’re going to fix another design flaw of our Freez­able class.

To do its work, it holds a hard ref­er­ence to each and every freez­able object it cre­ates. This is obvi­ously not a big deal if you cre­ate only a hand­ful of freez­able objects that are we want to be alive for the entire time the appli­ca­tion is running.

private static readonly IDictionary<object, IFreezable> _freezables = new Dictionary<object, IFreezable>();

How­ever, in most cases the objects we cre­ate are tran­sient, and we cre­ate a lot of them. Look­ing at it from this per­spec­tive – we have a mem­ory leak. Freez­able class holds a ref­er­ence to the objects, so garbage col­lec­tor can not col­lect them and reclaim the mem­ory they occupy, even though we may not use these objects ever again.

So how do we fix it? For now we’ll use evil hack I intro­duced in Get­Inter­cept­ed­Meth­od­sCount­For method in the last part. Ulti­mately, we’re arrive at much nicer solu­tion when we talk about mix­ins. But let’s not get ahead of ourselves.

If you took a look at the code from the pre­vi­ous part, the method is imple­mented as follows:

private int GetInterceptedMethodsCountFor(object freezable)
{
    Assert.True(Freezable.IsFreezable(freezable));

    var hack = freezable as IProxyTargetAccessor;
    Assert.NotNull(hack);
    var loggingInterceptor = hack.GetInterceptors().
                                 Where(i => i is CallLoggingInterceptor).
                                 Single() as CallLoggingInterceptor;
    return loggingInterceptor.Count;
}

The evil hack is based on the fact, that each proxy gen­er­ated by Cas­tle Dynamic Proxy frame­work imple­ments an IProx­y­Tar­ge­tAc­ces­sor inter­face. If you take a look at how it looks, it has two get­ter methods:

dptutorial_4_IProxyTargetAccessor

Dyn­Prox­yGet­Tar­get returns the prox­ies object. In our case the prox­ied object is the proxy itself, as we can prove with the fol­low­ing test

[Fact]
public void DynProxyGetTarget_should_return_proxy_itself()
{
    var pet = Freezable.MakeFreezable<Pet>();
    var hack = pet as IProxyTargetAccessor;
    Assert.NotNull(hack);
    Assert.Same(pet, hack.DynProxyGetTarget());
}

This is log­i­cal if you con­sider how Dynam­icProxy cre­ates class prox­ies. It does so by cre­at­ing a sub­class of prox­ied type. The whole inter­cep­tion magic hap­pens in over­rid­den vir­tual meth­ods and the meth­ods of actual inter­cepted type are called via base.MyMethod(args). That’s why IProx­y­Gen­er­a­tionHook con­tains a method that allows you to act upon meth­ods that are not vir­tual and hence can not be inter­cepted. We’ll use this fea­ture in forth­com­ing part of the tutorial.

The other method, Get­Inter­cep­tors, returns the inter­cep­tors asso­ci­ated with given proxy. We can use this method to obtain the proxy inter­cep­tors with­out keep­ing any hard ref­er­ence to it.

So out task for this part of the tuto­r­ial is to break the need for hard ref­er­ence the Freez­able class holds to freez­able objects. We state the require­ment with the fol­low­ing test:

[Fact]
public void Freezable_should_not_hold_any_reference_to_created_objects()
{
    var pet = Freezable.MakeFreezable<Pet>();
    var petWeakReference = new WeakReference(pet, false);
    pet = null;
    GC.Collect();
    Assert.False(petWeakReference.IsAlive, "Object should have been collected");
}

If you run this test now, it will fail (go on, see for your­self, don’t get my word on it).

Now let’s fac­tor out the depen­dency with out new found tool. First we refac­tor IsFreez­able method, to the following.

public static bool IsFreezable(object obj)
{
    if (obj == null)
        return false;
    var hack = obj as IProxyTargetAccessor;
    if (hack == null)
        return false;
    return hack.GetInterceptors().Count(i => i is IFreezable) > 0;
}

If we run out tests, the old tests will pass (great, we didn’t break any­thing), but the new test, will still fail, as we have two more meth­ods to refac­tor. Let’s now go to the IsFrozen method. I’m going to cheat here how­ever. While imple­ment­ing the method, I noticed that there’s a por­tion of code that each method in the class will require, so I fac­tored it out, to another method. With that, here’s changed code:

public static bool IsFreezable(object obj)
{
    return AsFreezable(obj) != null;
}

private static IFreezable AsFreezable(object target)
{
    if (target == null)
        return null;
    var hack = target as IProxyTargetAccessor;
    if (hack == null)
        return null;
    return hack.GetInterceptors().FirstOrDefault(i => i is FreezableInterceptor) as IFreezable;
}

public static bool IsFrozen(object obj)
{
    var freezable = AsFreezable(obj);
    return freezable != null && freezable.IsFrozen;
}

We added the AsFreez­able method that returns either IFreez­able imple­men­ta­tion asso­ci­ated with given object, or null, if there isn’t any.

We have two more meth­ods to refactor:

public static void Freeze(object freezable)
{
    var interceptor = AsFreezable(freezable);
    if (interceptor == null)
        throw new NotFreezableObjectException(freezable);
    interceptor.Freeze();
}

Freeze imple­men­ta­tion is still very sim­ple. The MakeFreezable<TFreezable>() method only puts newly cre­ated objects into the dic­tio­nary, so we can safely remove it from that method. We can now also delete the dic­tio­nary field, as it’s not used anymore.

If you run the tests now, you’ll see that they all pass, includ­ing the newly added one.

dptutorial_4_tests_passed

The code, includ­ing tests, is here.

Final words

Even though we fixed the mem­ory leak issue, it still is not the best solu­tion. That said, IProx­y­Tar­ge­tAc­ces­sor is a use­ful inter­face and it’s good to know that its there when you need it, but most of the time you don’t need it and in almost every case there’s a bet­ter way to accom­plish your goal, with­out using the interface.

It’s mostly intended for use in low level, frame­work infra­struc­ture code, and if used any­where else you should treat it as a warn­ing sign.

Tech­no­rati Tags: , ,
  • http://uberpwn.spaces.live.com/ Joel Holder

    Great series man.. Keep up the excel­lent work! Now I see how Cas­tle Proxy enables Spring.NET's AOP advice. Thanks..