I'm trying to better understand IoC/DI, in particular the concept of scopes.
I'm using a sample WPF app (repo here) from a Pluralsight course (great service, BTW) and modifying it slightly. Naturally, it worked fine before my changes.
Here's the original code:
Private Sub App_Startup(Sender As App, e As StartupEventArgs) Handles Me.Startup
Dim oBootStrapper As BootStrapper
Dim oContainer As IContainer
Dim oMainView As MainView
oBootStrapper = New BootStrapper
oContainer = oBootStrapper.GetContainer
oMainView = oContainer.Resolve(Of MainView)
oMainView.Show()
End Sub
...and here's what I changed it to:
Private Sub App_Startup(Sender As App, e As StartupEventArgs) Handles Me.Startup
Dim oBootStrapper As BootStrapper
Dim oContainer As IContainer
Dim oMainView As MainView
oBootStrapper = New BootStrapper
oContainer = oBootStrapper.GetContainer
Using oScope As ILifetimeScope = oContainer.BeginLifetimeScope
oMainView = oScope.Resolve(Of MainView)
oMainView.Show()
End Using
End Sub
However, this results in an ObjectDisposedException
when I try to add a new Friend:
Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has already been disposed.
This all seems to contradict what I'm finding in the official documentation:
It is important to always resolve services from a lifetime scope and not the root container.
That guidance is what led me to introduce the scope in the first place. But if a scope disposes the objects necessary for the application to function, how can we hope to use it? Further, WHY should we use it?
I'm confused. Is my proposed usage incorrect?
--EDIT--
To clarify, I'm seeking a solution to the conundrum posed by the official guidance: if we adhere to that our application will fail. But if we don't we risk memory leaks (according to the docs).
What to do?
The official guidance is there to help you prevent inadvertent memory leaks. Since Autofac holds references to disposable things so that it can do the disposing, if you resolve 1000 IDisposable items from the container directly, there's no way to let garbage collection clean them up without disposing the whole container.
This is particularly bad if you're not thinking about that and, say, have a long running app that does occasional work with maybe a database - the work might resolve a disposable database context from the container periodically and even if the calling code executes dispose, the container will hang onto it: memory leak.
However, put that together with the docs about things like singletons. Singletons, even if you resolve them from a child scope, live at the container root. Why? Because if you let a scope dispose a shared singleton it would be a problem for everything else needing the singleton.
The "resolve things from a scope" guidance is there to stop folks from shooting themselves in the foot with memory leaks they don't realize they're introducing by only using the container.
If the thing you're resolving is not disposable, AND nothing in the entire dependency chain for that thing is disposable, you're probably safe getting it from the container.
The larger lesson here is be diligent with your lifetime scopes and how you want disposal handled. I'm not a WPF guy, but if the main window for the app needs to be resolved and needs to live for the entire duration of the app, it seems like that's a perfect use for a singleton... which wouldn't have been disposed at the end of that lifetime scope and you wouldn't have seen the exception. Autofac also has ways you can opt out of making it do disposal, which stops it from holding those references.
But it's super hard to say "always do this" and "never do that" because, as you see, lifetime scope and disposal is a complex topic. It's important to dig into why, and memory management and disposal is why. Getting the lifetimes right on your registered components is equally important for the same reasons.