ServiceCollectionExtensions
namespace Invex.Extensions.Hosting;
public static class ServiceCollectionExtensions
Provides extension methods for IServiceCollection that register a single implementation type
under multiple service types ("forwarded" registrations) while sharing one instance per lifetime.
The standard AddSingleton/AddScoped methods create an independent instance per service type
when the same implementation is registered multiple times. The overloads in this class instead
register the implementation type once and forward each service type to it, so resolving any of the
service types yields the same instance (per scope, for scoped registrations).
Conceptual guide: Multi-interface registration
Methods
AddSingleton
public static IServiceCollection AddSingleton<TService1, TService2, TImplementation>(
this IServiceCollection services)
where TService1 : class
where TService2 : class
where TImplementation : class, TService1, TService2;
Overloads exist for 2, 3, 4, and 5 service types
(AddSingleton<TService1, …, TService5, TImplementation>), each with the corresponding
where TImplementation : class, TService1, …, TServiceN constraint.
Adds a singleton service of each specified service type with a single shared implementation instance.
| Type parameters | TService1…N — the service types to add. TImplementation — the implementation type; must implement all service types. |
| Parameters | services — the IServiceCollection to add the service to. |
| Returns | The services instance so that additional calls can be chained. |
Remarks. A single registration is created for TImplementation, and each service type is
registered as a forward to it, so all service types (and TImplementation itself) resolve to the
same shared instance.
services.AddSingleton<IFoo, IBar, MyService>();
// All three resolve to the same object:
var a = provider.GetRequiredService<IFoo>();
var b = provider.GetRequiredService<IBar>();
var c = provider.GetRequiredService<MyService>();
AddScoped
public static IServiceCollection AddScoped<TService1, TService2, TImplementation>(
this IServiceCollection services)
where TService1 : class
where TService2 : class
where TImplementation : class, TService1, TService2;
Overloads exist for 2, 3, 4, and 5 service types.
Adds a scoped service of each specified service type with a single implementation instance shared within each scope.
| Type parameters | TService1…N — the service types to add. TImplementation — the implementation type; must implement all service types. |
| Parameters | services — the IServiceCollection to add the service to. |
| Returns | The services instance so that additional calls can be chained. |
Remarks. All service types resolve to the same instance within a given scope; different scopes receive different instances.
AddHostedService
public static IServiceCollection AddHostedService<TService, TImplementation>(
this IServiceCollection serviceCollection)
where TService : class
where TImplementation : class, TService, IHostedService;
Overloads exist for 1, 2, 3, 4, and 5 service types
(AddHostedService<TService1, …, TService5, TImplementation>); TImplementation must additionally
implement IHostedService.
Adds a hosted service of each specified service type with a single shared implementation instance
that is also registered as the IHostedService.
| Type parameters | TService1…N — the service types to add. TImplementation — the implementation type; must implement all service types and IHostedService. |
| Parameters | serviceCollection — the IServiceCollection to add the service to. |
| Returns | The serviceCollection instance so that additional calls can be chained. |
Remarks. TImplementation is registered as a singleton and as a hosted service. Every service
type and the hosted service resolve to the same shared instance, allowing other components to
interact with the running hosted service through its service interfaces.
services.AddHostedService<IWorkerStatus, MyWorker>();
// The running hosted service is the same object as:
var status = provider.GetRequiredService<IWorkerStatus>();
Trimming / AOT
On .NET 8 and later, the TImplementation type parameter of every overload is annotated with
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)], so the
constructors required by the DI container are preserved under trimming.