Knowing that Func<B> is basically a "shortcut" for myCurrentLifetimeScope.Resolve<B> wrapped in a non-Autofac-specific object, it might help to turn this question around and ask why Func<B> wouldn't respect lifetime scopes, and what consequences it would have if it didn't.
The vast majority of applications that make use of lifetime scopes do so to isolate units of work. For example, web applications that have per-request lifetime scopes. There are lots of great reasons why this is an interesting pattern, like being able to only create resources that are required for a given request and then clean them up with the request is finished - use only the memory you need.
Given that, let's say that, in general, lifetime scopes are interesting to have, if for anything but a nice way to track and clean up allocated resources.
Now let's say you have a connection to, like, a web service or something. Maybe it's a WCF service. There are some things you might remember about WCF service clients:
- Once the channel is faulted, the client is worthless. You can't just create one client because if the service ever faults the channel on you, you have to throw that client instance away and create a new one.
- You need to dispose of the clients when you're done. They can keep channels open and eat up resources if you don't do that.
Given that, Func<IServiceClient> sorts of relationships become very interesting.
Let's say you have an MVC controller. This is kinda pseudocode, I'm not going to run it through a compiler.
public class MyController : Controller
{
private readonly Func<IServiceClient> clientFactory;
public MyController(Func<IServiceClient> clientFactory)
{
this._clientFactory = clientFactory;
}
public string GetSomethingReallyCool()
{
var retryCount = 0;
while(retryCount < 5)
{
var client = this._clientFactory();
try
{
return client.GetSomethingCool();
}
catch
{
retryCount++;
}
}
return "We failed to get the cool data.";
}
}
We have sort of a poor-man's retry built in here because the service is flaky. I'm not recommending this exact code, it's just quick and easy to demonstrate the concept.
The stage is set! Now, for the sake of discussion, let's pretend that the relationships didn't respect lifetime scopes, just for a moment.
What happens during a web request?
- Your MVC controller gets resolved from the per-request lifetime scope.
- The controller takes a
Func<IServiceClient> function to create - dynamically - a WCF service client for when it's needed.
- The controller action creates a service client and opens a channel. This service client lives in the root container which means it'll never be disposed until your whole app shuts down.
- The service faults. The channel's faulted, not closed. It'll just kinda hang out... but you can't use the same service client instance. That one's toast.
- The controller action creates another service client and opens a channel. This service client also lives in the root container and will be allocated forever.
- The service call succeeds and the value is returned. But while the clients are going out of scope, they're disposable - the container is handling disposal, right? So the locals are out of scope but they're still allocated.
This is a pretty horrible memory leak.
Turn the normal behavior back on! We're done pretending!
If it obeys lifetime scopes, it works like this:
- Your MVC controller gets resolved from the per-request lifetime scope.
- The controller takes a
Func<IServiceClient> function to create - dynamically - a WCF service client for when it's needed.
- The controller action creates a service client and opens a channel. This service client lives in the request lifetime scope which means it'll be disposed when the request is over.
- The service faults. The channel's faulted, not closed. It'll just kinda hang out... but you can't use the same service client instance. That one's toast.
- The controller action creates another service client and opens a channel. This service client also lives in the request scope and will be disposed at the end of the request.
- The service call succeeds and the value is returned.
- The request lifetime scope ends and disposes the clients - closing the channels, deallocating resources.
No memory leak, nice cleanup.
Another scenario - Let's say you register things when lifetime scopes are created.
var builder = new ContainerBuilder();
builder.RegisterType<Alpha>();
var container = builder.Build();
using(var scope = container.BeginLifetimeScope(b => b.RegisterType<Beta>())
{
var f = scope.Resolve<Func<Beta>>();
// f is a Func<Beta> - call it, and you should get a B.
// If Func<Beta> doesn't respect lifetime scopes, what
// does this yield?
var beta = f();
}
This is actually a common pattern for things like WebAPI integration - a request message comes in, and when the request lifetime scope is created the request message is dynamically registered into the request scope. It doesn't exist at the global level.
You can probably see from some of this that the only logical way for Func<B> to behave is to obey lifetime scopes. It just wouldn't work if it didn't.