There are many times I create a child lifetime scope in Autofac simply to replace or supplement registrations in the parent. And from that point forward, I only use the child lifetime scope. The autofac documentation states that child scopes are not automatically disposed:
Child Scopes are NOT Automatically Disposed
While lifetime scopes themselves implement
IDisposable
, the lifetime scopes that you create are not automatically disposed for you. If you create a lifetime scope, you are responsible for callingDispose()
on it to clean it up and trigger the automatic disposal of components.
However, in this case, I do want children to be automatically disposed.
The workaround I've thought of so far is to have the child scope dispose the parent. Something like this:
ILifetimeScope scope = ...;
var childScope = scope.BeginLifetimeScope();
childScope.Disposer.AddInstanceForDisposal(scope);
scope = childScope;
Since I never keep track of scope
past this point, only childScope
, I need a way to dispose the parent.
To complicate matters further, each parent can have multiple child scopes. So I can't do this in those cases. I only want to dispose the parent when the last child is disposed. To do that, I think in the parent I'd have to register some service dedicated to reference counting each time BeginLifetimeScope()
is called, and decrement that reference count when the child is disposed.
I'm not sure if I'm approaching this correctly, so I wanted to see if there's a better solution here. I'm migrating my code base from UnityContainer to Autofac. The code previously had a UnityDisposer
object that would walk the parent/child tree from top down and dispose everything, but I don't get that kind of hierarchy with Autofac.
There's some doubt about my speculated solution, so I wrote a sample app to see what happens:
class ThingA : IDisposable
{
public ThingA()
{
Console.WriteLine("Construct ThingA");
}
public void Dispose()
{
Console.WriteLine("Dispose ThingA");
}
}
class ThingB : IDisposable
{
public ThingB()
{
Console.WriteLine("Construct ThingB");
}
public void Dispose()
{
Console.WriteLine("Dispose ThingB");
}
}
public static class Program
{
public static void Main()
{
ContainerBuilder builder;
builder = new ContainerBuilder();
builder.RegisterType<ThingA>().InstancePerLifetimeScope();
ILifetimeScope container1 = builder.Build();
container1.Resolve<ThingA>();
var container2 = container1.BeginLifetimeScope(builder2 =>
{
builder2.RegisterType<ThingB>().InstancePerLifetimeScope();
});
container2.Disposer.AddInstanceForDisposal(container1);
container1 = container2;
container1.Resolve<ThingB>();
container1.Dispose();
}
}
The output I get is:
Construct ThingA
Construct ThingB
Dispose ThingB
Dispose ThingA
So from what I can tell, for a linear parent/child relationship, this seems to work. But it doesn't solve the multiple-child issue. And to make it clear, I'm not particularly happy with the solution for the linear case, either.
From an Autofac perspective, you actually will have to start tracking parent and child scopes so you can choose when to dispose them. It's sort of a Spider-Man situation: With great power comes great responsibility.
Sometimes this is as easy as wrapping the set of various units of work with a using
statement. In an async situation, like with ASP.NET, places like middleware and HttpContext
come into play.
Disposing a parent out from under a child is a bad idea. Besides the fact that once the parent is disposed you can't really resolve things from the child (yes, I understand the parent would somehow stick around until the end of the last child, but the fact remains); we've definitely seen weird edge cases folks get themselves into by intentionally or unintentionally disposing the parent out from under the child and, depending on how it got hooked up and the order in which things happen, you can end up with really hard to troubleshoot exceptions involving ObjectDisposedException
.
If you think about what you're trying to do outside of an Autofac context, it's kind of a bad design pattern to try to have the system dispose of itself, like having the child scope somehow responsible for the parent. For example, if you had some other parent/child relationship where you wanted to do this, more often you'd see some sort of external orchestrator that handles reference counts and disposal - outside the logical parent/child relationship.
I would recommend doing something like that instead - actually track the parent and child scopes with some nature of orchestrator class and have the orchestrator watch for all the child scopes to disappear.
Plus, it'll make other situations easier, like "I created the parent, I created one child, it got disposed and cleaned up the parent while I was trying to create a second child." Threading stuff. You'll be able to account for that in your orchestrator code.