Replies: 1 comment 4 replies
-
Of course, we can write our own overload which enforces strict singletons, but this requires developer attention or code analyzers. It is not "inherently safe" for singleton type registrations. Here's the sample code with demo tests: [Fact]
public void StrictSingletonForConcreteType()
{
IServiceCollection services = new ServiceCollection();
Action addStrictSingleton = () => services.AddStrictSingleton<X>();
addStrictSingleton();
addStrictSingleton.Should().Throw<InvalidOperationException>();
}
[Fact]
public void StrictSingletonForTypeWithInterface()
{
IServiceCollection services = new ServiceCollection();
Action addStrictSingleton = () => services.AddStrictSingleton<IMyInterface, Implementation>();
addStrictSingleton();
addStrictSingleton.Should().Throw<InvalidOperationException>();
}
public static class SingletonExtensions
{
public static IServiceCollection AddStrictSingleton<T>(this IServiceCollection services)
where T : class
{
return services
.ThrowIfTypeAlreadyRegistered<T>(
$"Cannot add {typeof(T).Name} as a singleton, because it was already registered in the service collection.")
.AddSingleton<T>();
}
public static IServiceCollection AddStrictSingleton<TInterface, TImplementation>(this IServiceCollection services)
where TInterface : class
where TImplementation : class, TInterface
{
return services
.ThrowIfTypeAlreadyRegistered<TInterface>(
$"Cannot add {typeof(TImplementation).Name} as a singleton {typeof(TInterface).Name}, because {typeof(TInterface).Name} was already registered in the service collection.")
.AddSingleton<TInterface, TImplementation>();
}
private static IServiceCollection ThrowIfTypeAlreadyRegistered<T>(this IServiceCollection services, string message)
{
if (services.HasRegistrationFor<T>())
throw new InvalidOperationException(message);
return services;
}
private static bool HasRegistrationFor<T>(this IServiceCollection services)
{
return services.Any(sd => sd.ServiceType == typeof(T));
}
} |
Beta Was this translation helpful? Give feedback.
4 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
The basic issue is this:
The
IServiceProvider
will return aTImpl2
on a request for aTInterface
. Developer 1 will be really surprised about this and developer 2 will probably not realize that he has overruled the intentions of developer 1.Similarly, two invocations of
services.AddSingleton<X>()
will result in two instances ofX
in the service provider. This is also unexpected behaviour for most developers.The advice seems to be to use
TryAddSingleton
, but this has multiple shortcomings:IServiceCollection
(i.e. inspect allServiceDescriptor
s)So, I'm looking for possibilities to enforce stricter rules on a subset of types (e.g. all types in my own assemblies). The type filter is obviously required for backwards compatibility.
Having completely overlooked the discussions tab, I already created an API proposal...
Basically, it would be good to be able to enforce strict singleton rules by writing something like this:
Given that in many cases we are using the generic host, our only real interaction with DI configuration is adding service to the
IServiceCollection
. With the generic host, developers are not involved in the creation of theIServiceProvider
and also don't have any access to it, so I think this API should be available on theIServiceCollection
.Beta Was this translation helpful? Give feedback.
All reactions