Garbage?
Creative Commons License photo credit: wok

In the previous post we discussed various relationships between singletons and transients in an inversion of control container. I introduced WindsorContainerInspector, a little utility class for analyzing the container and showed a sample unit test which looks for singletons that rely on transient objects.

Now I’ll show a few other checks that I think are good to perform on the container. They involve disposability of objects.

Disposable Transients

Let’s say we have a transient service IWebClient:

public interface IWebClient : IDisposable
{
void DownloadMeSomething(string somethingUrl);
}

As you can see, the service implements IDisposable. Now let’s say we have a class DownloadData which depends on this service:

public class DownloadData
{
public DownloadData (IWebClient webClient) {...}

...
}

Note the constructor dependency on IWebClient. What’s wrong with this picture?

What happens when the DownloadData is no longer used? The webClient is not disposed. We could hold up some valuable system resource by not disposing of such an object. And since we know IWebClient is defined as transient in our IoC container, we can be pretty sure that DownloadData is the only one using the received IWebClient instance. So we are responsible for this instance!

In other words, our DownloadData should also be disposable. We can say that, in general, components that depend on disposable transients should also be disposable:

[Test, MultipleAsserts]
public void TransientsThatRelyOnDisposableTransientsShouldAlsoBeDisposable ()
{
    Type disposableType = typeof(IDisposable);
 
    IList<KeyValuePair<ComponentModel, ComponentModel>> dependencies = inspector.FindDependencies (
        (a, b) =>
        {
            return a.LifestyleType == LifestyleType.Transient && false == disposableType.IsAssignableFrom (a.Implementation)
                && b.LifestyleType == LifestyleType.Transient && disposableType.IsAssignableFrom(b.Service);
        });
 
    foreach (KeyValuePair<ComponentModel, ComponentModel> dependency in dependencies)
    {
        Assert.Fail (
            "Transient component {0} relies on the disposable transient component {1}, but it itself is not disposable",
            dependency.Key.Implementation.FullName,
            dependency.Value.Implementation.FullName);
    }
}

Some might say that this kind of check can be done by static analysis like FxCop. FxCop will detect this only if DownloadData stores the IWebClient in a class field. But we could have used IWebClient parameter just within the constructor and then forget about it:

public class DownloadData
{
public DownloadData (IWebClient webClient)
{
webClient.CheckConnection();
}
}

In such cases no warning will be issued by FxCop.

Disposable Implementations Of Non-Disposable Services

What if we had another service, called IMailChecker:

public interface IMailChecker
{
bool AnyNewMail();
}

and we implemented a GMailChecker:

public class GMailChecker : IMailChecker, IDisposable
{
public bool AnyNewMail() {...}
public void Dispose() {...}
}

Notice GMailChecker implements IDisposable in addition to IMailChecker interface. But IMailChecker itself is not defined as disposable. How will the user of IMailChecker service know whether the object it holds should be disposed or not?

One way would be to use reflection to determine this. But this is very cumbersome and easy to forget when coding. My way of thinking is that if there’s reasonable possibility the implementations of the service will require disposability, you should declare the service interface as disposable. We can check for this condition with the following unit test:

[Test, MultipleAsserts]
public void TransientsThatRelyOnDisposableTransientsShouldAlsoBeDisposable ()
{
    Type disposableType = typeof(IDisposable);
 
    IList<KeyValuePair<ComponentModel, ComponentModel>> dependencies = inspector.FindDependencies (
        (a, b) =>
        {
            return a.LifestyleType == LifestyleType.Transient && false == disposableType.IsAssignableFrom (a.Implementation)
                && b.LifestyleType == LifestyleType.Transient && disposableType.IsAssignableFrom(b.Service);
        });
 
    foreach (KeyValuePair<ComponentModel, ComponentModel> dependency in dependencies)
    {
        Assert.Fail (
            "Transient component {0} relies on the disposable transient component {1}, but it itself is not disposable",
            dependency.Key.Implementation.FullName,
            dependency.Value.Implementation.FullName);
    }
}

Below is the updated the implementation of WindsorContainerInspector. Next time we will be discussing issues (= mostly problems) around disposing stuff received from the Windsor container.

public class WindsorContainerInspector
{
    public WindsorContainerInspector(IWindsorContainer container)
    {
        this.container = container;
    }
 
    public IList<KeyValuePair<ComponentModel, ComponentModel>> FindDependencies(
        Func<ComponentModel, ComponentModel, bool> dependencyPredicate)
    {
        List<KeyValuePair<ComponentModel, ComponentModel>> dependencies = new List<KeyValuePair<ComponentModel, ComponentModel>>();
 
        foreach (GraphNode node in container.Kernel.GraphNodes)
        {
            ComponentModel dependingNode = (ComponentModel) node;
 
            foreach (GraphNode depender in node.Dependents)
            {
                ComponentModel dependerNode = (ComponentModel)depender;
 
                if (dependencyPredicate(dependingNode, dependerNode))
                    dependencies.Add(new KeyValuePair<ComponentModel, ComponentModel>(dependingNode, dependerNode));
            }
        }
 
        return dependencies;
    }
 
    public IList<ComponentModel> FindComponents (Func<ComponentModel, bool> predicate)
    {
        List<ComponentModel> components = new List<ComponentModel>();
 
        foreach (GraphNode node in container.Kernel.GraphNodes)
        {
            ComponentModel component = (ComponentModel) node;
            if (predicate(component))
                components.Add(component);
        }
 
        return components;
    }
 
    private readonly IWindsorContainer container;
}