Skip to content

Factories and proxies

Construct a service yourself with Factory, or forward one registration to another with UseProxyFactory.

3 min read

Two properties change how a registration produces its instance rather than what it is registered as. Factory supplies your own construction code. UseProxyFactory forwards to another registration instead of constructing anything.

Factory

Factory names a static method on the attributed class that builds the instance. The generator emits it as a method group in the ImplementationFactory slot.

FactoryService.cs
using Immediate.Injections.Shared;
using Microsoft.Extensions.DependencyInjection;

[RegisterSingleton]
public sealed class FactoryDependency;

public interface IFactoryService
{
	FactoryDependency Dependency { get; }
}

[RegisterTransient<IFactoryService>(Factory = nameof(Create))]
public sealed class FactoryService(FactoryDependency dependency) : IFactoryService
{
	public FactoryDependency Dependency { get; } = dependency;

	public static FactoryService Create(IServiceProvider provider) =>
		new(provider.GetRequiredService<FactoryDependency>());
}
Generated output
ServiceDescriptor.Transient(typeof(IFactoryService), FactoryService.Create);

The required signature

RegistrationSignature
Unkeyedstatic TSelf Method(IServiceProvider)
Keyedstatic TSelf Method(IServiceProvider, object)

Four things the analyzer checks:

  • The method must be static. Instance methods do not qualify.
  • The return type must be the attributed class itself — not the service type, not a base class.
  • The parameter list must match exactly. An extra parameter, a missing one, or a wrong type is INJ0010.
  • The method must exist on the attributed class, or it is INJ0009.

Use nameof(...) rather than a string literal so a rename cannot silently break it.

Keyed registrations use the two-parameter overload, and that second parameter carries the key the caller requested — see Keyed services.

UseProxyFactory

UseProxyFactory = true makes the registration resolve through the container instead of constructing anything. The generated implementation factory is ServiceProviderServiceExtensions.GetRequiredService<TImplementation> — or ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService<TImplementation> when the registration is keyed.

Proxy.cs
using Immediate.Injections.Shared;

public interface IService;

[RegisterSingleton]                                  // registers Service -> Service
[RegisterSingleton<IService>(UseProxyFactory = true)] // registers IService -> resolve Service
public sealed class Service : IService;
Generated output
ServiceDescriptor.Singleton(
	typeof(IService),
	ServiceProviderServiceExtensions.GetRequiredService<Service>
);

Two combinations are rejected outright:

  • Proxying to self. [RegisterSingleton<Service>(UseProxyFactory = true)] on Service would resolve Service by resolving Service — an infinite loop. That is INJ0007. The same error fires for RegistrationStrategy.Self, for ServiceType = typeof(Service), and for a bare [RegisterSingleton(UseProxyFactory = true)], which resolves to Self.
  • Proxy plus factory, in the generic attribute forms and in most non-generic ones — the two compete for the same ImplementationFactory slot. That is INJ0011. There is one exception, below.

The shared-instance pattern

The reason UseProxyFactory exists: registering a class as itself and as its interfaces normally produces one descriptor per service type, and MSDI treats each as its own instance — so a singleton registered three ways gives you three objects. Proxying the interfaces at the concrete registration collapses them into one.

SharedService.cs
using Immediate.Injections.Shared;

public interface ISharedService
{
	Guid Id { get; }
}

[RegisterSingleton(
	RegistrationStrategy = RegistrationStrategy.SelfAndImplementedInterfaces,
	UseProxyFactory = true
)]
public sealed class SharedService : ISharedService
{
	public Guid Id { get; } = Guid.NewGuid();
}
Generated output
ServiceDescriptor.Singleton(typeof(SharedService), typeof(SharedService));
ServiceDescriptor.Singleton(
	typeof(ISharedService),
	ServiceProviderServiceExtensions.GetRequiredService<SharedService>
);

GetRequiredService<SharedService>() and GetRequiredService<ISharedService>() now return the same object. The self registration is never proxied — that would be INJ0007 — so the pair is always “one real registration, N forwarding ones”.

This is the combination to set as an assembly-wide default when migrating from a library whose default behaved this way; see Assembly-wide defaults.

With ImplementedInterfaces

The same works without the self registration, but then something else must register the concrete type:

ProxyOnly.cs
[RegisterSingleton]
[RegisterSingleton(
	RegistrationStrategy = RegistrationStrategy.ImplementedInterfaces,
	UseProxyFactory = true
)]
public sealed class SharedService : ISharedService;

Combining Factory with UseProxyFactory

FactoryWithProxy.cs
using Immediate.Injections.Shared;
using Microsoft.Extensions.DependencyInjection;

public interface IService;

[RegisterSingleton(
	RegistrationStrategy = RegistrationStrategy.SelfAndImplementedInterfaces,
	Factory = nameof(BuildService),
	UseProxyFactory = true
)]
public sealed class Service : IService
{
	public static Service BuildService(IServiceProvider sp) => new();
}
Generated output
ServiceDescriptor.Singleton(typeof(Service), Service.BuildService);
ServiceDescriptor.Singleton(
	typeof(IService),
	ServiceProviderServiceExtensions.GetRequiredService<Service>
);

That gives you custom construction and one shared instance behind every interface.

Where to go next