Castle Windsor Transient Disposables
Asked Answered
A

2

12

I know this has been discussed ad nauseum...but I have an issue with the way Windsor is tracking Transient IDisposable objects.

I understand the benefits of letting Windsor manage my IDiposables...but I don't like it.

What happens if I want to wrap my component in a using block? The coder would make the assumption the resource would get cleaned up at the end of the using block, right? Wrong - Dispose would be called, but Windsor would hold onto the instance until explicitly released. This is all well and fine for me, since I know what I'm doing...but what about another developer who's coding a class and wants to use an IDisposable the way every other IDisposable is usually used - in a using block?

using(factory.CreateInstance()) 
{
   ....  
}

looks much clearer to me than:

MyDisposable instance;
try
{
    instance = factory.GetInstance();
}
finally 
{
    factory.Release(instance);
}

In order to truly dispose my instances and have them eligible for GC, I need to reference the WindsorContainer or use a typed factory that exposes a release method. That means the only acceptable way of using IDisposable components is to use a typed factory. This is not good, in my opinion...what if someone adds the IDisposable interface to an existing component? Every single place that expects the component to be injected will need to change. That's really bad in my opinion. (Granted, in a non DI scenario, it would need to change to call Dispose also...but with Windsor every place will need to change to use a typed factory, which is a much larger change).

Ok, fair enough, I can use a custom ReleasePolicy right? How about this?

public class CustomComponentsReleasePolicy : AllComponentsReleasePolicy
{
    public override void Track(object instance, Burden burden)
    {
        if (burden.Model.LifestyleType == LifestyleType.Pooled) 
            base.Track(instance, burden);
    }
}

Ok, great, my IDisposable Transient components will be GC'd now.

What if I want to use a TypedFactory so my class can produce many instances of a type?

public interface IMyFactory 
{
    MyDisposable GetInstance();
    void Release(MyDisposable instance);
}

[Singleton]
public class TestClass
{
    public TestClass(IMyFactory factory) { }
}

Ok, well, for one, calling Release on factory will do nothing to call Dispose() on MyDisposable, since MyDisposable isn't tracked....

How can I overcome these difficulties?

Thanks.

Aalto answered 21/7, 2011 at 20:34 Comment(1)
I think I will need to blog about thisNummary
H
12

First of all, how do you know the decommision concerns associated with an object which you did not create? You do not have control over the creation of the object because you did not create it yourself (the factory did this for you). When you combine ioc resolving with consumer disposing (calling .Dispose instead of factory.Release) you are introducing the requirement that you object knows how it was created, yet it did not create itself. Consider the following example:

“Component” is something you resolve through the container, but you want to dispose of yourself

public class Component : IDisposable
{
    private readonly IAmSomething _something;

    public Component(IAmSomething something)
    {
        _something = something;
    }

    public void Dispose()
    {
        // Problem 1: the component doesnt know that an implementation of IAmSomething might be disposable
        // Problem 2: the component did not create "something" so it does not have the right to dispose it
        // Problem 3: What if an implementation of "something" has a depenency on a disposable instance deep down in its object graph?

        // this is just bad..
        IDisposable disposable = _something as IDisposable;

        if (disposable != null)
            disposable.Dispose();

    }
}

public interface IAmSomething
{

}

public class SomethingA : IAmSomething
{

}

public class SomethingB : IAmSomething, IDisposable 
{
    public void Dispose()
    {
    }
}

As shown above decommission can be complex and I simple don’t see how I can handle this gracefully myself, especially when Windsor does this for me. If your codebase is littered with the service-locator anti-pattern (http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) I can see how this becomes an issue (I am not saying that you code is), but then you really how lot bigger problems.

using(factory.CreateInstance()) 
{
   ....  
}

looks much clearer to me than: …

Well the using statement is a convention, there no compile time error if you omit it, so from my point of view the try/finally with release is just another convention, although a bit more verbose. You could for example shorten the try/finally by creating a helper, such as:

[TestFixture]
public class SomeCastleTests
{
    [Test]
    public void Example()
    {
        var container = new WindsorContainer();

        // you would of course use a typed factory instead in real word

        using (var usage = new ComponentUsage<IAmSomething>(container.Resolve<IAmSomething>, container.Release))
        {
            // use..
            usage.Component
        }
    }
}

public class ComponentUsage<T> : IDisposable where T : class
{
    private Func<T> _create;
    private Action<T> _release;

    private T _instance;

    public ComponentUsage(Func<T> create, Action<T> release)
    {
        _create = create;
        _release = release;
    }

    public T Component
    {
        get
        {
            if (_instance == null)
                _instance = _create();

            return _instance;
        }
    }

    public void Dispose()
    {
        if (_instance != null)
        {
            _release(_instance);
            _instance = null;
        }
    }
}

This is not good, in my opinion...what if someone adds the IDisposable interface to an existing component? Every single place that expects the component to be injected will need to change.

I don’t understand this statement. How a component is released and when you need it are to different concerns, if a component is provided as a constructor dependency, adding IDisposable doesn’t change anything. The class which gets the dependency through the constructor did not create it and is therefore not responsible for releasing it.

Hortatory answered 28/10, 2011 at 6:42 Comment(0)
O
0

As a piggy back, you can solve some of the other points you mentioned by creating an interceptor that releases your object for you. I do this for a unit of work concern. The interceptor looks like this:

public class UnitOfWorkReleaseOnDispose : IInterceptor
{
    private readonly IUnitOfWorkFactory unitOfWorkFactory;

    public UnitOfWorkReleaseOnDispose(IUnitOfWorkFactory unitOfWorkFactory)
    {
        this.unitOfWorkFactory = unitOfWorkFactory;
    }

    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();
        this.unitOfWorkFactory.DestroyUnitOfWork((IUnitOfWork)invocation.Proxy);
    }
}

My registration code looks like this:

            Component.For<IUnitOfWork>()
                .ImplementedBy<TransactionalUnitOfWork>()
                .LifestyleTransient()
                .Interceptors<UnitOfWorkReleaseOnDispose>()
                    .Proxy.Hook(h => h.Service<InterfaceMethodsOnlyProxyGenerationHook<IDisposable>>())

The proxy hook is only there to say that I only want to proxy the IDiposable interface methods. That class looks like this:

public class InterfaceMethodsOnlyProxyGenerationHook<TInterface> : IProxyGenerationHook
{
    public void MethodsInspected()
    {

    }

    public void NonProxyableMemberNotification(Type type, System.Reflection.MemberInfo memberInfo)
    {

    }

    public bool ShouldInterceptMethod(Type type, System.Reflection.MethodInfo methodInfo)
    {
        return typeof(TInterface) == type;
    }
}

Which also requires to be registered since I'm using the overload of hook that I'm using:

            Component.For<IProxyGenerationHook>()
                .ImplementedBy<InterfaceMethodsOnlyProxyGenerationHook<IDisposable>>()
                .LifestyleSingleton()
Odeen answered 18/7, 2014 at 22:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.