Search code examples
c#dependency-injectionsimple-injector

Supplying runtime data to the Container inside a parallel for loop


public class Factor1(int FactorX, int FactorY) : IFactors;
public class Factor2(int FactorX, int FactorY, int FZ) : IFactors;

public interface IFactors
{
    int FactorX { get; set; }
    int FactorY { get; set; }
}

public class BusinessLayerClass1
{
    IFactors _factorObj;
    public BusinessLayerClass1(IFactors factorObj)
    {
        _factorObj = factorObj;
    }
    public void Call() =>
        new BusinessLayerClass2(_factorObj).ShowFactors();
}

public class BusinessLayerClass2
{
    IFactors _factorObj;
    public BusinessLayerClass2(IFactors factorObj)
    {
        _factorObj = factorObj;
    }

    public void ShowFactors()
    {
        Debug.WriteLine(String.Concat(_factorObj.FactorX.ToString(), " - " +
         _factorObj.FactorY.ToString()));
    }
}

Main program

using SimpleInjector;

class Program
{
    public static void Main(string[] args)
    {
        List<Task> tasks = new List<Task>();
        for (var i = 0; i < 10; i++)
        {
            tasks.Add(Task.Factory.StartNew((num) =>
            {
                int num_ = (int)num;
                var factorObj = new Factor1() { FactorX = num_, FactorY = num_ + 1 };
                var container = new Container();
                var lifestyle = Lifestyle.Transient;
                container.Register<BusinessLayerClass1>(lifestyle);
                container.Register<BusinessLayerClass2>(lifestyle);
                container.RegisterInstance<IFactors>(factorObj);
                container.Verify();
                var BL = container.GetInstance<BusinessLayerClass1>();
                BL.Call();
            }, i));
        }
        Task.WaitAll(tasks.ToArray());
    }
}
  • Here, i implement Dependency injection to BusinessLayer classes to process factors(show values in that example).
  • Factor1 objects must be created at the beginning asynchronously using Task factory.
  • This works but i wonder if this is the right way to implement DI(for this example)?
  • If yes then, is there a better/convenient way to do that.
  • If no then, how should i implement DI regarding to this example?

Solution

    • You are creating a new container instance inside a while loop. This is not advised because of the performance overhead this is giving in a real-life application (see the Simple Injector documentation for more info). Perhaps not so much an issue for a short-lived Console application, but certainly something to consider.
    • The reason you seem to be creating a many container instances is likely because the Factor classes consist of runtime data and you are injecting runtime data into your application components. This practice is discourages, as I explained here. The refernced article also explains what yo do instead, which is to "let runtime data flow through the method calls of constructed object graphs.".
    • In your case this either means
      1. Changing the BL.Call method to include that runtime data as input parameter, and later on call BL.Call(factorObj) or
      2. Defining an IFactorContext interface/class pair, register it as Scoped, and supply the runtime values to a resolved IFactorContext.

    Concerning the second option, this might look like this:

    // Definitions
    public interface IFactorContext { IFactors Factors { get; } }
    public class FactorContext : IFactorContext { public IFactors Factors { get; set; } }
    
    // Register container once
    var container = new Container();
    container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
    container.Register<BusinessLayerClass1>();
    container.Register<BusinessLayerClass2>();
    container.Register<FactorContext>(Lifestyle.Scoped);
    container.Register<IFactorContext, FactorContext>(Lifestyle.Scoped);
    container.Verify();
    
    // Operate loop many times
    tasks.Add(Task.Factory.StartNew((num) =>
    {
      using (AsyncScopedLifestyle.BeginScope(container))
      {
          int num_ = (int)num;
          var factorObj = new Factor1() { FactorX = num_, FactorY = num_ + 1 };
      
          // Always reuse the single container instance here.
          container.GetInstance<FactorContext>().Factor = factorObj;
      
          var BL = container.GetInstance<BusinessLayerClass1>();
          BL.Call();
      }
    }, i));