Search code examples
c#wpfinversion-of-controlsimple-injector

Simple Injector - Singleton with transient dependency design


The context

I know the purpose of SimpleInjector's LifestyleMismatch exception and why it throws it. But suppose to have:

Players.dll

public abstract class PlayerEqualizer { ... }

public abstract class Player : IPlayer, ISongAware
{
    public Player(PlayerEqualizer eq)
    {
        Equalizer = eq;
    }

    public PlayerEqualizer Equalizer { get; }
    public abstract void StartPlay();
}

Players.Rock.dll

public class RockPlayerEqualizer  : PlayerEqualizer {}

public class RockPlayer : Player
{
    public RockPlayer(RockPlayerEqualizer  eq) : base(eq) {}

    public override void StartPlay() { ... }
}

public class RnBPlayer : Player
{
    public RnBPlayer(RockPlayerEqualizer  eq) : base(eq) {}

    public override void StartPlay() { ... }
}

Players.Pop.dll

public class PopPlayerEqualizer : PlayerEqualizer{}

public class PopPlayer : Player
{
    public PopPlayer(PopPlayerEqualizer eq) : base(eq) {}

    public override void StartPlay() { ... }
}

All the implementation of Player are registered as a collection of IPlayer and all the registrations are singleton:

var registrations = container
    .GetTypesToRegister(typeof(IPlayer), assemblies)
    .Select(t => Lifestyle.Singleton.CreateRegistration(t, container));


foreach (var registration in registrations)
{
    container.AddRegistration(registration.ImplementationType, registration);
}

container.RegisterCollection<IPlayer>(registrations);
container.RegisterCollection<ISongAware>(container.GetCurrentRegistrations()
    .Where(ip => typeof(ISongAware).IsAssignableFrom(ip.ServiceType))
    .Select(ip => ip.Registration));

The problem

All

  • container.GetInstance<RockPlayer>
  • container.GetAllInstances<IPlayer>
  • container.GetAllInstances<ISongAware>

must return the same instance, so the IPlayer registrations must be singleton. Doing so, all the PlayerEqualizer must be singleton as well, since they are dependency of a singleton registration, but the PlayerEqualizer implementation aren't singleton (RockPlayer and RnBPlayer both depend on RockPlayerEqualizer but they need different instances).

What I tried

The only solution I could find is to set the SimpleInjector container.Options.SuppressLifestyleMismatchVerification flag to False but I don't want to lose that feature... Another option could be call the SuppressDiagnosticWarning method on the IPlayer's registrations but despite I couldn't get it to work, my real concern is that these solutions are just workaround...

Am I missing something?


Solution

  • What you want is not to register PlayerEqualizer instances as Transient but as Instance Per Dependency.

    Technically, both lifestyles are the same, as they both return new instances on every request. The intend of Instance per Dependency is however, very different, because:

    Each consumer will get a new instance of the given service type and that dependency is expected to get live as long as its consuming type.

    While with Transient the intention is the dependency to be short-lived.

    This lifestyle is deliberately left out of Simple Injector, because:

    its usefulness is very limited compared to the Transient lifestyle. It ignores lifestyle mismatch checks and this can easily lead to errors, and it ignores the fact that application components should be immutable. In case a component is immutable, it’s very unlikely that each consumer requires its own instance of the injected dependency.

    The project's Code Samples however contains the definition of a InstancePerDependencyLifestyle that does what you want it do do:

    • It gives every consumer its own instance
    • It ignores lifestyle mismatches on the registration, since the instance is expected to live as long as its consumer

    You can use this lifestyle as follows:

    container.Register<RockPlayerEqualizer>(new InstancePerDependencyLifestyle());
    

    UPDATE:

    Do note that your configuration can be simplified to the following:

    var playerTypes = var registrations = container
        .GetTypesToRegister(typeof(IPlayer), assemblies);
    
    foreach (Type playerType in playerTypes)
    {
        container.Register(playerType, Lifestyle.Singleton);
    }
    
    container.RegisterCollection<IPlayer>(assemblies);
    container.RegisterCollection<ISongAware>(assemblies);