DI pura para .NET

Para seguir los principios de OOP y SOLID , a menudo se utilizan bibliotecas de inyección de dependencia. Hay muchas de estas bibliotecas, y todas están unidas por un conjunto de funciones comunes:





  • API para definir el gráfico de dependencia





  • composición de objetos





  • gestión del ciclo de vida de los objetos





Estaba interesado en comprender cómo funciona, y la mejor manera de hacerlo es escribir su propia biblioteca de inyección de dependencia de contenedores IoC . Le permite hacer cosas complejas de formas simples: funciona bien con tipos genéricos, otros no pueden , le permite crear código sin dependencias de infraestructura y proporciona un buen rendimiento en comparación con otras soluciones similares, pero NO en comparación con un enfoque DI puro.





Al usar bibliotecas de inyección de dependencia clásicas, obtenemos la simplicidad de definir el gráfico de dependencia y perdemos en rendimiento. Esto nos obliga a buscar compromisos. Entonces, en el caso de que necesite trabajar con una gran cantidad de objetos, el uso de la biblioteca de inyección de dependencias puede ralentizar la ejecución de la aplicación. Una de las compensaciones aquí será evitar el uso de bibliotecas en esta parte del código y crear objetos a la antigua. Esto finalizará el gráfico predefinido y predecible, y cada caso especial aumentará la complejidad general del código. Además del impacto en el rendimiento, las bibliotecas clásicas pueden ser una fuente de problemas de tiempo de ejecución debido a una mala configuración.





En DI pura, la composición de objetos se hace manualmente: generalmente hay muchos constructores que toman otros constructores como argumentos, y así sucesivamente. No hay gastos generales adicionales. El compilador comprueba la corrección de la composición. La gestión de la vida útil de los objetos o cualquier otro problema se aborda a medida que surgen de formas que sean efectivas para una situación particular o más preferidas por el autor del código. A medida que aumenta el número de nuevas clases, o con cada nueva dependencia, la complejidad del código de composición de objetos crece cada vez más rápido. En algún momento, puede perder el control sobre esta complejidad, lo que posteriormente ralentizará enormemente el desarrollo posterior y dará lugar a errores. Por lo tanto, en mi experiencia, DI puro es aplicable siempre que la cantidad de código sea pequeña.





¿Qué pasa si mantenemos solo lo mejor de estos enfoques?





  • 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<>()



se llama siempre que no se puede encontrar una dependencia y: devuelve el objeto creado para la inyección, lanza una excepción o devuelve nulo para dejar el comportamiento predeterminado. Cuando se adjunta la estrategia de respaldo , el generador cambiará el error por una advertencia, asumiendo que la situación está bajo su control, y el código se volverá compilable.





Espero que esta biblioteca sea útil. Cualquier comentario e idea es muy apreciado.








All Articles