Reputation: 2570
Why does Autofac's implicit relationship type for dynamic instantiation respect lifetime scopes?
The docs on the subject states:
Lifetime scopes are respected using this relationship type. If you register an object as
InstancePerDependency()
and call theFunc<B>
multiple times, you’ll get a new instance each time. However, if you register an object asSingleInstance()
and call theFunc<B>
to resolve the object more than once, you will get the same object instance every time.
The docs also claim the purpose of this implicit relationship type is for these two purposes:
Delayed instantiation (Lazy<B>
) accomplishes purpose #1 in a manner which I understand and I agree with its design to respect lifetime scopes. I don't understand or agree with Autofac's design for Func<B>
as there is no way to accomplish purpose #2 with the dynamic instantiation implicit relationship type.
To work around this and actually accomplish #2, we manually register Func<B>
using the same factory method as we use to register B
and a closure:
void RegisterDependencies(ContainerBuilder builder)
{
builder.Register<B>(bFactory).InstancePerLifetimeScope();
builder.Register<Func<B>>(context => () => bFactory(context)).SingleInstance();
B bFactory(IComponentContext context) => new B(context.Resolve<A>(), ...);
}
This works to accomplish both purposes that dynamic instantiation claims to accomplish but requires us to be very verbose with many of our registrations.
Can someone shed some light on to why Autofac designed its implicit relationship type for dynamic instantiation to respect lifetime scopes?
Thank you in advance :)
Upvotes: 3
Views: 156
Reputation: 23924
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:
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?
Func<IServiceClient>
function to create - dynamically - a WCF service client for when it's needed.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:
Func<IServiceClient>
function to create - dynamically - a WCF service client for when it's needed.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.
Upvotes: 4