From b36e0d20a547e972240af820b18180e5e65a2900 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Tue, 23 Jul 2024 10:10:49 +0300 Subject: [PATCH] Update README.md --- Pure.DI.sln.DotSettings | 2 + README.md | 73 +++++++++++++------ readme/FooterTemplate.md | 70 ++++++++++++------ readme/ReadmeTemplate.md | 3 + readme/tag-on-a-constructor-argument.md | 63 +++++++++++++++- .../Advanced/TagOnConstructorArgScenario.cs | 2 +- 6 files changed, 169 insertions(+), 44 deletions(-) diff --git a/Pure.DI.sln.DotSettings b/Pure.DI.sln.DotSettings index 72627da10..8b3a5cd54 100644 --- a/Pure.DI.sln.DotSettings +++ b/Pure.DI.sln.DotSettings @@ -273,6 +273,7 @@ True True True + True True True True @@ -289,6 +290,7 @@ True True True + True True True True diff --git a/README.md b/README.md index 80cd73973..541cd0683 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,9 @@ DI.Setup(nameof(Composition)) .Root("Root"); ``` +> [!NOTE] +> In fact, the `Bind().As(Singleton).To()` binding is unnecessary since _Pure.DI_ supports many .NET BCL types out of the box, including [Random](https://github.com/DevTeam/Pure.DI/blob/27a1ccd604b2fdd55f6bfec01c24c86428ddfdcb/src/Pure.DI.Core/Features/Default.g.cs#L289). It was added just for the example of using the _Singleton_ lifetime. + The above code specifies the generation of a partial class named *__Composition__*, this name is defined in the `DI.Setup(nameof(Composition))` call. This class contains a *__Root__* property that returns a graph of objects with an object of type *__Program__* as the root. The type and name of the property is defined by calling `Root("Root")`. The code of the generated class looks as follows: ```c# @@ -387,10 +390,10 @@ If you specify this value, the class will not be generated, but this setup can b ```c# DI.Setup("BaseComposition", CompositionKind.Internal) - .Bind().To(); + .Bind().To(); DI.Setup("Composition").DependsOn("BaseComposition") - .Bind().To(); + .Bind().To(); ``` If the _CompositionKind.Public_ flag is set in the composition setup, it can also be the base for other compositions, as in the example above. @@ -425,10 +428,7 @@ In this case, the constructor with arguments is as follows: public Composition(string name, int id) { ... } ``` -and there is no default constructor. - -> [!IMPORTANT] -> 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 cannot be defined, as they are omitted from the constructor parameters to save resources. +and there is no default 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 cannot be defined, as they are omitted from the constructor parameters to save resources. ### Scope constructor @@ -441,7 +441,7 @@ This constructor creates a composition instance for the new scope. This allows ` ### Public Composition Roots -To create an object graph quickly and conveniently, a set of properties (or a methods) is formed. These properties are here called roots of compositions. The type of a property/method is the type of the root object created by the composition. Accordingly, each invocation of a property/method leads to the creation of a composition with a root element of this type. +To create an object graph quickly and conveniently, a set of properties (or a methods) is formed. These properties/methods are here called roots of compositions. The type of a property/method is the type of the root object created by the composition. Accordingly, each invocation of a property/method leads to the creation of a composition with a root element of this type. ```c# DI.Setup("Composition") @@ -512,16 +512,29 @@ var composition = new Composition(); composition.Resolve(); ``` -This is a [not recommended](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) way to create composition roots. To control the generation of these methods, see the [Resolve](#resolve-hint) hint. +This is a [not recommended](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) way to create composition roots because _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. -### Dispose +To control the generation of these methods, see the [Resolve](#resolve-hint) hint. -Provides a mechanism to release unmanaged resources. This method is generated only if the composition contains at least one singleton instance that implements the [IDisposable](https://learn.microsoft.com/en-us/dotnet/api/system.idisposable) interface. To dispose of all created singleton objects, the `Dispose()` method of the composition should be called: +### Dispose and DisposeAsync + +Provides a mechanism to release unmanaged resources. These methods are generated only if the composition contains at least one singleton/scoped instance that implements either the [IDisposable](https://learn.microsoft.com/en-us/dotnet/api/system.idisposable) and/or [DisposeAsync](https://learn.microsoft.com/en-us/dotnet/api/system.iasyncdisposable.disposeasync) interface. The `Dispose()` or `DisposeAsync()` method of the composition should be called to dispose of all created singleton/scoped objects: ```c# using var composition = new Composition(); ``` +or + +```c# +await using var composition = new Composition(); +``` + +To dispose objects of other lifetimes please see [this](readme/tracking-disposable-instances-per-a-composition-root.md) or [this](readme/tracking-disposable-instances-in-delegates.md) examples. +
@@ -545,11 +558,17 @@ In addition, setup hints can be commented out before the _Setup_ method as `hint // Resolve = Off // ThreadSafe = Off DI.Setup("Composition") - .Hint(Hint.ToString, "On") ... ``` -Both approaches can be used in combination with each other. +Both approaches can be mixed: + +```c# +// Resolve = Off +DI.Setup("Composition") + .Hint(Hint.ThreadSafe, "Off") + ... +``` | Hint | Values | C# version | Default | |------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|------------|-----------| @@ -601,10 +620,8 @@ Determines whether to use the _OnNewInstance_ partial method. By default, this p ```c# internal partial class Composition { - partial void OnNewInstance(ref T value, object? tag, object lifetime) - { - Console.WriteLine($"'{typeof(T)}'('{tag}') created."); - } + partial void OnNewInstance(ref T value, object? tag, object lifetime) => + Console.WriteLine($"'{typeof(T)}'('{tag}') created."); } ``` @@ -952,17 +969,31 @@ If you are using the Rider IDE, it already has a set of configurations to run th > > This tool helps to make .NET builds more efficient. -### Contribution Prerequisites +Contribution Prerequisites: Installed [.NET SDK 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) -### Additional resources: - -DotNext video +### Additional resources - RU DotNext video +> +> DotNext Pure.DI +> [C# interactive](https://github.com/DevTeam/csharp-interactive) build automation system for .NET + +Examples of how to set up a composition +- [Pure.DI](https://github.com/DevTeam/Pure.DI/blob/master/src/Pure.DI.Core/Generator.Composition.cs) +- [C# interactive](https://github.com/DevTeam/csharp-interactive/blob/master/CSharpInteractive/Composition.cs) +- [Immutype](https://github.com/DevTeam/Immutype/blob/master/Immutype/Composition.cs) +- [MSBuild logger](https://github.com/JetBrains/teamcity-msbuild-logger/blob/master/TeamCity.MSBuild.Logger/Composition.cs) + +Articles +- [RU New in Pure.DI](https://habr.com/ru/articles/808297/) +- [RU Pure.DI v2.1](https://habr.com/ru/articles/795809/) +- [RU Pure.DI next step](https://habr.com/ru/articles/554236/) +- [RU Pure.DI for .NET](https://habr.com/ru/articles/552858/) + ## Benchmarks diff --git a/readme/FooterTemplate.md b/readme/FooterTemplate.md index c16410853..6ef33969a 100644 --- a/readme/FooterTemplate.md +++ b/readme/FooterTemplate.md @@ -96,10 +96,10 @@ If you specify this value, the class will not be generated, but this setup can b ```c# DI.Setup("BaseComposition", CompositionKind.Internal) - .Bind().To(); + .Bind().To(); DI.Setup("Composition").DependsOn("BaseComposition") - .Bind().To(); + .Bind().To(); ``` If the _CompositionKind.Public_ flag is set in the composition setup, it can also be the base for other compositions, as in the example above. @@ -134,10 +134,7 @@ In this case, the constructor with arguments is as follows: public Composition(string name, int id) { ... } ``` -and there is no default constructor. - -> [!IMPORTANT] -> 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 cannot be defined, as they are omitted from the constructor parameters to save resources. +and there is no default 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 cannot be defined, as they are omitted from the constructor parameters to save resources. ### Scope constructor @@ -150,7 +147,7 @@ This constructor creates a composition instance for the new scope. This allows ` ### Public Composition Roots -To create an object graph quickly and conveniently, a set of properties (or a methods) is formed. These properties are here called roots of compositions. The type of a property/method is the type of the root object created by the composition. Accordingly, each invocation of a property/method leads to the creation of a composition with a root element of this type. +To create an object graph quickly and conveniently, a set of properties (or a methods) is formed. These properties/methods are here called roots of compositions. The type of a property/method is the type of the root object created by the composition. Accordingly, each invocation of a property/method leads to the creation of a composition with a root element of this type. ```c# DI.Setup("Composition") @@ -221,16 +218,29 @@ var composition = new Composition(); composition.Resolve(); ``` -This is a [not recommended](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) way to create composition roots. To control the generation of these methods, see the [Resolve](#resolve-hint) hint. +This is a [not recommended](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) way to create composition roots because _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. + +To control the generation of these methods, see the [Resolve](#resolve-hint) hint. -### Dispose +### Dispose and DisposeAsync -Provides a mechanism to release unmanaged resources. This method is generated only if the composition contains at least one singleton instance that implements the [IDisposable](https://learn.microsoft.com/en-us/dotnet/api/system.idisposable) interface. To dispose of all created singleton objects, the `Dispose()` method of the composition should be called: +Provides a mechanism to release unmanaged resources. These methods are generated only if the composition contains at least one singleton/scoped instance that implements either the [IDisposable](https://learn.microsoft.com/en-us/dotnet/api/system.idisposable) and/or [DisposeAsync](https://learn.microsoft.com/en-us/dotnet/api/system.iasyncdisposable.disposeasync) interface. The `Dispose()` or `DisposeAsync()` method of the composition should be called to dispose of all created singleton/scoped objects: ```c# using var composition = new Composition(); ``` +or + +```c# +await using var composition = new Composition(); +``` + +To dispose objects of other lifetimes please see [this](readme/tracking-disposable-instances-per-a-composition-root.md) or [this](readme/tracking-disposable-instances-in-delegates.md) examples. +
@@ -254,11 +264,17 @@ In addition, setup hints can be commented out before the _Setup_ method as `hint // Resolve = Off // ThreadSafe = Off DI.Setup("Composition") - .Hint(Hint.ToString, "On") ... ``` -Both approaches can be used in combination with each other. +Both approaches can be mixed: + +```c# +// Resolve = Off +DI.Setup("Composition") + .Hint(Hint.ThreadSafe, "Off") + ... +``` | Hint | Values | C# version | Default | |------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|------------|-----------| @@ -310,10 +326,8 @@ Determines whether to use the _OnNewInstance_ partial method. By default, this p ```c# internal partial class Composition { - partial void OnNewInstance(ref T value, object? tag, object lifetime) - { - Console.WriteLine($"'{typeof(T)}'('{tag}') created."); - } + partial void OnNewInstance(ref T value, object? tag, object lifetime) => + Console.WriteLine($"'{typeof(T)}'('{tag}') created."); } ``` @@ -661,13 +675,27 @@ If you are using the Rider IDE, it already has a set of configurations to run th > > This tool helps to make .NET builds more efficient. -### Contribution Prerequisites +Contribution Prerequisites: Installed [.NET SDK 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) -### Additional resources: - -DotNext video +### Additional resources - RU DotNext video +> +> DotNext Pure.DI + +> [C# interactive](https://github.com/DevTeam/csharp-interactive) build automation system for .NET + +Examples of how to set up a composition +- [Pure.DI](https://github.com/DevTeam/Pure.DI/blob/master/src/Pure.DI.Core/Generator.Composition.cs) +- [C# interactive](https://github.com/DevTeam/csharp-interactive/blob/master/CSharpInteractive/Composition.cs) +- [Immutype](https://github.com/DevTeam/Immutype/blob/master/Immutype/Composition.cs) +- [MSBuild logger](https://github.com/JetBrains/teamcity-msbuild-logger/blob/master/TeamCity.MSBuild.Logger/Composition.cs) + +Articles +- [RU New in Pure.DI](https://habr.com/ru/articles/808297/) +- [RU Pure.DI v2.1](https://habr.com/ru/articles/795809/) +- [RU Pure.DI next step](https://habr.com/ru/articles/554236/) +- [RU Pure.DI for .NET](https://habr.com/ru/articles/552858/) diff --git a/readme/ReadmeTemplate.md b/readme/ReadmeTemplate.md index 1003526a8..8719684fe 100644 --- a/readme/ReadmeTemplate.md +++ b/readme/ReadmeTemplate.md @@ -67,6 +67,9 @@ DI.Setup(nameof(Composition)) .Root("Root"); ``` +> [!NOTE] +> In fact, the `Bind().As(Singleton).To()` binding is unnecessary since _Pure.DI_ supports many .NET BCL types out of the box, including [Random](https://github.com/DevTeam/Pure.DI/blob/27a1ccd604b2fdd55f6bfec01c24c86428ddfdcb/src/Pure.DI.Core/Features/Default.g.cs#L289). It was added just for the example of using the _Singleton_ lifetime. + The above code specifies the generation of a partial class named *__Composition__*, this name is defined in the `DI.Setup(nameof(Composition))` call. This class contains a *__Root__* property that returns a graph of objects with an object of type *__Program__* as the root. The type and name of the property is defined by calling `Root("Root")`. The code of the generated class looks as follows: ```c# diff --git a/readme/tag-on-a-constructor-argument.md b/readme/tag-on-a-constructor-argument.md index 4ac9cbd21..94e60c0a5 100644 --- a/readme/tag-on-a-constructor-argument.md +++ b/readme/tag-on-a-constructor-argument.md @@ -6,7 +6,7 @@ The wildcards ‘*’ and ‘?’ are supported. ```c# -namespace Pure.DI.UsageTests.Advanced.OnConstructorArgScenario; +namespace Pure.DI.UsageTests.Advanced.TagOnConstructorArgScenario; interface IDependency; @@ -56,5 +56,66 @@ service.Dependency2.ShouldBeOfType(); > [!WARNING] > Each potentially injectable argument, property, or field contains an additional tag. This tag can be used to specify what can be injected there. This will only work if the binding type and the tag match. So while this approach can be useful for specifying what to enter, it can be more expensive to maintain and less reliable, so it is recommended to use attributes like `[Tag(...)]` instead. +The following partial class will be generated: +```c# +partial class Composition +{ + private readonly Composition _root; + + public Composition() + { + _root = this; + } + + internal Composition(Composition parentScope) + { + _root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root; + } + + public IService Root + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return new Service(new AbcDependency(), new Consumer(new XyzDependency())); + } + } +} +``` + +Class diagram: + +```mermaid +classDiagram + class Composition { + <> + +IService Root + } + class ConsumerᐸStringᐳ { + +Consumer(IDependency myDep) + } + AbcDependency --|> IDependency + class AbcDependency { + +AbcDependency() + } + XyzDependency --|> IDependency + class XyzDependency { + +XyzDependency() + } + Service --|> IService + class Service { + +Service(IDependency dependency1, ConsumerᐸStringᐳ consumer) + } + class IDependency { + <> + } + class IService { + <> + } + ConsumerᐸStringᐳ *-- XyzDependency : IDependency + Composition ..> Service : IService Root + Service *-- AbcDependency : IDependency + Service *-- ConsumerᐸStringᐳ : ConsumerᐸStringᐳ +``` diff --git a/tests/Pure.DI.UsageTests/Advanced/TagOnConstructorArgScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TagOnConstructorArgScenario.cs index a0dc660a1..4aaef322c 100644 --- a/tests/Pure.DI.UsageTests/Advanced/TagOnConstructorArgScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/TagOnConstructorArgScenario.cs @@ -16,7 +16,7 @@ // ReSharper disable UnusedTypeParameter #pragma warning disable CS9113 // Parameter is unread. // { -namespace Pure.DI.UsageTests.Advanced.OnConstructorArgScenario; +namespace Pure.DI.UsageTests.Advanced.TagOnConstructorArgScenario; // }