Pure DI pour .NET

Afin de suivre les principes de la POO et de SOLID , des bibliothèques d'injection de dépendances sont souvent utilisées. Il existe un grand nombre de ces bibliothèques, et toutes sont unies par un ensemble de fonctions communes:





  • API pour définir le graphe de dépendances





  • composition d'objets





  • gestion du cycle de vie des objets





Je voulais comprendre comment cela fonctionne, et la meilleure façon de le faire est d'écrire votre propre bibliothèque d'injection de dépendances IoC.Container . Il vous permet de faire des choses complexes de manière simple: cela fonctionne bien avec les types génériques - d' autres ne le peuvent pas , cela vous permet de créer du code, sans dépendances d'infrastructure et offre de bonnes performances par rapport à d'autres solutions similaires, mais PAS par rapport à une approche pure DI.





En utilisant des bibliothèques d'injection de dépendances classiques, nous obtenons la simplicité de définir le graphe de dépendances et perdons en performances. Cela nous oblige à rechercher des compromis. Ainsi, dans le cas où vous devez travailler avec un grand nombre d'objets, l'utilisation de la bibliothèque d'injection de dépendances peut ralentir l'exécution de l'application. L'un des compromis ici sera d'éviter d'utiliser des bibliothèques dans cette partie du code et de créer des objets à l'ancienne. Cela mettra fin au graphe prédéfini et prévisible, et chaque cas spécial augmentera la complexité globale du code. En plus de l'impact sur les performances, les bibliothèques classiques peuvent être une source de problèmes d'exécution dus à une mauvaise configuration.





En DI pure, la composition des objets se fait manuellement: il y a généralement beaucoup de constructeurs qui prennent d'autres constructeurs comme arguments, et ainsi de suite. Il n'y a pas de frais généraux supplémentaires. Le compilateur vérifie l'exactitude de la composition. La gestion de la durée de vie des objets ou de tout autre problème est résolu à mesure qu'ils surviennent de manière efficace pour une situation particulière ou plus préférée par l'auteur du code. À mesure que le nombre de nouvelles classes augmente, ou à chaque nouvelle dépendance, la complexité du code de composition d'objet augmente de plus en plus vite. À un moment donné, vous pouvez perdre le contrôle de cette complexité, ce qui ralentira considérablement le développement ultérieur et entraînera des erreurs. Par conséquent, d'après mon expérience, la DI pure est applicable tant que la quantité de code est faible.





Et si nous ne gardions que le meilleur de ces approches:





  • 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 : est appelé chaque fois qu'une dépendance est introuvable et: renvoie l'objet créé pour injection, lève une exception ou retourne null pour laisser le comportement par défaut. Lorsque la stratégie de secours est attachée, le générateur change l'erreur en un avertissement, en supposant que la situation est sous votre contrôle et que le code deviendra compilable.IFallback



. Resolve<>()







J'espère que cette bibliothèque est utile. Tous les commentaires et idées sont grandement appréciés.








All Articles