Skip to content

Service Lifecycles in C#

service lifecycles in c#
Lost in coding? Discover our Learning Paths!
Lost in coding? Discover our Learning Paths!

In this article, we’ll learn about Service Lifecycles in C# by examples. In C#, Dependency Injection (DI) containers or Inversion of Control (IoC) containers are responsible for creating and managing the lifecycle of objects, including how they are created, how they are disposed, and how they are shared between components.

Imagine that we have the following service:

class MyService { }

And the following component needs it.

class MyComponent
{
    private readonly MyService _service;
    public MyComponent(MyService service)
    {
        _service = service;
    }
}

And we want to let an Inversion of Control Container instantiate the service and the component. 

In this example let’s use the dotnet’s IoC library. So install the Microsoft.Extensions.DependencyInjection Nuget package.

And create a new instance of the service collection.

using Microsoft.Extensions.DependencyInjection;

// Create a new service collection
var services = new ServiceCollection();

There are three method of adding services to the collection. To see an example of each, let’s register MyService as a singleton, and MyComponent as a scoped for now.

// Add a singleton service 
services.AddSingleton<MyService>();

// Add a scoped service 
services.AddScoped<MyComponent>();

 

For now, we use the AddSingletonand AddScoped extension methods to register the services with the DI container. These methods determine the lifecycle of the service, so to speak.

  • The AddSingleton method creates a single instance of the service for the entire application.
  • AddScoped creates a single instance of the service per scope (I’ll explain it later in this article).
  • AddTransient creates a new service instance each time it is requested (I’ll illustrate this later in this article).

You’ll see the difference better once we instantiate the services. But we need a service provider to instantiate the services. So let’s create one that can provide the dependency instances based on the configuration above.

// Create a new service provider 
var provider = services.BuildServiceProvider();

 

Singleton

We registered MyService as a singleton. Now, if I use the providerto create two instances of MyService:

// Resolve a singleton service 
var service1 = provider.GetService<MyService>();
var service2 = provider.GetService<MyService>();

The instances are identical. I can prove it. 😉

// Both service1 and service2 should be the same instance 
Console.WriteLine($"service1 == service2: {service1 == service2}");

This is the console’s output:

service1 == service2: True

 

Scoped

If I create two instances of a scoped dependency:

using (var scope1 = provider.CreateScope())
{
    var component1 = scope1.ServiceProvider.GetService<MyComponent>();
    var component2 = scope1.ServiceProvider.GetService<MyComponent>();
    
    Console.WriteLine($"component1 == component2: {component1 == component2}");
}

Both variables refer to the same object within the same scope. And that’s how you can create scopes.

So the output is: 

component1 == component2: True

 

To clarify what that means, let’s create each component instance in a different scope.

MyComponent component1;
MyComponent component2;

using (var scope1 = provider.CreateScope())
{
    component1 = scope1.ServiceProvider.GetService<MyComponent>();
}

using (var scope2 = provider.CreateScope())
{
   component2 = scope2.ServiceProvider.GetService<MyComponent>();
}

And test it:

Console.WriteLine($"component1 == component2: {component1 == component2}");

So the output is:

component1 == component2: False

So, scopes help us separate environments in which the instances are shared. You can resolve the service using the GetService method and control the scope of the service by creating a scope using the CreateScope method and resolving the service in that scope.

Transient

Transient is the simplest one. Each time it creates a new instance regardless of the scope. This is how I can register a transient dependency:

services.AddTransient<MyComponent>();

 

Now, if I ask the provider for the service twice:

// Resolve a transient service 
var component3 = provider.GetService<MyComponent>();
var component4 = provider.GetService<MyComponent>();

 

Guess the output of the following test 😃

Console.WriteLine($"component3 == component4: {component3 == component4}");

Right! `component3` and component4 should refer to different instances. So the output is:

component3 == component4: False

By the way, did you know that we offer a unique online course that boosts your C# career? Check it out here!

Conclusion

There are three methods for service dependency resolution when using Dotnet’s built-in IoC library.

  • The AddSingleton method creates a single instance of the service for the entire application.
  • AddScoped creates a single instance of the service per scope.
  • AddTransient creates a new service instance each time it is requested.

ASP.NET creates a scope per request. If you register a dependency as scoped, ASP.NET shares the instances of that dependency with all clients (clients are the objects that need the dependency in the Object-Oriented programming context). 

Lost in coding? Discover our Learning Paths!
Lost in coding? Discover our Learning Paths!
Tired of being just an average developer?
Stop wasting your time and learn coding the right (and easy) way!
Tired of being just an average developer?
Stop wasting your time and learn coding the right (and easy) way!
Enter your email and we will send you the PDF guide:
Enter your email and we will send you the PDF guide