Controlling Scope and Lifetime

A great place to start learning about Autofac scope and lifetime is in Nick Blumhardt’s Autofac lifetime primer. There’s a lot to digest, though, and a lot of intermixed concepts there, so we’ll try to complement that article here.

You may recall from the registration topic that you add components to the container that implement services. You then end up resolving services and using those service instances to do your work.

The lifetime of a service is how long the service instance will live in your application - from the original instantiation to disposal. For example, if you “new up” an object that implements IDisposable and then later call Dispose() on it, the lifetime of that object is from the time you instantiated it all the way through disposal (or garbage collection if you didn’t proactively dispose it).

The scope of a service is the area in the application where that service can be shared with other components that consume it. For example, in your application you may have a global static singleton - the “scope” of that global object instance would be the whole application. On the other hand, you might create a local variable in a for loop that makes use of the global singleton - the local variable has a much smaller scope than the global.

The concept of a lifetime scope in Autofac combines these two notions. Effectively, a lifetime scope equates with a unit of work in your application. A unit of work might begin a lifetime scope at the start, then services required for that unit of work get resolved from a lifetime scope. As you resolve services, Autofac tracks disposable (IDisposable) components that are resolved. At the end of the unit of work, you dispose of the associated lifetime scope and Autofac will automatically clean up/dispose of the resolved services.

The two important things lifetime scopes control are sharing and disposal.

  • Lifetime scopes are nestable and they control how components are shared. For example, a “singleton” service might be resolved from a root lifetime scope while individual units of work may require their own instances of other services. You can determine how a component is shared by setting its instance scope at registration.
  • Lifetime scopes track disposable objects and dispose of them when the lifetime scope is disposed. For example, if you have a component that implements IDisposable and you resolve it from a lifetime scope, the scope will hold onto it and dispose of it for you so your service consumers don’t have to know about the underlying implementation. You have the ability to control this behavior or add new disposal behavior if you choose.

As you work in your application, it’s good to remember these concepts so you make the most efficient use of your resources.

It is important to always resolve services from a lifetime scope and not the root container. Due to the disposal tracking nature of lifetime scopes, if you resolve a lot of disposable components from the container (the “root lifetime scope”), you may inadvertently cause yourself a memory leak. The root container will hold references to those disposable components for as long as it lives (usually the lifetime of the application) so it can dispose of them. You can change disposal behavior if you choose, but it’s a good practice to only resolve from a scope. If Autofac detects usage of a singleton or shared component, it will automatically place it in the appropriate tracking scope.

Let’s look at a web application as a more concrete example to illustrate lifetime scope usage. Say you have the following scenario:

  • You have a global singleton logging service.
  • Two simultaneous requests come in to the web application.
  • Each request is a logical “unit of work” and each requires its own order processing service.
  • Each order processing service needs to log information to the logging service.

In this scenario, you’d have a root lifetime scope that contains the singleton logging service and you’d have one child lifetime scope per request, each with its own order processing service:

+---------------------------------------------------+
|                 Autofac Container                 |
|                Root Lifetime Scope                |
|                                                   |
|                  Logging Service                  |
|            (shared across all requests)           |
|                                                   |
| +----------------------+ +----------------------+ |
| |  First Request Scope | | Second Request Scope | |
| |                      | |                      | |
| |   Order Processor    | |   Order Processor    | |
| +----------------------+ +----------------------+ |
+---------------------------------------------------+

When each request ends, the request lifetime scope ends and the respective order processor gets disposed. The logging service, as a singleton, stays alive for sharing by future requests.

You can dive deeper on lifetime scopes in Nick Blumhardt’s Autofac lifetime primer.

Additional lifetime scope topics to explore: