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 , .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.