diff --git a/README.md b/README.md index 15a466961..daa4ae877 100644 --- a/README.md +++ b/README.md @@ -298,8 +298,7 @@ dotnet run - [https://devteam.github.io/Pure.DI/](https://devteam.github.io/Pure.DI/) - Git repo with examples - [Schrödinger's cat](https://github.com/DevTeam/Pure.DI.Example) - - [How to use Pure.DI to create libraries](https://github.com/DevTeam/Pure.DI.Solution) - + - [How to use Pure.DI to create and test libraries](https://github.com/DevTeam/Pure.DI.Solution) ## Generated Code Each generated class, hereafter called a _composition_, must be customized. Setup starts with a call to the `Setup(string compositionTypeName)` method: diff --git a/readme/Avalonia.md b/readme/Avalonia.md index 328b133c2..9e966deb9 100644 --- a/readme/Avalonia.md +++ b/readme/Avalonia.md @@ -4,6 +4,9 @@ This example demonstrates the creation of a [Avalonia](https://avaloniaui.net/) application in the pure DI paradigm using the _Pure.DI_ code generator. +> [!NOTE] +> [Another example](samples/SingleRootAvaloniaApp) with Avalonia shows how to create an application with a single composition root. + The definition of the composition is in [Composition.cs](/samples/AvaloniaApp/Composition.cs). You must not forget to define any necessary composition roots, for example, these can be view models such as _ClockViewModel_: ```csharp diff --git a/readme/AvaloniaPageTemplate.md b/readme/AvaloniaPageTemplate.md index 1cbd03d86..907b4e7fc 100644 --- a/readme/AvaloniaPageTemplate.md +++ b/readme/AvaloniaPageTemplate.md @@ -4,6 +4,9 @@ This example demonstrates the creation of a [Avalonia](https://avaloniaui.net/) application in the pure DI paradigm using the _Pure.DI_ code generator. +> [!NOTE] +> [Another example](samples/SingleRootAvaloniaApp) with Avalonia shows how to create an application with a single composition root. + The definition of the composition is in [Composition.cs](/samples/AvaloniaApp/Composition.cs). You must not forget to define any necessary composition roots, for example, these can be view models such as _ClockViewModel_: ```csharp diff --git a/readme/FooterTemplate.md b/readme/FooterTemplate.md index d34f3596d..e11f249a1 100644 --- a/readme/FooterTemplate.md +++ b/readme/FooterTemplate.md @@ -19,8 +19,7 @@ - [https://devteam.github.io/Pure.DI/](https://devteam.github.io/Pure.DI/) - Git repo with examples - [Schrödinger's cat](https://github.com/DevTeam/Pure.DI.Example) - - [How to use Pure.DI to create libraries](https://github.com/DevTeam/Pure.DI.Solution) - + - [How to use Pure.DI to create and test libraries](https://github.com/DevTeam/Pure.DI.Solution) ## Generated Code Each generated class, hereafter called a _composition_, must be customized. Setup starts with a call to the `Setup(string compositionTypeName)` method: diff --git a/readme/check-for-a-root.md b/readme/check-for-a-root.md index 2e7e1e43d..9cbdae365 100644 --- a/readme/check-for-a-root.md +++ b/readme/check-for-a-root.md @@ -59,6 +59,8 @@ Composition.HasRoot(typeof(IComparable)).ShouldBeFalse(); ``` +For more hints, see [this](README.md#setup-hints) page. + The following partial class will be generated: ```c# diff --git a/readme/class-arguments.md b/readme/class-arguments.md index d4f9f09a4..ffd6123db 100644 --- a/readme/class-arguments.md +++ b/readme/class-arguments.md @@ -3,6 +3,9 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Basics/ClassArgumentsScenario.cs) Sometimes you need to pass some state to a composition class to use it when resolving dependencies. To do this, just use the `Arg(string argName)` method, specify the type of argument and its name. You can also specify a tag for each argument. You can then use them as dependencies when building the object graph. If you have multiple arguments of the same type, just use tags to distinguish them. The values of the arguments are manipulated when you create a composition class by calling its constructor. It is important to remember that only those arguments that are used in the object graph will appear in the constructor. Arguments that are not involved will not be added to the constructor arguments. +> [!NOTE] +> Actually, class arguments work like normal bindings. The difference is that they bind to the values of the arguments. These values will be implemented as dependencies wherever they are required. + ```c# @@ -43,7 +46,7 @@ DI.Setup(nameof(Composition)) .Bind().To() // Composition root "MyRoot" - .Root("MyRoot") + .Root("MyService") // Some kind of identifier .Arg("id") @@ -57,7 +60,7 @@ DI.Setup(nameof(Composition)) var composition = new Composition(id: 123, serviceName: "Abc", dependencyName: "Xyz"); // service = new Service("Abc", new Dependency(123, "Xyz")); -var service = composition.MyRoot; +var service = composition.MyService; service.Name.ShouldBe("Abc"); service.Dependency.Id.ShouldBe(123); @@ -91,7 +94,7 @@ partial class Composition _argDependencyName = _root._argDependencyName; } - public IService MyRoot + public IService MyService { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -108,7 +111,7 @@ Class diagram: classDiagram class Composition { <> - +IService MyRoot + +IService MyService } Dependency --|> IDependency class Dependency { @@ -126,7 +129,7 @@ classDiagram class IService { <> } - Composition ..> Service : IService MyRoot + Composition ..> Service : IService MyService Dependency o-- Int32 : Argument "id" Dependency o-- String : Argument "dependencyName" Service o-- String : "my service name" Argument "serviceName" diff --git a/readme/composition-roots.md b/readme/composition-roots.md index 7f2de0b8b..b7c33a867 100644 --- a/readme/composition-roots.md +++ b/readme/composition-roots.md @@ -2,7 +2,11 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Basics/CompositionRootsScenario.cs) -This example demonstrates several ways to create a composition root. There is no limit to the number of roots, but you should consider limiting the number of roots. Ideally, an application should have a single composition root. +This example demonstrates several ways to create a composition root. +> [!TIP] +> There is no limit to the number of roots, but you should consider limiting the number of roots. Ideally, an application should have a single composition root. + +If you use classic DI containers, the composition is created dynamically every time you call a method similar to `T Resolve()` or `object GetService(Type type)`. The root of the composition there is simply the root type of the composition of objects in memory T or Type type. There can be as many of these as you like. In the case of Pure.DI, the number of composition roots is limited because for each composition root a separate property or method is created at compile time. Therefore, each root is defined explicitly by calling the `Root(string rootName)` method. ```c# diff --git a/readme/decorator.md b/readme/decorator.md index 7816e4a43..147df19b6 100644 --- a/readme/decorator.md +++ b/readme/decorator.md @@ -2,6 +2,7 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Interception/DecoratorScenario.cs) +Interception is the ability to intercept calls between objects in order to enrich or change their behavior, but without having to change their code. A prerequisite for interception is weak binding. That is, if programming is abstraction-based, the underlying implementation can be transformed or improved by "packaging" it into other implementations of the same abstraction. At its core, intercept is an application of the Decorator design pattern. This pattern provides a flexible alternative to inheritance by dynamically "attaching" additional responsibility to an object. Decorator "packs" one implementation of an abstraction into another implementation of the same abstraction like a "matryoshka doll". _Decorator_ is a well-known and useful design pattern. It is convenient to use tagged dependencies to build a chain of nested decorators, as in the example below: diff --git a/readme/factory.md b/readme/factory.md index f2482a771..ba2c176ff 100644 --- a/readme/factory.md +++ b/readme/factory.md @@ -2,7 +2,8 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Basics/FactoryScenario.cs) -This example demonstrates how to create and initialize an instance manually. This approach is more expensive to maintain, but allows you to create objects more flexibly by passing them some state and introducing dependencies. As in the case of automatic dependency embedding, objects give up control on embedding, and the whole process takes place when the object graph is created. +This example demonstrates how to create and initialize an instance manually. +At the compilation stage, the set of dependencies that an object needs in order to be created is determined. In most cases, this happens automatically according to the set of constructors and their arguments and does not require any additional customization efforts. But sometimes it is necessary to manually create an object, as in lines of code: ```c# @@ -60,6 +61,8 @@ var service = composition.MyService; service.Dependency.IsInitialized.ShouldBeTrue(); ``` +This approach is more expensive to maintain, but allows you to create objects more flexibly by passing them some state and introducing dependencies. As in the case of automatic dependency embedding, objects give up control on embedding, and the whole process takes place when the object graph is created. + The following partial class will be generated: ```c# diff --git a/readme/func-with-arguments.md b/readme/func-with-arguments.md index 3f599d748..c1caa1b22 100644 --- a/readme/func-with-arguments.md +++ b/readme/func-with-arguments.md @@ -17,11 +17,19 @@ class Clock : IClock interface IDependency { int Id { get; } + + int SubId { get; } } -class Dependency(IClock clock, int id) : IDependency +class Dependency( + IClock clock, + int id, + [Tag("sub")] int subId) + : IDependency { public int Id => id; + + public int SubId => subId; } interface IService @@ -31,11 +39,11 @@ interface IService class Service : IService { - public Service(Func dependencyFactory) => + public Service(Func dependencyFactory) => Dependencies = [ ..Enumerable .Range(0, 10) - .Select((_, index) => dependencyFactory(index)) + .Select((_, index) => dependencyFactory(index, 99)) ]; public ImmutableArray Dependencies { get; } @@ -46,12 +54,16 @@ DI.Setup(nameof(Composition)) // Binds a dependency of type int // to the source code statement "dependencyId" .Bind().To("dependencyId") - .Bind>() - .To>(ctx => - dependencyId => + // Binds a dependency of type int with tag "sub" + // to the source code statement "subId" + .Bind("sub").To("subId") + .Bind>() + .To>(ctx => + (dependencyId, subId) => { // Builds up an instance of type Dependency - // referring the source code statement "dependencyId" + // referring source code statements "dependencyId" + // and source code statements "subId" ctx.Inject(out var dependency); return dependency; }) @@ -64,8 +76,11 @@ var composition = new Composition(); var service = composition.Root; service.Dependencies.Length.ShouldBe(10); service.Dependencies[3].Id.ShouldBe(3); +service.Dependencies[3].SubId.ShouldBe(99); ``` +Using a binding of the form `.Bind().To("some statement")` is a kind of hack that allows you to replace an injection with just its own string. + The following partial class will be generated: ```c# @@ -93,8 +108,11 @@ partial class Composition [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - Func transientFunc1 = dependencyId => + Func transientFunc1 = + [MethodImpl(MethodImplOptions.AggressiveInlining)] + (dependencyId, subId) => { + int transientInt324 = subId; int transientInt323 = dependencyId; if (_root._singletonClock39 == null) { @@ -107,7 +125,7 @@ partial class Composition } } - Dependency localDependency19 = new Dependency(_root._singletonClock39!, transientInt323); + Dependency localDependency19 = new Dependency(_root._singletonClock39!, transientInt323, transientInt324); return localDependency19; }; return new Service(transientFunc1); @@ -243,17 +261,17 @@ classDiagram + object Resolve(Type type, object? tag) } class Dependency { - +Dependency(IClock clock, Int32 id) + +Dependency(IClock clock, Int32 id, Int32 subId) } Clock --|> IClock class Clock { +Clock() } class Int32 - class FuncᐸInt32ˏIDependencyᐳ + class FuncᐸInt32ˏInt32ˏIDependencyᐳ Service --|> IService class Service { - +Service(FuncᐸInt32ˏIDependencyᐳ dependencyFactory) + +Service(FuncᐸInt32ˏInt32ˏIDependencyᐳ dependencyFactory) } class IClock { <> @@ -263,8 +281,9 @@ classDiagram } Dependency o-- "Singleton" Clock : IClock Dependency *-- Int32 : Int32 + Dependency *-- Int32 : "sub" Int32 Composition ..> Service : IService Root - FuncᐸInt32ˏIDependencyᐳ *-- Dependency : Dependency - Service *-- FuncᐸInt32ˏIDependencyᐳ : FuncᐸInt32ˏIDependencyᐳ + FuncᐸInt32ˏInt32ˏIDependencyᐳ *-- Dependency : Dependency + Service *-- FuncᐸInt32ˏInt32ˏIDependencyᐳ : FuncᐸInt32ˏInt32ˏIDependencyᐳ ``` diff --git a/readme/generics.md b/readme/generics.md index 4ddb56894..97cf6a15a 100644 --- a/readme/generics.md +++ b/readme/generics.md @@ -2,7 +2,11 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs) -Generic types are also supported, this is easy to do by binding generic types and specifying generic markers like `TT`, `TT1` etc. as generic type parameters: +Generic types are also supported. +> [!IMPORTANT] +> Instead of open generic types, as in classical DI container libraries, "marker" types are used as parameters of generalized types. Such "marker" types allow to define dependency graph more precisely. + +This is easy to do by binding generic types and specifying generic markers like `TT`, `TT1` etc. as generic type parameters: ```c# @@ -52,6 +56,38 @@ public IService Root } } ``` +Even in this simple scenario, it is not possible to precisely define the binding of an abstraction to its implementation using open generic types: +```c# +.Bind(typeof(IMap<,>)).To(typeof(Map<,>)) +``` +You can try to match them by order or by name derived from the .NET type reflection. But this is not reliable, since order and name matching is not guaranteed. For example, there is some interface with two arguments of type _key and _value_. But in its implementation the sequence of type arguments is mixed up: first comes the _value_ and then the _key_ and the names do not match: +```c# +class Map: IMap { } +``` +At the same time, the marker types `TT1` and `TT2` handle this easily. They determine the exact correspondence between the type arguments in the interface and its implementation: +```c# +.Bind>().To>() +``` +The first argument of the type in the interface, corresponds to the second argument of the type in the implementation and is a _key_. The second argument of the type in the interface, corresponds to the first argument of the type in the implementation and is a _value_. This is a simple example. Obviously, there are plenty of more complex scenarios where tokenized types will be useful. +Marker types are regular .NET types marked with a special attribute, such as: +```c# +[GenericTypeArgument] +internal abstract class TT1 { } + +[GenericTypeArgument] +internal abstract class TT2 { } +``` +This way you can easily create your own, including making them fit the constraints on the type parameter, for example: +```c# +[GenericTypeArgument] +internal struct TTS { } + +[GenericTypeArgument] +internal interface TTDisposable: IDisposable { } + +[GenericTypeArgument] +internal interface TTEnumerator: IEnumerator { } +``` The following partial class will be generated: diff --git a/readme/injections-of-abstractions.md b/readme/injections-of-abstractions.md index 733da788c..44760e299 100644 --- a/readme/injections-of-abstractions.md +++ b/readme/injections-of-abstractions.md @@ -42,6 +42,11 @@ var root = composition.Root; root.Run(); ``` +Usually the biggest block in the setup is the chain of bindings, which describes which implementation corresponds to which abstraction. This is necessary so that the code generator can build a composition of objects using only NOT abstract types. This is true because the cornerstone of DI technology implementation is the principle of abstraction-based programming rather than concrete class-based programming. Thanks to it, it is possible to replace one concrete implementation by another. And each implementation can correspond to an arbitrary number of abstractions. +> [!TIP] +>Even if the binding is not defined, there is no problem with the injection, but obviously under the condition that the consumer requests an injection NOT of abstract type. + + The following partial class will be generated: ```c# diff --git a/readme/interception.md b/readme/interception.md index cf0509dbc..0a48c6b8c 100644 --- a/readme/interception.md +++ b/readme/interception.md @@ -54,6 +54,22 @@ var service = composition.Root; service.GetMessage().ShouldBe("Hello World !!!"); ``` +Using an intercept gives you the ability to add end-to-end functionality such as: + +- Logging + +- Action logging + +- Performance monitoring + +- Security + +- Caching + +- Error handling + +- Providing resistance to failures, etc. + The following partial class will be generated: ```c# diff --git a/readme/oncannotresolve-hint.md b/readme/oncannotresolve-hint.md index 69a832a81..1aaf261a8 100644 --- a/readme/oncannotresolve-hint.md +++ b/readme/oncannotresolve-hint.md @@ -55,7 +55,7 @@ service.Dependency.ToString().ShouldBe("Dependency with name"); ``` The `OnCannotResolveContractTypeNameRegularExpression` hint helps define the set of types that require manual dependency resolution. You can use it to specify a regular expression to filter the full type name. -For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +For more hints, see [this](README.md#setup-hints) page. The following partial class will be generated: diff --git a/readme/ondependencyinjection-hint.md b/readme/ondependencyinjection-hint.md index 5723afdbe..6d5137287 100644 --- a/readme/ondependencyinjection-hint.md +++ b/readme/ondependencyinjection-hint.md @@ -54,7 +54,7 @@ log.ShouldBe(ImmutableArray.Create("Dependency injected")); ``` The `OnDependencyInjectionContractTypeNameRegularExpression` hint helps identify the set of types that require injection control. You can use it to specify a regular expression to filter the full name of a type. -For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +For more hints, see [this](README.md#setup-hints) page. The following partial class will be generated: diff --git a/readme/onnewinstance-hint.md b/readme/onnewinstance-hint.md index e93118908..13f86d280 100644 --- a/readme/onnewinstance-hint.md +++ b/readme/onnewinstance-hint.md @@ -56,7 +56,7 @@ log.ShouldBe(ImmutableArray.Create("Dependency", "Service", "Service")); ``` The `OnNewInstanceLifetimeRegularExpression` hint helps you define a set of lifetimes that require instance creation control. You can use it to specify a regular expression to filter bindings by lifetime name. -For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +For more hints, see [this](README.md#setup-hints) page. The following partial class will be generated: diff --git a/readme/property-injection.md b/readme/property-injection.md index 89bbd86f1..206f67f90 100644 --- a/readme/property-injection.md +++ b/readme/property-injection.md @@ -29,10 +29,10 @@ DI.Setup(nameof(Composition)) .Bind().To() // Composition root - .Root("Root"); + .Root("MyService"); var composition = new Composition(); -var service = composition.Root; +var service = composition.MyService; service.Dependency.ShouldBeOfType(); ``` @@ -53,7 +53,7 @@ partial class Composition _root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root; } - public IService Root + public IService MyService { [MethodImpl(MethodImplOptions.AggressiveInlining)] get @@ -72,7 +72,7 @@ Class diagram: classDiagram class Composition { <> - +IService Root + +IService MyService } Dependency --|> IDependency class Dependency { @@ -89,7 +89,7 @@ classDiagram class IService { <> } - Composition ..> Service : IService Root + Composition ..> Service : IService MyService Service *-- Dependency : IDependency ``` diff --git a/readme/resolve-hint.md b/readme/resolve-hint.md index 64f78dbc0..f16c41772 100644 --- a/readme/resolve-hint.md +++ b/readme/resolve-hint.md @@ -27,7 +27,7 @@ var service = composition.Root; var dependencyRoot = composition.DependencyRoot; ``` -For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +For more hints, see [this](README.md#setup-hints) page. The following partial class will be generated: diff --git a/readme/resolve-methods.md b/readme/resolve-methods.md index 7acd855a9..af31565b6 100644 --- a/readme/resolve-methods.md +++ b/readme/resolve-methods.md @@ -43,6 +43,14 @@ var otherService2 = composition.Resolve(typeof(IService),"My Tag"); var otherService3 = composition.OtherService; // Gets the composition through the public root ``` +_Resolve_ methods are similar to calls to the roots of a composition. Composition roots are common properties. Their use is efficient and does not cause exceptions. And that is why it is recommended to use them. In contrast, _Resolve_ methods have a number of disadvantages: + +- They provide access to an unlimited set of dependencies. + +- Their use can potentially lead to runtime exceptions, for example, when the corresponding root has not been defined. + +- Lead to performance degradation because they search for the root of a composition based on its type. + The following partial class will be generated: ```c# diff --git a/readme/root-arguments.md b/readme/root-arguments.md index 23e67f893..4d28f4c27 100644 --- a/readme/root-arguments.md +++ b/readme/root-arguments.md @@ -2,7 +2,10 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Basics/RootArgumentsScenario.cs) -Sometimes it is necessary to pass some state to the composition root to use it when resolving dependencies. To do this, just use the `RootArg(string argName)` method, specify the type of argument and its name. You can also specify a tag for each argument. You can then use them as dependencies when building the object graph. If you have multiple arguments of the same type, just use tags to distinguish them. The root of a composition that uses at least one root argument is prepended as a method, not a property. It is important to remember that the method will only display arguments that are used in the object graph of that composition root. Arguments that are not involved will not be added to the method arguments. It is best to use unique argument names so that there are no collisions. +Sometimes it is necessary to pass some state to the composition to use it when resolving dependencies. To do this, just use the `RootArg(string argName)` method, specify the type of argument and its name. You can also specify a tag for each argument. You can then use them as dependencies when building the object graph. If you have multiple arguments of the same type, just use tags to distinguish them. The root of a composition that uses at least one root argument is prepended as a method, not a property. It is important to remember that the method will only display arguments that are used in the object graph of that composition root. Arguments that are not involved will not be added to the method arguments. It is best to use unique argument names so that there are no collisions. +> [!NOTE] +> Actually, root arguments work like normal bindings. The difference is that they bind to the values of the arguments. These values will be implemented as dependencies wherever they are required. + ```c# diff --git a/readme/singleton.md b/readme/singleton.md index fe7f63d14..8250836b4 100644 --- a/readme/singleton.md +++ b/readme/singleton.md @@ -40,6 +40,20 @@ service1.Dependency1.ShouldBe(service1.Dependency2); service2.Dependency1.ShouldBe(service1.Dependency1); ``` +Some articles advise using objects with a _Singleton_ lifetime as often as possible, but the following details must be considered: + +- For .NET the default behavior is to create a new instance of the type each time it is needed, other behavior requires, additional logic that is not free and requires additional resources. + +- The use of _Singleton_, adds a requirement for thread-safety controls on their use, since singletons are more likely to share their state between different threads without even realizing it. + +- The thread-safety control should be automatically extended to all dependencies that _Singleton_ uses, since their state is also now shared. + +- Logic for thread-safety control can be resource-costly, error-prone, interlocking, and difficult to test. + +- _Singleton_ can retain dependency references longer than their expected lifetime, this is especially significant for objects that hold "non-renewable" resources, such as the operating system Handler. + +- Sometimes additional logic is required to dispose of _Singleton_. + The following partial class will be generated: ```c# diff --git a/readme/threadsafe-hint.md b/readme/threadsafe-hint.md index 3ac108501..62932f1c3 100644 --- a/readme/threadsafe-hint.md +++ b/readme/threadsafe-hint.md @@ -26,7 +26,7 @@ var composition = new Composition(); var service = composition.Root; ``` -For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +For more hints, see [this](README.md#setup-hints) page. The following partial class will be generated: diff --git a/readme/tostring-hint.md b/readme/tostring-hint.md index 4c9ecfbe8..e7949c5b7 100644 --- a/readme/tostring-hint.md +++ b/readme/tostring-hint.md @@ -24,7 +24,8 @@ var composition = new Composition(); string classDiagram = composition.ToString(); ``` -For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +Developers who start using DI technology often complain that they stop seeing the structure of the application because it is difficult to understand how it is built. To make life easier, you can add the _ToString_ hint by telling the generator to create a `ToString()` method. +For more hints, see [this](README.md#setup-hints) page. The following partial class will be generated: diff --git a/readme/transient.md b/readme/transient.md index 93505f82b..30bb8123a 100644 --- a/readme/transient.md +++ b/readme/transient.md @@ -40,6 +40,17 @@ service1.Dependency1.ShouldNotBe(service1.Dependency2); service2.Dependency1.ShouldNotBe(service1.Dependency1); ``` +The _Transient_ lifetime is the safest and is used by default. Yes, its widespread use can cause a lot of memory traffic, but if there are doubts about thread safety, the _Transient_ lifetime is preferable because each consumer has its own instance of the dependency. The following nuances should be considered when choosing the _Transient_ lifetime: + +- There will be unnecessary memory overhead that could be avoided. + +- Every object created must be disposed of, and this will waste CPU resources, at least when the GC does its memory-clearing job. + +- Poorly designed constructors can run slowly, perform functions that are not their own, and greatly hinder the efficient creation of compositions of multiple objects. + +> [!IMPORTANT] +> The following very important rule, in my opinion, will help in the last point. Now, when a constructor is used to implement dependencies, it should not be loaded with other tasks. Accordingly, constructors should be free of all logic except for checking arguments and saving them for later use. Following this rule, even the largest compositions of objects will be built quickly. + The following partial class will be generated: ```c# diff --git a/tests/Pure.DI.UsageTests/BaseClassLibrary/FuncWithArgumentsScenario.cs b/tests/Pure.DI.UsageTests/BaseClassLibrary/FuncWithArgumentsScenario.cs index b2a455972..dbb6f6330 100644 --- a/tests/Pure.DI.UsageTests/BaseClassLibrary/FuncWithArgumentsScenario.cs +++ b/tests/Pure.DI.UsageTests/BaseClassLibrary/FuncWithArgumentsScenario.cs @@ -2,6 +2,7 @@ $v=true $p=99 $d=Func with arguments +$f=Using a binding of the form `.Bind().To("some statement")` is a kind of hack that allows you to replace an injection with just its own string. */ // ReSharper disable ClassNeverInstantiated.Local @@ -32,11 +33,19 @@ class Clock : IClock interface IDependency { int Id { get; } + + int SubId { get; } } -class Dependency(IClock clock, int id) : IDependency +class Dependency( + IClock clock, + int id, + [Tag("sub")] int subId) + : IDependency { public int Id => id; + + public int SubId => subId; } interface IService @@ -46,11 +55,11 @@ interface IService class Service : IService { - public Service(Func dependencyFactory) => + public Service(Func dependencyFactory) => Dependencies = [ ..Enumerable .Range(0, 10) - .Select((_, index) => dependencyFactory(index)) + .Select((_, index) => dependencyFactory(index, 99)) ]; public ImmutableArray Dependencies { get; } @@ -68,12 +77,16 @@ public void Run() // Binds a dependency of type int // to the source code statement "dependencyId" .Bind().To("dependencyId") - .Bind>() - .To>(ctx => - dependencyId => + // Binds a dependency of type int with tag "sub" + // to the source code statement "subId" + .Bind("sub").To("subId") + .Bind>() + .To>(ctx => + (dependencyId, subId) => { // Builds up an instance of type Dependency - // referring the source code statement "dependencyId" + // referring source code statements "dependencyId" + // and source code statements "subId" ctx.Inject(out var dependency); return dependency; }) @@ -86,6 +99,7 @@ public void Run() var service = composition.Root; service.Dependencies.Length.ShouldBe(10); service.Dependencies[3].Id.ShouldBe(3); + service.Dependencies[3].SubId.ShouldBe(99); // } composition.SaveClassDiagram(); } diff --git a/tests/Pure.DI.UsageTests/Basics/ClassArgumentsScenario.cs b/tests/Pure.DI.UsageTests/Basics/ClassArgumentsScenario.cs index d150fd915..268a5c0bb 100644 --- a/tests/Pure.DI.UsageTests/Basics/ClassArgumentsScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/ClassArgumentsScenario.cs @@ -3,6 +3,9 @@ $p=5 $d=Class arguments $h=Sometimes you need to pass some state to a composition class to use it when resolving dependencies. To do this, just use the `Arg(string argName)` method, specify the type of argument and its name. You can also specify a tag for each argument. You can then use them as dependencies when building the object graph. If you have multiple arguments of the same type, just use tags to distinguish them. The values of the arguments are manipulated when you create a composition class by calling its constructor. It is important to remember that only those arguments that are used in the object graph will appear in the constructor. Arguments that are not involved will not be added to the constructor arguments. +$h=> [!NOTE] +$h=> Actually, class arguments work like normal bindings. The difference is that they bind to the values of the arguments. These values will be implemented as dependencies wherever they are required. +$h= */ // ReSharper disable ClassNeverInstantiated.Local @@ -60,7 +63,7 @@ public void Run() .Bind().To() // Composition root "MyRoot" - .Root("MyRoot") + .Root("MyService") // Some kind of identifier .Arg("id") @@ -74,7 +77,7 @@ public void Run() var composition = new Composition(id: 123, serviceName: "Abc", dependencyName: "Xyz"); // service = new Service("Abc", new Dependency(123, "Xyz")); - var service = composition.MyRoot; + var service = composition.MyService; service.Name.ShouldBe("Abc"); service.Dependency.Id.ShouldBe(123); diff --git a/tests/Pure.DI.UsageTests/Basics/CompositionRootsScenario.cs b/tests/Pure.DI.UsageTests/Basics/CompositionRootsScenario.cs index 4f3df74f1..1001d831e 100644 --- a/tests/Pure.DI.UsageTests/Basics/CompositionRootsScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/CompositionRootsScenario.cs @@ -2,7 +2,11 @@ $v=true $p=1 $d=Composition roots -$h=This example demonstrates several ways to create a composition root. There is no limit to the number of roots, but you should consider limiting the number of roots. Ideally, an application should have a single composition root. +$h=This example demonstrates several ways to create a composition root. +$h=> [!TIP] +$h=> There is no limit to the number of roots, but you should consider limiting the number of roots. Ideally, an application should have a single composition root. +$h= +$h=If you use classic DI containers, the composition is created dynamically every time you call a method similar to `T Resolve()` or `object GetService(Type type)`. The root of the composition there is simply the root type of the composition of objects in memory T or Type type. There can be as many of these as you like. In the case of Pure.DI, the number of composition roots is limited because for each composition root a separate property or method is created at compile time. Therefore, each root is defined explicitly by calling the `Root(string rootName)` method. $f=The name of the root of a composition is arbitrarily chosen depending on its purpose, but should be restricted by the property naming conventions in C# since it is the same name as a property in the composition class. In reality, the _Root_ property has the form: $f=```c# $f=public IService Root diff --git a/tests/Pure.DI.UsageTests/Basics/FactoryScenario.cs b/tests/Pure.DI.UsageTests/Basics/FactoryScenario.cs index 3b203ffc2..3511cf9aa 100644 --- a/tests/Pure.DI.UsageTests/Basics/FactoryScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/FactoryScenario.cs @@ -2,7 +2,9 @@ $v=true $p=2 $d=Factory -$h=This example demonstrates how to create and initialize an instance manually. This approach is more expensive to maintain, but allows you to create objects more flexibly by passing them some state and introducing dependencies. As in the case of automatic dependency embedding, objects give up control on embedding, and the whole process takes place when the object graph is created. +$h=This example demonstrates how to create and initialize an instance manually. +$h=At the compilation stage, the set of dependencies that an object needs in order to be created is determined. In most cases, this happens automatically according to the set of constructors and their arguments and does not require any additional customization efforts. But sometimes it is necessary to manually create an object, as in lines of code: +$f=This approach is more expensive to maintain, but allows you to create objects more flexibly by passing them some state and introducing dependencies. As in the case of automatic dependency embedding, objects give up control on embedding, and the whole process takes place when the object graph is created. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Basics/InjectionsOfAbstractionsScenario.cs b/tests/Pure.DI.UsageTests/Basics/InjectionsOfAbstractionsScenario.cs index e2aa2ac14..1b9ebca97 100644 --- a/tests/Pure.DI.UsageTests/Basics/InjectionsOfAbstractionsScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/InjectionsOfAbstractionsScenario.cs @@ -3,6 +3,10 @@ $p=0 $d=Injections of abstractions $h=This example demonstrates the recommended approach of using abstractions instead of implementations when injecting dependencies. +$f=Usually the biggest block in the setup is the chain of bindings, which describes which implementation corresponds to which abstraction. This is necessary so that the code generator can build a composition of objects using only NOT abstract types. This is true because the cornerstone of DI technology implementation is the principle of abstraction-based programming rather than concrete class-based programming. Thanks to it, it is possible to replace one concrete implementation by another. And each implementation can correspond to an arbitrary number of abstractions. +$f=> [!TIP] +$f=>Even if the binding is not defined, there is no problem with the injection, but obviously under the condition that the consumer requests an injection NOT of abstract type. +$f= */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Basics/PropertyInjectionScenario.cs b/tests/Pure.DI.UsageTests/Basics/PropertyInjectionScenario.cs index 8434aa835..58e855711 100644 --- a/tests/Pure.DI.UsageTests/Basics/PropertyInjectionScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/PropertyInjectionScenario.cs @@ -46,10 +46,10 @@ public void Run() .Bind().To() // Composition root - .Root("Root"); + .Root("MyService"); var composition = new Composition(); - var service = composition.Root; + var service = composition.MyService; service.Dependency.ShouldBeOfType(); // } composition.SaveClassDiagram(); diff --git a/tests/Pure.DI.UsageTests/Basics/ResolveMethodsScenario.cs b/tests/Pure.DI.UsageTests/Basics/ResolveMethodsScenario.cs index b0dc11787..cc97319d5 100644 --- a/tests/Pure.DI.UsageTests/Basics/ResolveMethodsScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/ResolveMethodsScenario.cs @@ -3,6 +3,13 @@ $p=1 $d=Resolve methods $h=This example shows how to resolve the composition roots using the _Resolve_ methods by _Service Locator_ approach. `Resolve` methods are generated automatically for each registered root. +$f=_Resolve_ methods are similar to calls to the roots of a composition. Composition roots are common properties. Their use is efficient and does not cause exceptions. And that is why it is recommended to use them. In contrast, _Resolve_ methods have a number of disadvantages: +$f= +$f=- They provide access to an unlimited set of dependencies. +$f= +$f=- Their use can potentially lead to runtime exceptions, for example, when the corresponding root has not been defined. +$f= +$f=- Lead to performance degradation because they search for the root of a composition based on its type. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Basics/RootArgumentsScenario.cs b/tests/Pure.DI.UsageTests/Basics/RootArgumentsScenario.cs index 4df51b06f..fc3596e6a 100644 --- a/tests/Pure.DI.UsageTests/Basics/RootArgumentsScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/RootArgumentsScenario.cs @@ -2,7 +2,10 @@ $v=true $p=5 $d=Root arguments -$h=Sometimes it is necessary to pass some state to the composition root to use it when resolving dependencies. To do this, just use the `RootArg(string argName)` method, specify the type of argument and its name. You can also specify a tag for each argument. You can then use them as dependencies when building the object graph. If you have multiple arguments of the same type, just use tags to distinguish them. The root of a composition that uses at least one root argument is prepended as a method, not a property. It is important to remember that the method will only display arguments that are used in the object graph of that composition root. Arguments that are not involved will not be added to the method arguments. It is best to use unique argument names so that there are no collisions. +$h=Sometimes it is necessary to pass some state to the composition to use it when resolving dependencies. To do this, just use the `RootArg(string argName)` method, specify the type of argument and its name. You can also specify a tag for each argument. You can then use them as dependencies when building the object graph. If you have multiple arguments of the same type, just use tags to distinguish them. The root of a composition that uses at least one root argument is prepended as a method, not a property. It is important to remember that the method will only display arguments that are used in the object graph of that composition root. Arguments that are not involved will not be added to the method arguments. It is best to use unique argument names so that there are no collisions. +$h=> [!NOTE] +$h=> Actually, root arguments work like normal bindings. The difference is that they bind to the values of the arguments. These values will be implemented as dependencies wherever they are required. +$h= $f=When using composition root arguments, compilation warnings are shown if `Resolve` methods are generated, since these methods will not be able to create these roots. You can disable the creation of `Resolve` methods using the `Hint(Hint.Resolve, "Off")` hint, or ignore them but remember the risks of using `Resolve` methods. */ diff --git a/tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs b/tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs index d674495b2..60a225ac6 100644 --- a/tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs +++ b/tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs @@ -2,7 +2,11 @@ $v=true $p=1 $d=Generics -$h=Generic types are also supported, this is easy to do by binding generic types and specifying generic markers like `TT`, `TT1` etc. as generic type parameters: +$h=Generic types are also supported. +$h=> [!IMPORTANT] +$h=> Instead of open generic types, as in classical DI container libraries, "marker" types are used as parameters of generalized types. Such "marker" types allow to define dependency graph more precisely. +$h= +$h=This is easy to do by binding generic types and specifying generic markers like `TT`, `TT1` etc. as generic type parameters: $f=Actually, the property _Root_ looks like: $f=```c# $f=public IService Root @@ -12,7 +16,39 @@ $f= return new Service(new Dependency(), new Dependency()); $f= } $f=} -$f=``` +$f=``` +$f=Even in this simple scenario, it is not possible to precisely define the binding of an abstraction to its implementation using open generic types: +$f=```c# +$f=.Bind(typeof(IMap<,>)).To(typeof(Map<,>)) +$f=``` +$f=You can try to match them by order or by name derived from the .NET type reflection. But this is not reliable, since order and name matching is not guaranteed. For example, there is some interface with two arguments of type _key and _value_. But in its implementation the sequence of type arguments is mixed up: first comes the _value_ and then the _key_ and the names do not match: +$f=```c# +$f=class Map: IMap { } +$f=``` +$f=At the same time, the marker types `TT1` and `TT2` handle this easily. They determine the exact correspondence between the type arguments in the interface and its implementation: +$f=```c# +$f=.Bind>().To>() +$f=``` +$f=The first argument of the type in the interface, corresponds to the second argument of the type in the implementation and is a _key_. The second argument of the type in the interface, corresponds to the first argument of the type in the implementation and is a _value_. This is a simple example. Obviously, there are plenty of more complex scenarios where tokenized types will be useful. +$f=Marker types are regular .NET types marked with a special attribute, such as: +$f=```c# +$f=[GenericTypeArgument] +$f=internal abstract class TT1 { } +$f= +$f=[GenericTypeArgument] +$f=internal abstract class TT2 { } +$f=``` +$f=This way you can easily create your own, including making them fit the constraints on the type parameter, for example: +$f=```c# +$f=[GenericTypeArgument] +$f=internal struct TTS { } +$f= +$f=[GenericTypeArgument] +$f=internal interface TTDisposable: IDisposable { } +$f= +$f=[GenericTypeArgument] +$f=internal interface TTEnumerator: IEnumerator { } +$f=``` */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Hints/CheckForRootScenario.cs b/tests/Pure.DI.UsageTests/Hints/CheckForRootScenario.cs index 547d21075..8ab67c3ab 100644 --- a/tests/Pure.DI.UsageTests/Hints/CheckForRootScenario.cs +++ b/tests/Pure.DI.UsageTests/Hints/CheckForRootScenario.cs @@ -3,6 +3,7 @@ $p=7 $d=Check for a root $h=Sometimes you need to check if you can get the root of a composition using the _Resolve_ method before calling it, this example will show you how to do it: +$f=For more hints, see [this](README.md#setup-hints) page. */ // ReSharper disable CheckNamespace diff --git a/tests/Pure.DI.UsageTests/Hints/OnCannotResolveHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/OnCannotResolveHintScenario.cs index ad49c178c..1b4905c1c 100644 --- a/tests/Pure.DI.UsageTests/Hints/OnCannotResolveHintScenario.cs +++ b/tests/Pure.DI.UsageTests/Hints/OnCannotResolveHintScenario.cs @@ -5,7 +5,7 @@ $h=Hints are used to fine-tune code generation. The _OnCannotResolve_ hint determines whether to generate a partial `OnCannotResolve(...)` method to handle a scenario where an instance which cannot be resolved. $h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnCannotResolveContractTypeNameRegularExpression = string`. $f=The `OnCannotResolveContractTypeNameRegularExpression` hint helps define the set of types that require manual dependency resolution. You can use it to specify a regular expression to filter the full type name. -$f=For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +$f=For more hints, see [this](README.md#setup-hints) page. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionHintScenario.cs index e1ca3b543..d76314956 100644 --- a/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionHintScenario.cs +++ b/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionHintScenario.cs @@ -5,7 +5,7 @@ $h=Hints are used to fine-tune code generation. The _OnDependencyInjection_ hint determines whether to generate partial _OnDependencyInjection_ method to control of dependency injection. $h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnDependencyInjection = On`. $f=The `OnDependencyInjectionContractTypeNameRegularExpression` hint helps identify the set of types that require injection control. You can use it to specify a regular expression to filter the full name of a type. -$f=For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +$f=For more hints, see [this](README.md#setup-hints) page. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Hints/OnNewInstanceHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/OnNewInstanceHintScenario.cs index e97f47475..5dbf322b8 100644 --- a/tests/Pure.DI.UsageTests/Hints/OnNewInstanceHintScenario.cs +++ b/tests/Pure.DI.UsageTests/Hints/OnNewInstanceHintScenario.cs @@ -5,7 +5,7 @@ $h=Hints are used to fine-tune code generation. The _OnNewInstance_ hint determines whether to generate partial _OnNewInstance_ method. $h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnNewInstance = On`. $f=The `OnNewInstanceLifetimeRegularExpression` hint helps you define a set of lifetimes that require instance creation control. You can use it to specify a regular expression to filter bindings by lifetime name. -$f=For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +$f=For more hints, see [this](README.md#setup-hints) page. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Hints/ResolveHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/ResolveHintScenario.cs index eccbb960d..79dbd9255 100644 --- a/tests/Pure.DI.UsageTests/Hints/ResolveHintScenario.cs +++ b/tests/Pure.DI.UsageTests/Hints/ResolveHintScenario.cs @@ -4,7 +4,7 @@ $d=Resolve hint $h=Hints are used to fine-tune code generation. The _Resolve_ hint determines whether to generate _Resolve_ methods. By default a set of four _Resolve_ methods are generated. Set this hint to _Off_ to disable the generation of resolve methods. This will reduce class composition generation time and no private composition roots will be generated in this case. When the _Resolve_ hint is disabled, only the public root properties are available, so be sure to define them explicitly with the `Root(...)` method. $h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// Resolve = Off`. -$f=For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +$f=For more hints, see [this](README.md#setup-hints) page. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Hints/ThreadSafeHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/ThreadSafeHintScenario.cs index f7242f2f4..7a5caf592 100644 --- a/tests/Pure.DI.UsageTests/Hints/ThreadSafeHintScenario.cs +++ b/tests/Pure.DI.UsageTests/Hints/ThreadSafeHintScenario.cs @@ -4,7 +4,7 @@ $d=ThreadSafe hint $h=Hints are used to fine-tune code generation. The _ThreadSafe_ hint determines whether object composition will be created in a thread-safe manner. This hint is _On_ by default. It is good practice not to use threads when creating an object graph, in which case this hint can be turned off, which will lead to a slight increase in performance. $h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// ThreadSafe = Off`. -$f=For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +$f=For more hints, see [this](README.md#setup-hints) page. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Hints/ToStringHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/ToStringHintScenario.cs index de5a8b50d..d26f2eea2 100644 --- a/tests/Pure.DI.UsageTests/Hints/ToStringHintScenario.cs +++ b/tests/Pure.DI.UsageTests/Hints/ToStringHintScenario.cs @@ -4,7 +4,8 @@ $d=ToString hint $h=Hints are used to fine-tune code generation. The _ToString_ hint determines if the _ToString()_ method should be generated. This method provides a text-based class diagram in the format [mermaid](https://mermaid.js.org/). To see this diagram, just call the ToString method and copy the text to [this site](https://mermaid.live/). An example class diagram can be seen below. $h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// ToString = On`. -$f=For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page. +$f=Developers who start using DI technology often complain that they stop seeing the structure of the application because it is difficult to understand how it is built. To make life easier, you can add the _ToString_ hint by telling the generator to create a `ToString()` method. +$f=For more hints, see [this](README.md#setup-hints) page. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Interception/DecoratorScenario.cs b/tests/Pure.DI.UsageTests/Interception/DecoratorScenario.cs index 8827e5d76..ed4190548 100644 --- a/tests/Pure.DI.UsageTests/Interception/DecoratorScenario.cs +++ b/tests/Pure.DI.UsageTests/Interception/DecoratorScenario.cs @@ -2,6 +2,7 @@ $v=true $p=0 $d=Decorator +$h=Interception is the ability to intercept calls between objects in order to enrich or change their behavior, but without having to change their code. A prerequisite for interception is weak binding. That is, if programming is abstraction-based, the underlying implementation can be transformed or improved by "packaging" it into other implementations of the same abstraction. At its core, intercept is an application of the Decorator design pattern. This pattern provides a flexible alternative to inheritance by dynamically "attaching" additional responsibility to an object. Decorator "packs" one implementation of an abstraction into another implementation of the same abstraction like a "matryoshka doll". $h=_Decorator_ is a well-known and useful design pattern. It is convenient to use tagged dependencies to build a chain of nested decorators, as in the example below: $f=Here an instance of the _Service_ type, labeled _"base"_, is embedded in the decorator _DecoratorService_. You can use any tag that semantically reflects the feature of the abstraction being embedded. The tag can be a constant, a type, or a value of an enumerated type. */ diff --git a/tests/Pure.DI.UsageTests/Interception/InterceptionScenario.cs b/tests/Pure.DI.UsageTests/Interception/InterceptionScenario.cs index 5e1f9cd31..dd50ba018 100644 --- a/tests/Pure.DI.UsageTests/Interception/InterceptionScenario.cs +++ b/tests/Pure.DI.UsageTests/Interception/InterceptionScenario.cs @@ -3,6 +3,21 @@ $p=1 $d=Interception $h=Interception allows you to enrich or change the behavior of a certain set of objects from the object graph being created without changing the code of the corresponding types. +$f=Using an intercept gives you the ability to add end-to-end functionality such as: +$f= +$f=- Logging +$f= +$f=- Action logging +$f= +$f=- Performance monitoring +$f= +$f=- Security +$f= +$f=- Caching +$f= +$f=- Error handling +$f= +$f=- Providing resistance to failures, etc. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Lifetimes/SingletonScenario.cs b/tests/Pure.DI.UsageTests/Lifetimes/SingletonScenario.cs index 0b8dd1d83..35f153986 100644 --- a/tests/Pure.DI.UsageTests/Lifetimes/SingletonScenario.cs +++ b/tests/Pure.DI.UsageTests/Lifetimes/SingletonScenario.cs @@ -3,6 +3,19 @@ $p=1 $d=Singleton $h=The _Singleton_ lifetime ensures that there will be a single instance of the dependency for each composition. +$f=Some articles advise using objects with a _Singleton_ lifetime as often as possible, but the following details must be considered: +$f= +$f=- For .NET the default behavior is to create a new instance of the type each time it is needed, other behavior requires, additional logic that is not free and requires additional resources. +$f= +$f=- The use of _Singleton_, adds a requirement for thread-safety controls on their use, since singletons are more likely to share their state between different threads without even realizing it. +$f= +$f=- The thread-safety control should be automatically extended to all dependencies that _Singleton_ uses, since their state is also now shared. +$f= +$f=- Logic for thread-safety control can be resource-costly, error-prone, interlocking, and difficult to test. +$f= +$f=- _Singleton_ can retain dependency references longer than their expected lifetime, this is especially significant for objects that hold "non-renewable" resources, such as the operating system Handler. +$f= +$f=- Sometimes additional logic is required to dispose of _Singleton_. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Lifetimes/TransientScenario.cs b/tests/Pure.DI.UsageTests/Lifetimes/TransientScenario.cs index d4a1ee52f..11345b25f 100644 --- a/tests/Pure.DI.UsageTests/Lifetimes/TransientScenario.cs +++ b/tests/Pure.DI.UsageTests/Lifetimes/TransientScenario.cs @@ -3,6 +3,16 @@ $p=0 $d=Transient $h=The _Transient _ lifetime specifies to create a new dependency instance each time. It is the default lifetime and can be omitted. +$f=The _Transient_ lifetime is the safest and is used by default. Yes, its widespread use can cause a lot of memory traffic, but if there are doubts about thread safety, the _Transient_ lifetime is preferable because each consumer has its own instance of the dependency. The following nuances should be considered when choosing the _Transient_ lifetime: +$f= +$f=- There will be unnecessary memory overhead that could be avoided. +$f= +$f=- Every object created must be disposed of, and this will waste CPU resources, at least when the GC does its memory-clearing job. +$f= +$f=- Poorly designed constructors can run slowly, perform functions that are not their own, and greatly hinder the efficient creation of compositions of multiple objects. +$f= +$f=> [!IMPORTANT] +$f=> The following very important rule, in my opinion, will help in the last point. Now, when a constructor is used to implement dependencies, it should not be loaded with other tasks. Accordingly, constructors should be free of all logic except for checking arguments and saving them for later use. Following this rule, even the largest compositions of objects will be built quickly. */ // ReSharper disable ClassNeverInstantiated.Local