Pure DI for .NET

In order to follow the principles of OOP and SOLID , dependency injection libraries are often used. There are many of these libraries, and all of them are united by a set of common functions:





  • API for defining the dependency graph





  • composition of objects





  • object lifecycle management





I was interested in understanding how it works, and the best way to do it is to write your own IoC.Container dependency injection library . It allows you to do complex things in simple ways: it works well with generic types - others cannot , it allows you to create code without infrastructure dependencies and provides good performance compared to other similar solutions, but NOT compared to a pure DI approach.





Using classic dependency injection libraries, we get the simplicity of defining the dependency graph and lose in performance. This forces us to seek compromises. So, in the case when you need to work with a large number of objects, using the dependency injection library can slow down the execution of the application. One of the tradeoffs here will be to avoid using libraries in this part of the code, and create objects the old fashioned way. This will end the predefined and predictable graph, and each such special case will increase the overall complexity of the code. In addition to the performance impact, classic libraries can be a source of runtime problems due to misconfiguration.





In pure DI, object composition is done manually: usually there are a large number of constructors, the arguments of which are other constructors, and so on. There are no additional overhead costs. The compiler checks the correctness of the composition. Managing the lifetime of objects or any other issues are addressed as they arise in ways that are effective for a particular situation or more preferred by the author of the code. As the number of new classes increases, or with each new dependency, the complexity of the object composition code grows faster and faster. At some point, you can lose control over this complexity, which will subsequently greatly slow down further development and lead to errors. Therefore, in my experience, pure DI is applicable as long as the amount of code is small.





What if we keep only the best of these approaches:





  • API





  • DI





  • ""





, . , - . .NET , /API . , JIT.





, - Pure.DI! - , . NuGet beta , :





  • Pure.DI.Contracts API





  • Pure.DI





Pure.DI.Contracts , .NET Framework 3.5, .NET Standard .NET Core , , .NET 5 6, .NET Framework 2, . API, , , C#. API IoC.Container.





.NET 5 source code generator Roslyn Pure.DI. IDE , . “” . , .





, , “” “”:





interface IBox<out T> { T Content { get; } }

interface ICat { State State { get; } }

enum State { Alive, Dead }
      
      



“ ” :





class CardboardBox<T> : IBox<T>
{
    public CardboardBox(T content) => Content = content;

    public T Content { get; }
}

class ShroedingersCat : ICat
{
  //  
  private readonly Lazy<State> _superposition;

  public ShroedingersCat(Lazy<State> superposition) =>
    _superposition = superposition;

  //    
  //        
  public State State => _superposition.Value;

  public override string ToString() => $"{State} cat";
}

      
      



, . DI, SOLID.





, , . Pure.DI.Contracts Pure.DI. “” :





static partial class Glue
{
  //    ,
  //   ,     
  private static readonly Random Indeterminacy = new();

  static Glue()
  {
    DI.Setup()
      //    
      .Bind<State>().To(_ => (State)Indeterminacy.Next(2))
      //     
      .Bind<ICat>().To<ShroedingersCat>()
      //     
      .Bind<IBox<TT>>().To<CardboardBox<TT>>()
      //         
      //   
      .Bind<Program>().As(Singleton).To<Program>();
  }
}

      
      



Setup()



DI “”. static partial , , “DI”. Setup()



string . “Indeterminacy”, Glue static partial, .





Setup()



Bind<>()



To<>()



, :





.Bind().To()







ICat - , , .NET . ShroedingersCat - , .NET . , , . - , . , Bind<>()



, To<>()



. :





  • Bind<>()



    ,





  • As(Lifetime)



    , ,





  • Tag(object)



    , , ,





, :





  • Transient - ,





  • Singleton - ,





  • PerThread -





  • PerResolve -





  • Binding - ILifetime





, , . , :





.Bind().Tag(“Fat”).Tag(“Fluffy”).To()







, Bind<>()



To<>()



- . , . , , typeof(IBox<>)



API , “TT”. - IBox<TT>



, CardboardBox<TT>



. ? , . TT, TT1, TT2 .. API . . c , [GenericTypeArgument]



, :





[GenericTypeArgument]
public class TTMy: IMyInterface { }
      
      



To<>()



. . , “ ” . [Obsolete]



. , , , - . To<>(factory)



. , ,





.Bind<IBox>().To<CardboardBox>()











.Bind<IBox>().To(ctx => new CardboardBox(ctx.Resolve()))







To<>(factory)



lambda , . lambda , - ctx, . ctx.Resolve()



TT . Resolve()



, - object.





!





class Program
{
  //      
  public static void Main() => Glue.Resolve<Program>().Run();

  private readonly IBox<ICat> _box;

  internal Program(IBox<ICat> box) => _box = box;

  private void Run() => Console.WriteLine(_box);
}
      
      



void Main()



Glue.Resolve<Program>()



. Composition Root, , , , , . Resolve<>()



:





static class ProgramSingleton
{
  static readonly Program Shared = 
    new Program(
      new CardboardBox<ICat>(
        new ShroedingersCat(
          new Lazy<State>(
            new Func<State>(
              (State)Indeterminacy.Next(2))))));
}
      
      



, Program Singleton Resolve<>()



Program . , Shared



ProgramSingleton, Glue.





, . ,





ShroedingersCat(Lazy<State> superposition)







Lazy<>



.NET. , Lazy<>



? , Pure.DI BCL Lazy<>, Task<>, Tuple<..>



, . , . DependsOn()



, , .





, ? - Func<>



, BCL . , ICat



, - Func<ICat>



, .





. , . , IEnumerable<ICat>,



ICat[]



.NET, IReadOnlyCollection<T>



. , IEnumerable<ICat>



.





, , API . To<>(factory)



c lambda , , .





, , - . API . , , , TagAttribute:





  • : .Bind<ICat>().Tag(“Fat”).Tag(“Fluffy”).To<FatCat>()







  • : BigBox([Tag(“Fat”)] T content) { }







TagAttribute :





  • TypeAttribute - , , , ,





  • OrderAttribute - , /





  • OrderAttribute -





, , Pure.DI.Contracts. , , , . , :





  • TypeAttribute<>()







  • TagAttribute<>()







  • OrderAttribute<>()







, - : , , . 0, , . , , , “InjectAttribute”, , .





. , Roslyn API, IDE , . . , IDE , , . . , , , . , fallback : IFallback



. Resolve<>()



called whenever a dependency cannot be found and: returns the created object for injection, throws an exception, or returns null to leave the default behavior. When the fallback strategy is attached, the generator will change the error to a warning, assuming that the situation is under your control, and the code will become compileable.





Hope this library is helpful. Any comments and ideas are greatly appreciated.








All Articles