Fork me on GitHub

StructureMap and IDisposable Edit on GitHub


Singletons

This one is easy, any object marked as the Singleton lifecycle that implements IDisposable is disposed when the root container is disposed:


[Fact]
public void singletons_are_disposed_when_the_container_is_disposed()
{
    var container = new Container(_ =>
    {
        _.ForSingletonOf<DisposableSingleton>();
    });

    // As a singleton-scoped object, every request for DisposableSingleton
    // will return the same object
    var singleton = container.GetInstance<DisposableSingleton>();
    singleton.ShouldBeSameAs(container.GetInstance<DisposableSingleton>());
    singleton.ShouldBeSameAs(container.GetInstance<DisposableSingleton>());
    singleton.ShouldBeSameAs(container.GetInstance<DisposableSingleton>());
    singleton.ShouldBeSameAs(container.GetInstance<DisposableSingleton>());

    singleton.WasDisposed.ShouldBeFalse();

    // now, dispose the Container
    container.Dispose();

    // the SingletonThing scoped object should be disposed
    singleton.WasDisposed.ShouldBeTrue();
}

Ejecting a singleton-scoped object will also force it to be disposed. See Using the Container Model for more information on how to eject singletons from a running Container.

Nested Containers

As discussed in Nested Containers (Per Request/Transaction), any transient, AlwaysUnique (as of 4.0), or container-scoped object that implements IDisposable and is created by a nested container will be disposed as the nested container is disposed:


[Fact]
public void nested_container_disposal()
{
    var container = new Container(_ =>
    {
        // A SingletonThing scoped service
        _.ForSingletonOf<IColorCache>().Use<ColorCache>();

        // A transient scoped service
        _.For<IColor>().Use<Green>();

        // An AlwaysUnique scoped service
        _.For<Purple>().AlwaysUnique();
    });

    ColorCache singleton = null;
    Green nestedGreen = null;
    Blue nestedBlue = null;
    Purple nestedPurple = null;

    using (var nested = container.GetNestedContainer())
    {
        // SingletonThing's are really built by the parent
        singleton = nested.GetInstance<IColorCache>()
            .ShouldBeOfType<ColorCache>();

        nestedGreen = nested.GetInstance<IColor>()
            .ShouldBeOfType<Green>();

        nestedBlue = nested.GetInstance<Blue>();

        nestedPurple = nested.GetInstance<Purple>();
    }

    // Transients created by the Nested Container
    // are disposed
    nestedGreen.WasDisposed.ShouldBeTrue();
    nestedBlue.WasDisposed.ShouldBeTrue();

    // Unique's created by the Nested Container
    // are disposed
    nestedPurple.WasDisposed.ShouldBeTrue();

    // NOT disposed because it's owned by
    // the parent container
    singleton.WasDisposed.ShouldBeFalse();
}

Transients

Regardless of the new tracking/release mode, the StructureMap team still strongly recommends using a nested container per HTTP request or service bus message handling session or logical transaction to deal with disposing transient objects.

By default, StructureMap will not hang on to any transient-scoped objects created by the root or child containers. Dealing with IDisposable is completely up to the user in this case. The StructureMap team has long believed that trying to track transient disposables with an explicit Release(object) mode as other IoC containers behave would do more harm than good (memory leaks from forgetting to call Release(), more work on the user).

That all being said, in order to comply with the new ASP.Net vNext compliance behavior, StructureMap 4.0 introduces a new opt-in transient tracking mode with the prerequisite Release(object) method:


[Fact]
public void release_transient_created_by_root_container()
{
    var container = new Container(_ => _.TransientTracking = TransientTracking.ExplicitReleaseMode);

    container.TransientTracking.ShouldBeOfType<TrackingTransientCache>();

    var transient1 = container.GetInstance<DisposableGuy>();
    var transient2 = container.GetInstance<DisposableGuy>();

    transient1.WasDisposed.ShouldBeFalse();
    transient2.WasDisposed.ShouldBeFalse();

    // release ONLY transient2
    container.Release(transient2);

    transient1.WasDisposed.ShouldBeFalse();
    transient2.WasDisposed.ShouldBeTrue();

    // transient2 should no longer be
    // tracked by the container
    container.TransientTracking.Tracked
        .Single()
        .ShouldBeTheSameAs(transient1);
}

[Fact]
public void disposing_the_container_disposes_tracked_transients()
{
    var container = new Container(_ => _.TransientTracking = TransientTracking.ExplicitReleaseMode);

    var transient1 = container.GetInstance<DisposableGuy>();
    var transient2 = container.GetInstance<DisposableGuy>();

    transient1.WasDisposed.ShouldBeFalse();
    transient2.WasDisposed.ShouldBeFalse();

    container.Dispose();

    transient1.WasDisposed.ShouldBeTrue();
    transient2.WasDisposed.ShouldBeTrue();
}

As of right now, StructureMap will only track the top level object requested from a Container and not all the other IDisposable objects that may have been created as part of the main object graph.