6

I have 3 interfaces ( for singleton/scoped/transient) :

    public interface ISingleton
    {
    }

    class Singlet : ISingleton
    {
    }

    public interface IScoped
    {
    }

    class Scoped : IScoped
    {
    }


    public interface Itransient
    {
    }

    class Transient : Itransient
    {
    }

I register them as :

 services.AddScoped<IScoped, Scoped>();
 services.AddTransient<Itransient, Transient>();
 services.AddSingleton<ISingleton, Singlet>();

If I try to inject scoped service to singleton , I should (and do) get an exception (I know the reason for this):

    public interface ISingleton
    {
    }

    class Singlet : ISingleton
    {

        public Singlet( IScoped sc)
        {
            
        }
    }

Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: WebApplication2.ISingleton Lifetime: Singleton ImplementationType: WebApplication2.Singlet': Cannot consume scoped service 'WebApplication2.IScoped' from singleton 'WebApplication2.ISingleton'.)

But if I try to inject transient , I do not get an exception :

public interface ISingleton
    {
    }

    class Singlet : ISingleton
    {

        public Singlet( Itransient tr)
        {
            
        }
    }

Question:

Why does .net core forbids injecting a shorter lifetime (scoped) services raising an exception, while allowing transient to be injected to a singleton?

Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • 1
    "Why does .net core forbids injecting a shorter lifetime (scoped) services raising an exception, while allowing transient to be injected to a singleton?" - because the lifetime of a transient service _doesn't matter_ - it's not a shared resource. – Dai Jun 21 '21 at 13:34
  • @Dai But it's still Captive Dependency for that transient service. and what does you mean by "not matter" ? so why scoped does matter ? – Royi Namir Jun 21 '21 at 13:35
  • "it's still Captive Dependency for that transient service" - the transient service in your example doesn't have any dependencies. – Dai Jun 21 '21 at 13:36
  • @Dai I'm re-thinking about what you said : _it's not a shared resource._....it is not. – Royi Namir Jun 21 '21 at 13:40
  • So by the same logic are you saying that a scoped service is not allowed to ask for any transients? The problem with the scoped injection in your singlet is that the same instance is expected to be used across everything in that scope but outside of that scope a new instance is created. That requirement conflicts with a singleton because the singleton was already created it would keep using the scoped instance from the previous scope. For transients every service gets its own instance so there's never a scope/sharing issue. –  Jun 21 '21 at 13:40
  • @Knoop I note that transient services _can_ be shared by their consumers if they so wish - it's just that the MEDI container won't do that by itself. – Dai Jun 21 '21 at 13:40
  • @Dai my apologies, my comment was directed at OP. –  Jun 21 '21 at 13:41
  • So it's all about "Shareables protection". and since transient is not shared , there is no protection.... ? ( although , effectively that transient becomes singleton......") – Royi Namir Jun 21 '21 at 13:42
  • @RoyiNamir That's _one specific view_ - there's more to it than that. – Dai Jun 21 '21 at 13:44
  • @RoyiNamir That transient **instance**, not the entire transient services' registration. – Dai Jun 21 '21 at 13:44
  • @Dai sure...... I know. just like AddTypedClient in Http. – Royi Namir Jun 21 '21 at 13:44

1 Answers1

10

I suggest reading this first: When are .NET Core dependency injected instances disposed?

(Preface: My answer assumes that service implementations never dispose of any injected services themselves.)

Why does .net core forbids injecting a shorter lifetime (scoped) services raising an exception, while allowing transient to be injected to a singleton?

  • The lifetime of a transient service is the same as the container it's instantiated inside.
    • Transient services can be instantiated in either the root-scope/root-container (where singletons are) or inside a scoped container.
    • The lifetime of a new transient service instance injected into a singleton is the lifetime of the singleton's container (usually the root container or root-scope).
      • That is, the transient instance will be disposed-of only when the root container is disposed, usually during application shutdown (IHost.Dispose())
    • The lifetime of a new transient service instance injected into a scoped service is the lifetime of that scope container.
      • That is, the transient instance will be disposed-of only when the scope is disposed (in ASP.NET Core, that's at the end of the HTTP request lifetime).
    • The lifetime of a new transient service injected into another transient service is the lifetime of the ultimate consumer which is constructed by the DI provider.

In short: the lifetime of a transient service is the same as the service it's injected into - not longer, not shorter. That's why it's allowed.

Whereas the lifetime of a scoped service is shorter than the root-scope/root-container's lifetime, and the root-scope/root-container is used for holding singleton instances, therefore a scoped service cannot be consumed by a singleton (more importantly it's because child scopes don't exist in the context of the root scope).

Dai
  • 141,631
  • 28
  • 261
  • 374