I've noticed in some .NET Core examples there are calls to TryAddSingleton
, and in some AddSingleton
when registering services.
Decompiler shows that TryAdd (called by TryAddSingleton) adds the specified param "descriptor" to the "collection" if the service type hasn't been already registered.
Does it mean that it's always safer to use TryAddSingleton, in case if some other method/library already registered the same class?
AddSingleton: Is used for instances that can be shared across the application. It is ideal for caching or to be used for objects that are costly to instantiate. Be careful not to accidentally make singletons of services that has state which should not be shared, especially if it is sensitive user data.
Singleton is a single instance for the lifetime of the application domain. Scoped is a single instance for the duration of the scoped request, which means per HTTP request in ASP.NET. Transient is a single instance per code request.
Use Transient lifetime for the lightweight service with little or no state. Scoped services service is the better option when you want to maintain state within a request. Singletons are created only once and not destroyed until the end of the Application. Any memory leaks in these services will build up over time.
The scope will reuse the object whatever has been created within the request. Transient will create a new instance within the request.
As you already noticed, the difference between TryAddSingleton
and AddSingleton
is that AddSingleton
always appends the registration to the collection, while TryAddSingleton
only does this when there exists no registration for the given service type.
When multiple registrations exist for the same service type, but a single instance is requested, .NET Core will always return the last one registered. This means that the behavior of AddSingleton
is to effectively replace instances for non-collection resolution, for instance:
services.AddSingleton<IX, A>(); services.AddSingleton<IX, B>(); // ‘replaces’ A IX x = container.GetService<IX>(); // resolves B
For collection resolution however, AddSingleton
behaves as a collection ‘append’ of already existing registrations for that service type. For instance:
services.AddSingleton<IX, A>(); services.AddSingleton<IX, B>(); IEnumerable<IX> xs = container.GetServices<IX>(); // resolves A *and* B
With TryAddSingleton
however, the registration will not be added when there already exist registrations for the given service type. This means that, independently of when a service type is resolved as one instance or as a collection of instances, the registration will not be added when there is at least one registration. For instance:
services.TryAddSingleton<IX, A>(); // adds A services.TryAddSingleton<IX, B>(); // does not add B, because of A IX x = container.GetService<IX>(); // resolves A services.TryAddSingleton <IX, A>(); // adds A services.TryAddSingleton <IX, B>(); // does not add B, because of A IEnumerable<IX> xs = container.GetServices<IX>(); // resolves A only
TryAddSingleton
is especially useful for framework and third-party library code that wishes to register its own components to the container. It allows an application developer to override the framework or library’s default registration, even if the application developer registered that component before the framework or third-party AddXXX
extension method is called. For instance:
services.TryAddSingleton<IX, A>(); // adds A services.AddThirdPartyLibrary(); // calls services.TryAddSingleton<IX, B>(); IX x = container.GetService<IX>(); // resolves A
If the third-party library had called AddSingleton
instead of TryAddSingleton
, the application developer’s A
will always be overridden, which is likely to result in unexpected behavior. As an application developer, you typically know what you registered, which makes the use of TryAddSingleton
less useful in such a case.
I would even argue that, from perspective of an application developer, the behavior of AddSingleton
can be very tricky, because it implicitly overrides an existing registration, without any warning whatsoever. My experience is that this behavior can cause hard to spot configuration errors. A safer design would have been to have AddSingleton
, AppendSingleton
and ReplaceSingleton
methods, where AddSingleton
would throw an exception in case a registration exists, and ReplaceSingleton
would actually discard the existing registration.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With