Pure DI para .NET

Para seguir os princípios de OOP e SOLID , as bibliotecas de injeção de dependência são frequentemente usadas. Existem muitas dessas bibliotecas, e todas elas são unidas por um conjunto de funções comuns:





  • API para definir o gráfico de dependência





  • composição de objetos





  • gerenciamento do ciclo de vida do objeto





Eu estava interessado em entender como isso funciona, e a melhor maneira de fazer isso é escrever sua própria biblioteca de injeção de dependência IoC.Container . Ele permite que você faça coisas complexas de maneiras simples: funciona bem com tipos genéricos - outros não , permite criar código sem dependências de infraestrutura e oferece bom desempenho em comparação com outras soluções semelhantes, mas NÃO em comparação com uma abordagem DI pura.





Usando bibliotecas clássicas de injeção de dependência, obtemos a simplicidade de definir o gráfico de dependência e perdemos desempenho. Isso nos obriga a buscar compromissos. Portanto, no caso em que você precisa trabalhar com um grande número de objetos, o uso da biblioteca de injeção de dependência pode retardar a execução do aplicativo. Uma das desvantagens aqui será evitar o uso de bibliotecas nesta parte do código e criar objetos da maneira antiga. Isso encerrará o gráfico predefinido e previsível, e cada caso especial aumentará a complexidade geral do código. Além do impacto no desempenho, as bibliotecas clássicas podem ser uma fonte de problemas de tempo de execução devido à configuração incorreta.





No DI puro, a composição do objeto é feita manualmente: geralmente há muitos construtores que tomam outros construtores como argumentos e assim por diante. Não há custos indiretos adicionais. O compilador verifica a exatidão da composição. O gerenciamento do tempo de vida de objetos ou quaisquer outros problemas são resolvidos à medida que surgem de maneiras que são eficazes para uma situação particular ou mais preferida pelo autor do código. Conforme o número de novas classes aumenta, ou com cada nova dependência, a complexidade do código de composição do objeto cresce cada vez mais rápido. Em algum ponto, você pode perder o controle sobre essa complexidade, o que, subsequentemente, tornará o desenvolvimento mais lento e levará a erros. Portanto, em minha experiência, DI puro é aplicável desde que a quantidade de código seja pequena.





E se mantivermos apenas o melhor dessas abordagens:





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



chamado sempre que uma dependência não pode ser encontrada e: retorna o objeto criado para injeção, lança uma exceção ou retorna nulo para deixar o comportamento padrão. Quando a estratégia de fallback é anexada, o gerador mudará o erro para um aviso, assumindo que a situação está sob seu controle, e o código se tornará compilável.





Espero que esta biblioteca seja útil. Quaisquer comentários e ideias são muito apreciados.








All Articles