From 27a5b35d31e2eb31dd21d860b79506f147689333 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sat, 8 Jun 2024 14:38:05 +0300 Subject: [PATCH] A special tag that allows injection by name --- README.md | 1 + readme/ArrayDetails.md | 20 +- readme/EnumDetails.md | 20 +- readme/FuncDetails.md | 16 +- readme/SingletonDetails.md | 14 +- readme/TransientDetails.md | 14 +- readme/auto-scoped.md | 4 +- readme/factory.md | 4 +- ...sync-composition-roots-with-constraints.md | 8 +- ...eric-composition-roots-with-constraints.md | 8 +- readme/generic-composition-roots.md | 8 +- readme/generics.md | 4 +- readme/injections-of-abstractions.md | 2 +- readme/manually-started-tasks.md | 4 +- readme/name-tags.md | 111 +++++ src/Pure.DI.Core/Core/BindingBuilder.cs | 10 +- .../Core/Code/ClassDiagramBuilder.cs | 4 +- src/Pure.DI.Core/Core/Code/CodeExtensions.cs | 1 + .../ImplementationDependencyNodeBuilder.cs | 42 +- src/Pure.DI.Core/Core/Models/Injection.cs | 25 +- src/Pure.DI.Core/Core/Models/MdTag.cs | 29 ++ tests/Pure.DI.IntegrationTests/SetupTests.cs | 106 ----- tests/Pure.DI.IntegrationTests/TagsTests.cs | 431 ++++++++++++++++++ tests/Pure.DI.Tests/InjectionTests.cs | 54 +++ tests/Pure.DI.Tests/Pure.DI.Tests.csproj | 6 +- .../Advanced/NameTagsScenario.cs | 101 ++++ .../Advanced/SeveralPartialClassesScenario.cs | 2 +- .../BaseClassLibrary/ManualTaskScenario.cs | 4 +- .../Basics/FactoryScenario.cs | 4 +- .../InjectionsOfAbstractionsScenario.cs | 2 +- ...CompositionRootsWithConstraintsScenario.cs | 8 +- ...CompositionRootsWithConstraintsScenario.cs | 8 +- .../GenericsCompositionRootsScenario.cs | 8 +- .../Generics/GenericsScenario.cs | 4 +- .../Lifetimes/AutoScopedScenario.cs | 4 +- 35 files changed, 875 insertions(+), 216 deletions(-) create mode 100644 readme/name-tags.md create mode 100644 tests/Pure.DI.Tests/InjectionTests.cs create mode 100644 tests/Pure.DI.UsageTests/Advanced/NameTagsScenario.cs diff --git a/README.md b/README.md index 6999fde47..8fd69d7ea 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,7 @@ dotnet run - [Composition root kinds](readme/composition-root-kinds.md) - [Tag Type](readme/tag-type.md) - [Tag Unique](readme/tag-unique.md) +- [Name tags](readme/name-tags.md) - [A few partial classes](readme/a-few-partial-classes.md) - [Partial class](readme/partial-class.md) - [Dependent compositions](readme/dependent-compositions.md) diff --git a/readme/ArrayDetails.md b/readme/ArrayDetails.md index 17a6dff01..8e17a8d5b 100644 --- a/readme/ArrayDetails.md +++ b/readme/ArrayDetails.md @@ -57,17 +57,17 @@ classDiagram class IService4 { <> } - CompositionRoot *-- Service1 : IService1 - CompositionRoot *-- "3 " Service2Array : IService2 - CompositionRoot *-- Service3 : IService3 - CompositionRoot *-- "2 " Service4 : IService4 + CompositionRoot *-- Service1 : IService1 + CompositionRoot *-- "3 " Service2Array : IService2 + CompositionRoot *-- Service3 : IService3 + CompositionRoot *-- "2 " Service4 : IService4 Array ..> CompositionRoot : CompositionRoot TestPureDIByCR() - Service1 *-- Service2Array : IService2 - Service2Array *-- ArrayᐸIService3ᐳ : ArrayᐸIService3ᐳ - Service3 *-- "2 " Service4 : IService4 - Service3v2 *-- "2 " Service4 : IService4 - Service3v3 *-- "2 " Service4 : IService4 - Service3v4 *-- "2 " Service4 : IService4 + Service1 *-- Service2Array : IService2 + Service2Array *-- ArrayᐸIService3ᐳ : ArrayᐸIService3ᐳ + Service3 *-- "2 " Service4 : IService4 + Service3v2 *-- "2 " Service4 : IService4 + Service3v3 *-- "2 " Service4 : IService4 + Service3v4 *-- "2 " Service4 : IService4 ArrayᐸIService3ᐳ *-- Service3 : IService3 ArrayᐸIService3ᐳ *-- Service3v2 : 2 IService3 ArrayᐸIService3ᐳ *-- Service3v3 : 3 IService3 diff --git a/readme/EnumDetails.md b/readme/EnumDetails.md index fc6d1573a..5b6e9b939 100644 --- a/readme/EnumDetails.md +++ b/readme/EnumDetails.md @@ -57,17 +57,17 @@ classDiagram class IService4 { <> } - CompositionRoot *-- Service1 : IService1 - CompositionRoot *-- "3 " Service2Enum : IService2 - CompositionRoot *-- Service3 : IService3 - CompositionRoot *-- "2 " Service4 : IService4 + CompositionRoot *-- Service1 : IService1 + CompositionRoot *-- "3 " Service2Enum : IService2 + CompositionRoot *-- Service3 : IService3 + CompositionRoot *-- "2 " Service4 : IService4 Enum ..> CompositionRoot : CompositionRoot TestPureDIByCR() - Service1 *-- Service2Enum : IService2 - Service2Enum o-- "PerBlock" IEnumerableᐸIService3ᐳ : IEnumerableᐸIService3ᐳ - Service3 *-- "2 " Service4 : IService4 - Service3v2 *-- "2 " Service4 : IService4 - Service3v3 *-- "2 " Service4 : IService4 - Service3v4 *-- "2 " Service4 : IService4 + Service1 *-- Service2Enum : IService2 + Service2Enum o-- "PerBlock" IEnumerableᐸIService3ᐳ : IEnumerableᐸIService3ᐳ + Service3 *-- "2 " Service4 : IService4 + Service3v2 *-- "2 " Service4 : IService4 + Service3v3 *-- "2 " Service4 : IService4 + Service3v4 *-- "2 " Service4 : IService4 IEnumerableᐸIService3ᐳ *-- Service3 : IService3 IEnumerableᐸIService3ᐳ *-- Service3v2 : 2 IService3 IEnumerableᐸIService3ᐳ *-- Service3v3 : 3 IService3 diff --git a/readme/FuncDetails.md b/readme/FuncDetails.md index f3671355c..d9ba7c133 100644 --- a/readme/FuncDetails.md +++ b/readme/FuncDetails.md @@ -45,15 +45,15 @@ classDiagram class IService4 { <> } - CompositionRoot *-- Service1 : IService1 - CompositionRoot *-- "3 " Service2Func : IService2 - CompositionRoot *-- Service3 : IService3 - CompositionRoot *-- "2 " Service4 : IService4 + CompositionRoot *-- Service1 : IService1 + CompositionRoot *-- "3 " Service2Func : IService2 + CompositionRoot *-- Service3 : IService3 + CompositionRoot *-- "2 " Service4 : IService4 Func ..> CompositionRoot : CompositionRoot TestPureDIByCR() - Service1 *-- Service2Func : IService2 - Service2Func o-- "PerBlock" FuncᐸIService3ᐳ : FuncᐸIService3ᐳ - Service3 *-- "2 " Service4 : IService4 - FuncᐸIService3ᐳ *-- Service3 : IService3 + Service1 *-- Service2Func : IService2 + Service2Func o-- "PerBlock" FuncᐸIService3ᐳ : FuncᐸIService3ᐳ + Service3 *-- "2 " Service4 : IService4 + FuncᐸIService3ᐳ *-- Service3 : IService3 ``` ### Generated code diff --git a/readme/SingletonDetails.md b/readme/SingletonDetails.md index 941c3e13e..edcb663f7 100644 --- a/readme/SingletonDetails.md +++ b/readme/SingletonDetails.md @@ -44,14 +44,14 @@ classDiagram class IService4 { <> } - CompositionRoot o-- "Scoped" Service1 : IService1 - CompositionRoot *-- "3 " Service2 : IService2 - CompositionRoot *-- Service3 : IService3 - CompositionRoot o-- "2 Scoped" Service4 : IService4 + CompositionRoot o-- "Scoped" Service1 : IService1 + CompositionRoot *-- "3 " Service2 : IService2 + CompositionRoot *-- Service3 : IService3 + CompositionRoot o-- "2 Scoped" Service4 : IService4 Singleton ..> CompositionRoot : CompositionRoot TestPureDIByCR() - Service1 *-- Service2 : IService2 - Service2 *-- "5 " Service3 : IService3 - Service3 o-- "2 Scoped" Service4 : IService4 + Service1 *-- Service2 : IService2 + Service2 *-- "5 " Service3 : IService3 + Service3 o-- "2 Scoped" Service4 : IService4 ``` ### Generated code diff --git a/readme/TransientDetails.md b/readme/TransientDetails.md index 981184d8f..7bb967010 100644 --- a/readme/TransientDetails.md +++ b/readme/TransientDetails.md @@ -44,14 +44,14 @@ classDiagram class IService4 { <> } - CompositionRoot *-- Service1 : IService1 - CompositionRoot *-- "3 " Service2 : IService2 - CompositionRoot *-- Service3 : IService3 - CompositionRoot *-- "2 " Service4 : IService4 + CompositionRoot *-- Service1 : IService1 + CompositionRoot *-- "3 " Service2 : IService2 + CompositionRoot *-- Service3 : IService3 + CompositionRoot *-- "2 " Service4 : IService4 Transient ..> CompositionRoot : CompositionRoot TestPureDIByCR() - Service1 *-- Service2 : IService2 - Service2 *-- "5 " Service3 : IService3 - Service3 *-- "2 " Service4 : IService4 + Service1 *-- Service2 : IService2 + Service2 *-- "5 " Service3 : IService3 + Service3 *-- "2 " Service4 : IService4 ``` ### Generated code diff --git a/readme/auto-scoped.md b/readme/auto-scoped.md index 30124f2cd..4092914e9 100644 --- a/readme/auto-scoped.md +++ b/readme/auto-scoped.md @@ -64,8 +64,8 @@ var service2 = program.CreateService(); service1.Dependency.ShouldNotBe(service2.Dependency); ``` ->[!IMPORTANT] ->The method `Inject()`cannot be used outside of the binding setup. +> [!IMPORTANT] +> The method `Inject()`cannot be used outside of the binding setup. The following partial class will be generated: diff --git a/readme/factory.md b/readme/factory.md index 6a4c1fb30..98b910cdc 100644 --- a/readme/factory.md +++ b/readme/factory.md @@ -62,8 +62,8 @@ 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 injecting, objects give up control on embedding, and the whole process takes place when the object graph is created. ->[!IMPORTANT] ->The method `Inject()`cannot be used outside of the binding setup. +> [!IMPORTANT] +> The method `Inject()`cannot be used outside of the binding setup. The following partial class will be generated: diff --git a/readme/generic-async-composition-roots-with-constraints.md b/readme/generic-async-composition-roots-with-constraints.md index b34b2bb83..79526d9e4 100644 --- a/readme/generic-async-composition-roots-with-constraints.md +++ b/readme/generic-async-composition-roots-with-constraints.md @@ -2,8 +2,8 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Generics/GenericAsyncCompositionRootsWithConstraintsScenario.cs) ->[!IMPORTANT] ->`Resolve' methods cannot be used to resolve generic composition roots. +> [!IMPORTANT] +> `Resolve' methods cannot be used to resolve generic composition roots. ```c# @@ -59,8 +59,8 @@ var service = await composition.GetMyRootAsync(CancellationToken var someOtherService = await composition.GetOtherServiceAsync(CancellationToken.None); ``` ->[!IMPORTANT] ->The method `Inject()`cannot be used outside of the binding setup. +> [!IMPORTANT] +> The method `Inject()`cannot be used outside of the binding setup. The following partial class will be generated: diff --git a/readme/generic-composition-roots-with-constraints.md b/readme/generic-composition-roots-with-constraints.md index 051c1c490..93f0bf458 100644 --- a/readme/generic-composition-roots-with-constraints.md +++ b/readme/generic-composition-roots-with-constraints.md @@ -2,8 +2,8 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Generics/GenericCompositionRootsWithConstraintsScenario.cs) ->[!IMPORTANT] ->`Resolve' methods cannot be used to resolve generic composition roots. +> [!IMPORTANT] +> `Resolve' methods cannot be used to resolve generic composition roots. ```c# @@ -57,8 +57,8 @@ var service = composition.GetMyRoot(); var someOtherService = composition.GetOtherService(); ``` ->[!IMPORTANT] ->The method `Inject()`cannot be used outside of the binding setup. +> [!IMPORTANT] +> The method `Inject()`cannot be used outside of the binding setup. The following partial class will be generated: diff --git a/readme/generic-composition-roots.md b/readme/generic-composition-roots.md index 9e8494515..bad849259 100644 --- a/readme/generic-composition-roots.md +++ b/readme/generic-composition-roots.md @@ -3,8 +3,8 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs) Sometimes you want to be able to create composition roots with type parameters. In this case, the composition root can only be represented by a method. ->[!IMPORTANT] ->`Resolve()' methods cannot be used to resolve generic composition roots. +> [!IMPORTANT] +> `Resolve()' methods cannot be used to resolve generic composition roots. ```c# @@ -51,8 +51,8 @@ var service = composition.GetMyRoot(); var someOtherService = composition.GetOtherService(); ``` ->[!IMPORTANT] ->The method `Inject()`cannot be used outside of the binding setup. +> [!IMPORTANT] +> The method `Inject()`cannot be used outside of the binding setup. The following partial class will be generated: diff --git a/readme/generics.md b/readme/generics.md index 9d80a1c94..2c88346c4 100644 --- a/readme/generics.md +++ b/readme/generics.md @@ -3,8 +3,8 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs) Generic types are also supported. ->[!IMPORTANT] ->Instead of open generic types, as in classical DI container libraries, regular generic types with _marker_ types as type parameters are used here. Such "marker" types allow to define dependency graph more precisely. +> [!IMPORTANT] +> Instead of open generic types, as in classical DI container libraries, regular generic types with _marker_ types as type parameters are used here. Such "marker" types allow to define dependency graph more precisely. For the case of `IDependency`, `TT` is a _marker_ type, which allows the usual `IDependency` to be used instead of an open generic type like `IDependency<>`. This makes it easy to bind generic types by specifying _marker_ types such as `TT`, `TT1`, etc. as parameters of generic types: diff --git a/readme/injections-of-abstractions.md b/readme/injections-of-abstractions.md index 44760e299..612992874 100644 --- a/readme/injections-of-abstractions.md +++ b/readme/injections-of-abstractions.md @@ -44,7 +44,7 @@ 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. +> 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: diff --git a/readme/manually-started-tasks.md b/readme/manually-started-tasks.md index 897fa0e70..56deb8755 100644 --- a/readme/manually-started-tasks.md +++ b/readme/manually-started-tasks.md @@ -67,8 +67,8 @@ var service = composition.GetRoot(cancellationTokenSource.Token); await service.RunAsync(cancellationTokenSource.Token); ``` ->[!IMPORTANT] ->The method `Inject()`cannot be used outside of the binding setup. +> [!IMPORTANT] +> The method `Inject()`cannot be used outside of the binding setup. The following partial class will be generated: diff --git a/readme/name-tags.md b/readme/name-tags.md new file mode 100644 index 000000000..98b047fbd --- /dev/null +++ b/readme/name-tags.md @@ -0,0 +1,111 @@ +#### Name tags + +[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/NameTagsScenario.cs) + +Name tag are ordinary string tag in the special format: the full name of the type member and the name of the target element to be injected, separated by ":". For example for the _MyNamespace_ namespace and the type _Class1_: + +- `MyNamespace.Class1.Class1:state1 - the tag corresponds to the constructor argument named _state_ of type _MyNamespace.Class1_ +- `MyNamespace.Class1.DoSomething:myArg` - the tag corresponds to the _myArg_ argument of the _DoSomething_ method +- `MyNamespace.Class1.MyData` - the tag corresponds to property or field _MyData_ + +All names are case-sensitive. The global namespace prefix `global::` must be omitted. + +>For generic types, the type name also contains the number of type parameters, for example, for `IDictionary` the type name would be ``IDictionary`2``. For example, ``MyNamespace.Consumer`1.Consumer:myDep`` in this code: + + +```c# +interface IDependency; + +class AbcDependency : IDependency; + +class XyzDependency : IDependency; + +class Consumer(IDependency myDep) +{ + public IDependency Dependency { get; } = myDep; +} + +interface IService +{ + IDependency Dependency1 { get; } + + IDependency Dependency2 { get; } + + IDependency Dependency3 { get; } + + IDependency Dependency4 { get; } +} + +class Service( + IDependency dependency1, + IDependency dependency2, + Consumer consumer) + : IService +{ + public IDependency Dependency1 { get; } = dependency1; + + public IDependency Dependency2 { get; } = dependency2; + + public required IDependency Dependency3 { init; get; } + + public IDependency Dependency4 => consumer.Dependency; +} + +DI.Setup(nameof(Composition)) + .Bind( + "MyNamespace.Service.Service:dependency1", + "MyNamespace.Consumer`1.Consumer:myDep") + .To() + .Bind("MyNamespace.Service.Service:dependency2", + "MyNamespace.Service:Dependency3") + .To() + .Bind().To>() + .Bind().To() + + // Specifies to create the composition root named "Root" + .Root("Root"); + +var composition = new Composition(); +var service = composition.Root; +service.Dependency1.ShouldBeOfType(); +service.Dependency2.ShouldBeOfType(); +service.Dependency3.ShouldBeOfType(); +service.Dependency4.ShouldBeOfType(); +``` + +> [!WARNING] +> Although this approach can be useful for specifying exactly what to inject, it can be more expensive to maintain, so it is recommended to use attributes like `[Tag(...)]`. + + +Class diagram: + +```mermaid +classDiagram + class Composition { + <> + +IService Root + } + AbcDependency --|> IDependency : "Pure.DI.UsageTests.Basics.NameTagsScenario.Service.Service:dependency1" + AbcDependency --|> IDependency + class AbcDependency { + +AbcDependency() + } + XyzDependency --|> IDependency : "Pure.DI.UsageTests.Basics.NameTagsScenario.Service.Service:dependency2" + class XyzDependency { + +XyzDependency() + } + Service --|> IService + class Service { + +Service(IDependency dependency1, IDependency dependency2, IDependency dependency3) + } + class IDependency { + <> + } + class IService { + <> + } + Composition ..> Service : IService Root + Service *-- "2 " AbcDependency : IDependency + Service o-- "Singleton" XyzDependency : IDependency +``` + diff --git a/src/Pure.DI.Core/Core/BindingBuilder.cs b/src/Pure.DI.Core/Core/BindingBuilder.cs index 6fb1870ae..ed173caca 100644 --- a/src/Pure.DI.Core/Core/BindingBuilder.cs +++ b/src/Pure.DI.Core/Core/BindingBuilder.cs @@ -128,8 +128,8 @@ 1 when source, setup, semanticModel, - _contracts.Select(i => i with { Tags = i.Tags.Select(tag => BuildTag(tag, type, id)).ToImmutableArray()}).ToImmutableArray(), - _tags.Select(tag => BuildTag(tag, type, id)).ToImmutableArray(), + _contracts.Select(i => i with { Tags = i.Tags.Select(tag => BuildTag(tag, type, id, i.Tags.ToList())).ToImmutableArray()}).ToImmutableArray(), + _tags.Select(tag => BuildTag(tag, type, id, _tags)).ToImmutableArray(), _lifetime ?? _defaultLifetime?.Lifetime, _implementation, _factory, @@ -151,7 +151,7 @@ 1 when } } - private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy id) + private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy id, IReadOnlyCollection tags) { if (type is null || tag.Value is null) { @@ -164,10 +164,10 @@ private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy id) switch (tagVal) { case Tag.Type: - return tag with { Value = type }; + return MdTag.CreateTypeTag(tag, type); case Tag.Unique: - return tag with { Value = new UniqueTag(id.Value) }; + return MdTag.CreateUniqueTag(tag, id.Value); } } diff --git a/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs b/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs index c4720f086..d39b56dee 100644 --- a/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs @@ -199,10 +199,10 @@ private static string FormatCardinality(int count, Lifetime lifetime) } private string FormatDependency(Dependency dependency, FormatOptions options) => - $"{(dependency.Injection.Tag == default ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}"; + $"{(dependency.Injection.Tag is null or MdTag.NameTagObject ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}"; private static string FormatTag(object? tag) => - tag != default ? $"{tag.ValueToString("").Replace("\"", "\\\"")} " : ""; + tag is null or MdTag.NameTagObject ? "" : $"{tag.ValueToString("").Replace("\"", "\\\"")} "; private static string FormatTags(IEnumerable tags) => string.Join(", ", tags.Distinct().Select(FormatTag).OrderBy(i => i)); diff --git a/src/Pure.DI.Core/Core/Code/CodeExtensions.cs b/src/Pure.DI.Core/Core/Code/CodeExtensions.cs index e966842b8..6082cdb72 100644 --- a/src/Pure.DI.Core/Core/Code/CodeExtensions.cs +++ b/src/Pure.DI.Core/Core/Code/CodeExtensions.cs @@ -22,6 +22,7 @@ public static string ValueToString(this object? tag, string defaultValue = "null char ch => $"'{ch.ToString()}'", Enum en => $"{en.GetType()}.{en}", ITypeSymbol => $"typeof({tag})", + MdTag.NameTagObject => defaultValue, not null => tag.ToString(), _ => defaultValue }; diff --git a/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs b/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs index 0414c440b..03d3ee583 100644 --- a/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs +++ b/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs @@ -4,13 +4,41 @@ // ReSharper disable ClassNeverInstantiated.Global namespace Pure.DI.Core; -using ITypeSymbol = Microsoft.CodeAnalysis.ITypeSymbol; +using System.Reflection; internal sealed class ImplementationDependencyNodeBuilder( ILogger logger, IBuilder> implementationVariantsBuilder) : IBuilder> { + private static readonly SymbolDisplayFormat NameTagQualifiedFormat; + + static ImplementationDependencyNodeBuilder() + { + NameTagQualifiedFormat = new SymbolDisplayFormat( + genericsOptions: + SymbolDisplayGenericsOptions.IncludeTypeParameters, + typeQualificationStyle: + SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, + memberOptions: + SymbolDisplayMemberOptions.IncludeType | + SymbolDisplayMemberOptions.IncludeContainingType, + miscellaneousOptions: + SymbolDisplayMiscellaneousOptions.UseSpecialTypes + | SymbolDisplayMiscellaneousOptions.CollapseTupleTypes + ); + + var qualifiedNameArityFormat = NameTagQualifiedFormat.GetType().GetField("QualifiedNameArityFormat", BindingFlags.Static | BindingFlags.NonPublic); + var format = (SymbolDisplayFormat?)qualifiedNameArityFormat?.GetValue(null) + ?? new SymbolDisplayFormat(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces); + + NameTagQualifiedFormat = + format + .WithGenericsOptions(SymbolDisplayGenericsOptions.IncludeTypeParameters) + .WithMemberOptions(SymbolDisplayMemberOptions.IncludeType | SymbolDisplayMemberOptions.IncludeContainingType) + .WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.CollapseTupleTypes); + } + public IEnumerable Build(MdSetup setup) { var injectionsWalker = new DependenciesToInjectionsCountWalker(); @@ -109,7 +137,7 @@ public IEnumerable Build(MdSetup setup) ordinal, new Injection( GetAttribute(setup.TypeAttributes, field, setup.TypeConstructor?.Construct(compilation, type) ?? type), - GetAttribute(setup.TagAttributes, field, default(object?))))); + GetAttribute(setup.TagAttributes, field, default(object?)) ?? CreateNameTag(field)))); } } @@ -128,7 +156,7 @@ public IEnumerable Build(MdSetup setup) ordinal, new Injection( GetAttribute(setup.TypeAttributes, property, setup.TypeConstructor?.Construct(compilation, type) ?? type), - GetAttribute(setup.TagAttributes, property, default(object?))))); + GetAttribute(setup.TagAttributes, property, default(object?)) ?? CreateNameTag(property)))); } } @@ -222,11 +250,17 @@ private ImmutableArray GetParameters( parameter, new Injection( GetAttribute(setup.TypeAttributes, parameter, typeConstructor?.Construct(compilation, type) ?? type), - GetAttribute(setup.TagAttributes, parameter, default(object?))))); + GetAttribute(setup.TagAttributes, parameter, default(object?)) ?? CreateNameTag(parameter)))); } return dependenciesBuilder.MoveToImmutable(); } + + private static object CreateNameTag(ISymbol symbol) + { + var memberName = symbol.ContainingSymbol.ToDisplayString(NameTagQualifiedFormat); + return MdTag.CreateNameTagValue($"{memberName}:{symbol.Name}"); + } private T GetAttribute( in ImmutableArray attributeMetadata, diff --git a/src/Pure.DI.Core/Core/Models/Injection.cs b/src/Pure.DI.Core/Core/Models/Injection.cs index 9e039c02b..345b23689 100644 --- a/src/Pure.DI.Core/Core/Models/Injection.cs +++ b/src/Pure.DI.Core/Core/Models/Injection.cs @@ -1,26 +1,27 @@ namespace Pure.DI.Core.Models; +using System.Runtime.CompilerServices; using Code; internal readonly record struct Injection( ITypeSymbol Type, object? Tag) { - public override string ToString() => $"{Type}{(Tag != default ? $"({Tag.ValueToString()})" : "")}"; + public override string ToString() => $"{Type}{(Tag != default && Tag is not MdTag.NameTagObject ? $"({Tag.ValueToString()})" : "")}"; - public bool Equals(Injection other) => - SymbolEqualityComparer.Default.Equals(Type, other.Type) && EqualTags(Tag, other.Tag); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Injection other) => + (ReferenceEquals(Type, other.Type) || SymbolEqualityComparer.Default.Equals(Type, other.Type)) + && EqualTags(Tag, other.Tag); public override int GetHashCode() => SymbolEqualityComparer.Default.GetHashCode(Type); - public static bool EqualTags(object? tag, object? otherTag) - { - if (ReferenceEquals(tag, MdTag.ContextTag)) - { - return true; - } - - return ReferenceEquals(otherTag, MdTag.ContextTag) || Equals(tag, otherTag); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualTags(object? tag, object? otherTag) => + ReferenceEquals(tag, MdTag.ContextTag) + || ReferenceEquals(otherTag, MdTag.ContextTag) + || (tag is MdTag.NameTagObject nameTagObject && nameTagObject.Equals(otherTag)) + || (otherTag is MdTag.NameTagObject otherNameTagObject && otherNameTagObject.Equals(tag)) + || Equals(tag, otherTag); } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/Models/MdTag.cs b/src/Pure.DI.Core/Core/Models/MdTag.cs index d6d6f6212..83b8b24a5 100644 --- a/src/Pure.DI.Core/Core/Models/MdTag.cs +++ b/src/Pure.DI.Core/Core/Models/MdTag.cs @@ -7,11 +7,21 @@ internal readonly record struct MdTag( { public static readonly object ContextTag = new ContextTagObject(); + public static MdTag CreateTypeTag(MdTag tag, ITypeSymbol? type) => + tag with { Value = type }; + + public static MdTag CreateUniqueTag(MdTag tag, int id) => + tag with { Value = new UniqueTag(id) }; + + public static object CreateNameTagValue(string name) => + new NameTagObject(name); + public override string ToString() => Value switch { null => "null", string => $"\"{Value}\"", + NameTagObject => "", _ => Value.ToString() }; @@ -19,4 +29,23 @@ private class ContextTagObject { public override string ToString() => "ContextTag"; } + + internal class NameTagObject(string name) + { + public override string ToString() => $"NameTag(\"{name}\")"; + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return true; + if (ReferenceEquals(this, obj)) return true; + return obj switch + { + string str => Equals(name, str), + NameTagObject nameTag => true, + _ => false + }; + } + + public override int GetHashCode() => name.GetHashCode(); + } } \ No newline at end of file diff --git a/tests/Pure.DI.IntegrationTests/SetupTests.cs b/tests/Pure.DI.IntegrationTests/SetupTests.cs index 34b98da42..205098008 100644 --- a/tests/Pure.DI.IntegrationTests/SetupTests.cs +++ b/tests/Pure.DI.IntegrationTests/SetupTests.cs @@ -1602,112 +1602,6 @@ public static void Main() // Then result.Success.ShouldBeTrue(result); } - - [Fact] - public async Task ShouldSupportTagUnique() - { - // Given - - // When - var result = await """ -namespace Sample -{ - using System; - using System.Linq; - using System.Collections.Generic; - using Pure.DI; - using Sample; - - internal interface IDep { } - - internal class Dep1: IDep { } - - internal class Dep2: IDep { } - - internal interface IService { } - - internal class Service: IService - { - public Service(IEnumerable deps) - { - Console.WriteLine(deps.Count()); - } - } - - internal partial class Composition - { - void Setup() => - DI.Setup("Composition") - .Bind(Tag.Unique).To() - .Bind(Tag.Unique).To() - .Bind().To() - .Root("Root"); - } - - public class Program - { - public static void Main() - { - var composition = new Composition(); - Console.WriteLine(composition.Root); - } - } -} -""".RunAsync(); - - // Then - result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["2", "Sample.Service"], result); - } - - [Fact] - public async Task ShouldSupportTagType() - { - // Given - - // When - var result = await """ -namespace Sample -{ - using System; - using Pure.DI; - using Sample; - - internal class Dep { } - - internal interface IService { } - - internal class Service: IService - { - public Service([Tag(typeof(Dep))] Dep dep) { } - } - - internal partial class Composition - { - void Setup() => - DI.Setup("Composition") - .Bind(Tag.Type).To() - .Bind(Tag.Type).To() - .Root("Root1", typeof(Service)) - .Root("Root2", typeof(Service)); - } - - public class Program - { - public static void Main() - { - var composition = new Composition(); - Console.WriteLine(composition.Root1); - Console.WriteLine(composition.Root2); - } - } -} -""".RunAsync(); - - // Then - result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["Sample.Service", "Sample.Service"], result); - } #if ROSLYN4_8_OR_GREATER [Fact] diff --git a/tests/Pure.DI.IntegrationTests/TagsTests.cs b/tests/Pure.DI.IntegrationTests/TagsTests.cs index ba648865b..9bf2409bd 100644 --- a/tests/Pure.DI.IntegrationTests/TagsTests.cs +++ b/tests/Pure.DI.IntegrationTests/TagsTests.cs @@ -387,4 +387,435 @@ public static void Main() result.StdOut.ShouldBe(["1", "2", "2"], result); } #endif + + [Fact] + public async Task ShouldSupportTagUnique() + { + // Given + + // When + var result = await """ +namespace Sample +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Pure.DI; + using Sample; + + internal interface IDep { } + + internal class Dep1: IDep { } + + internal class Dep2: IDep { } + + internal interface IService { } + + internal class Service: IService + { + public Service(IEnumerable deps) + { + Console.WriteLine(deps.Count()); + } + } + + internal partial class Composition + { + void Setup() => + DI.Setup("Composition") + .Bind(Tag.Unique).To() + .Bind(Tag.Unique).To() + .Bind().To() + .Root("Root"); + } + + public class Program + { + public static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Root); + } + } +} +""".RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["2", "Sample.Service"], result); + } + + [Fact] + public async Task ShouldSupportTagType() + { + // Given + + // When + var result = await """ +namespace Sample +{ + using System; + using Pure.DI; + using Sample; + + internal class Dep { } + + internal interface IService { } + + internal class Service: IService + { + public Service([Tag(typeof(Dep))] Dep dep) { } + } + + internal partial class Composition + { + void Setup() => + DI.Setup("Composition") + .Bind(Tag.Type).To() + .Bind(Tag.Type).To() + .Root("Root1", typeof(Service)) + .Root("Root2", typeof(Service)); + } + + public class Program + { + public static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Root1); + Console.WriteLine(composition.Root2); + } + } +} +""".RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Sample.Service", "Sample.Service"], result); + } + + [Fact] + public async Task ShouldSupportTagNameWhenCtor() + { + // Given + + // When + var result = await """ +namespace Sample +{ + using System; + using Pure.DI; + using Sample; + + internal class Dep { } + + internal interface IService { } + + internal class Service: IService + { + public Service(Dep dep) { } + } + + internal partial class Composition + { + void Setup() => + DI.Setup("Composition") + .Bind("Sample.Service.Service:dep").To() + .Bind().To() + .Root("Root"); + } + + public class Program + { + public static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Root); + } + } +} +""".RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Sample.Service"], result); + } + + [Fact] + public async Task ShouldSupportTagNameWhenInterfaceInCtor() + { + // Given + + // When + var result = await """ +namespace Sample +{ + using System; + using Pure.DI; + using Sample; + + internal interface IDep { } + + internal class Dep: IDep { } + + internal interface IService { } + + internal class Service: IService + { + public Service(IDep dep) { } + } + + internal partial class Composition + { + void Setup() => + DI.Setup("Composition") + .Bind("Sample.Service.Service:dep").To() + .Bind().To() + .Root("Root"); + } + + public class Program + { + public static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Root); + } + } +} +""".RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Sample.Service"], result); + } + + [Fact] + public async Task ShouldSupportTagNameWhenGenericInCtor() + { + // Given + + // When + var result = await """ +namespace Sample +{ + using System; + using Pure.DI; + using Sample; + + internal interface IAbc { } + + internal interface IDep { } + + internal class Dep: IDep { } + + internal interface IService { } + + internal class Service: IService + { + public Service(IDep dep) { } + } + + internal partial class Composition + { + void Setup() => + DI.Setup("Composition") + .Hint(Hint.Resolve, "Off") + .Bind("Sample.Service`1.Service:dep").To() + .Bind().To>() + .Root>("Root"); + } + + public class Program + { + public static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Root()); + } + } +} +""".RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Sample.Service`1[System.Int32]"], result); + } + +#if ROSLYN4_8_OR_GREATER + [Fact] + public async Task ShouldSupportSeveralTagNames() + { + // Given + + // When + var result = await """ +namespace Sample +{ + using System; + using Pure.DI; + using Sample; + + internal interface IDep { } + + internal class Dep: IDep { } + + internal interface IService { } + + internal class Service: IService + { + public Service(Dep dep, IDep abc) { } + private IDep? _myProp; + + public required IDep MyProp + { + init + { + _myProp = value; + Console.WriteLine(value); + } + + get + { + return _myProp!; + } + } + } + + internal partial class Composition + { + void Setup() => + DI.Setup("Composition") + .Bind("Sample.Service.Service:dep", "Sample.Service.Service:abc", "Sample.Service:MyProp").To() + .Bind().To() + .Root("Root"); + } + + public class Program + { + public static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Root); + } + } +} +""".RunAsync(new Options(LanguageVersion.CSharp11)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Sample.Dep", "Sample.Service"], result); + } + + [Fact] + public async Task ShouldSupportTagNameWhenRequiredProperty() + { + // Given + + // When + var result = await """ +namespace Sample +{ + using System; + using Pure.DI; + using Sample; + + internal interface IDep { } + + internal class Dep: IDep { } + + internal interface IService { } + + internal class Service: IService + { + private IDep? _myProp; + + public required IDep MyProp + { + init + { + _myProp = value; + } + + get + { + return _myProp!; + } + } + } + + internal partial class Composition + { + void Setup() => + DI.Setup("Composition") + .Bind("Sample.Service:MyProp").To() + .Bind().To() + .Root("Root"); + } + + public class Program + { + public static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Root); + Console.WriteLine(composition.Root.MyProp); + } + } +} +""".RunAsync(new Options(LanguageVersion.CSharp11)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Sample.Service", "Sample.Dep"], result); + } + + [Fact] + public async Task ShouldSupportTagNameWhenRequiredField() + { + // Given + + // When + var result = await """ +namespace Sample +{ + using System; + using Pure.DI; + using Sample; + + internal class Dep { } + + internal interface IService { } + + internal class Service: IService + { + public required Dep? MyField; + } + + internal partial class Composition + { + void Setup() => + DI.Setup("Composition") + .Bind("Sample.Service:MyField").To() + .Bind().To() + .Root("Root"); + } + + public class Program + { + public static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Root); + Console.WriteLine(composition.Root.MyField); + } + } +} +""".RunAsync(new Options(LanguageVersion.CSharp11)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Sample.Service", "Sample.Dep"], result); + } +#endif } \ No newline at end of file diff --git a/tests/Pure.DI.Tests/InjectionTests.cs b/tests/Pure.DI.Tests/InjectionTests.cs new file mode 100644 index 000000000..953ae9e9e --- /dev/null +++ b/tests/Pure.DI.Tests/InjectionTests.cs @@ -0,0 +1,54 @@ +namespace Pure.DI.Tests; + +using Core.Models; +using Microsoft.CodeAnalysis; +using Moq; + +public class InjectionTests +{ + private static readonly ITypeSymbol TypeSymbol1 = Mock.Of(); + private static readonly ITypeSymbol TypeSymbol2 = Mock.Of(); + + [Theory] + [MemberData(nameof(GetData))] + internal void ShouldImplementEqualityMethods(int id, Injection injection1, Injection injection2, bool expectedEqual) + { + // Given + // ReSharper disable once RedundantAssignment + id++; + + // When + var hashCode1 = injection1.GetHashCode(); + var hashCode2 = injection2.GetHashCode(); + var actualEqual = Equals(injection1, injection2); + + // Then + actualEqual.ShouldBe(expectedEqual); + if (expectedEqual) + { + hashCode1.ShouldBe(hashCode2); + } + } + + public static IEnumerable GetData() + { + yield return [0, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, null), true]; + yield return [1, new Injection(TypeSymbol1, 1), new Injection(TypeSymbol1, 1), true]; + yield return [2, new Injection(TypeSymbol1, "Abc"), new Injection(TypeSymbol1, "Abc"), true]; + yield return [3, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, MdTag.ContextTag), true]; + yield return [4, new Injection(TypeSymbol1, "Abc"), new Injection(TypeSymbol1, MdTag.ContextTag), true]; + yield return [5, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, new MdTag.NameTagObject("Abc")), true]; + yield return [6, new Injection(TypeSymbol1, new MdTag.NameTagObject("Abc")), new Injection(TypeSymbol1, new MdTag.NameTagObject("Abc")), true]; + yield return [7, new Injection(TypeSymbol1, "Abc"), new Injection(TypeSymbol1, new MdTag.NameTagObject("Abc")), true]; + yield return [8, new Injection(TypeSymbol1, new MdTag.NameTagObject("Abc")), new Injection(TypeSymbol1, MdTag.ContextTag), true]; + yield return [14, new Injection(TypeSymbol1, new MdTag.NameTagObject("Abc")), new Injection(TypeSymbol1, new MdTag.NameTagObject("Xyz")), true]; + + yield return [9, new Injection(TypeSymbol1, null), new Injection(TypeSymbol2, null), false]; + yield return [10, new Injection(TypeSymbol1, null), new Injection(TypeSymbol2, 1), false]; + yield return [11, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, 1), false]; + yield return [12, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, "Abc"), false]; + yield return [13, new Injection(TypeSymbol1, new MdTag.NameTagObject("Abc")), new Injection(TypeSymbol2, new MdTag.NameTagObject("Abc")), false]; + yield return [15, new Injection(TypeSymbol1, new MdTag.NameTagObject("Abc")), new Injection(TypeSymbol2, MdTag.ContextTag), false]; + yield return [16, new Injection(TypeSymbol1, "Xyz"), new Injection(TypeSymbol1, new MdTag.NameTagObject("Abc")), false]; + } +} \ No newline at end of file diff --git a/tests/Pure.DI.Tests/Pure.DI.Tests.csproj b/tests/Pure.DI.Tests/Pure.DI.Tests.csproj index 9cd0125d8..843f5f5ae 100644 --- a/tests/Pure.DI.Tests/Pure.DI.Tests.csproj +++ b/tests/Pure.DI.Tests/Pure.DI.Tests.csproj @@ -2,9 +2,11 @@ - + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Pure.DI.UsageTests/Advanced/NameTagsScenario.cs b/tests/Pure.DI.UsageTests/Advanced/NameTagsScenario.cs new file mode 100644 index 000000000..7e52e08e0 --- /dev/null +++ b/tests/Pure.DI.UsageTests/Advanced/NameTagsScenario.cs @@ -0,0 +1,101 @@ +/* +$v=true +$p=5 +$d=Name tags +$h=Name tag are ordinary string tag in the special format: the full name of the type member and the name of the target element to be injected, separated by ":". For example for the _MyNamespace_ namespace and the type _Class1_: +$h= +$h=- `MyNamespace.Class1.Class1:state1 - the tag corresponds to the constructor argument named _state_ of type _MyNamespace.Class1_ +$h=- `MyNamespace.Class1.DoSomething:myArg` - the tag corresponds to the _myArg_ argument of the _DoSomething_ method +$h=- `MyNamespace.Class1.MyData` - the tag corresponds to property or field _MyData_ +$h= +$h=All names are case-sensitive. The global namespace prefix `global::` must be omitted. +$h= +$h=>For generic types, the type name also contains the number of type parameters, for example, for `IDictionary` the type name would be ``IDictionary`2``. For example, ``MyNamespace.Consumer`1.Consumer:myDep`` in this code: +$f=> [!WARNING] +$f=> Although this approach can be useful for specifying exactly what to inject, it can be more expensive to maintain, so it is recommended to use attributes like `[Tag(...)]`. +*/ + +// ReSharper disable ClassNeverInstantiated.Local +// ReSharper disable CheckNamespace +// ReSharper disable UnusedType.Global +// ReSharper disable ArrangeTypeModifiers +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable UnusedTypeParameter +#pragma warning disable CS9113 // Parameter is unread. +namespace MyNamespace; + +using Pure.DI; +using Pure.DI.UsageTests; +using Xunit; + +// { +interface IDependency; + +class AbcDependency : IDependency; + +class XyzDependency : IDependency; + +class Consumer(IDependency myDep) +{ + public IDependency Dependency { get; } = myDep; +} + +interface IService +{ + IDependency Dependency1 { get; } + + IDependency Dependency2 { get; } + + IDependency Dependency3 { get; } + + IDependency Dependency4 { get; } +} + +class Service( + IDependency dependency1, + IDependency dependency2, + Consumer consumer) + : IService +{ + public IDependency Dependency1 { get; } = dependency1; + + public IDependency Dependency2 { get; } = dependency2; + + public required IDependency Dependency3 { init; get; } + + public IDependency Dependency4 => consumer.Dependency; +} +// } + +public class Scenario +{ + [Fact] + public void Run() + { + // Resolve = Off +// { + DI.Setup(nameof(Composition)) + .Bind( + "MyNamespace.Service.Service:dependency1", + "MyNamespace.Consumer`1.Consumer:myDep") + .To() + .Bind("MyNamespace.Service.Service:dependency2", + "MyNamespace.Service:Dependency3") + .To() + .Bind().To>() + .Bind().To() + + // Specifies to create the composition root named "Root" + .Root("Root"); + + var composition = new Composition(); + var service = composition.Root; + service.Dependency1.ShouldBeOfType(); + service.Dependency2.ShouldBeOfType(); + service.Dependency3.ShouldBeOfType(); + service.Dependency4.ShouldBeOfType(); +// } + composition.SaveClassDiagram(); + } +} \ No newline at end of file diff --git a/tests/Pure.DI.UsageTests/Advanced/SeveralPartialClassesScenario.cs b/tests/Pure.DI.UsageTests/Advanced/SeveralPartialClassesScenario.cs index 4e8722c76..119e58d48 100644 --- a/tests/Pure.DI.UsageTests/Advanced/SeveralPartialClassesScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/SeveralPartialClassesScenario.cs @@ -1,6 +1,6 @@ /* $v=true -$p=5 +$p=6 $d=A few partial classes $h=The setting code for one Composition can be located in several methods and/or in several partial classes. */ diff --git a/tests/Pure.DI.UsageTests/BaseClassLibrary/ManualTaskScenario.cs b/tests/Pure.DI.UsageTests/BaseClassLibrary/ManualTaskScenario.cs index 00438edcb..64d6fe4ab 100644 --- a/tests/Pure.DI.UsageTests/BaseClassLibrary/ManualTaskScenario.cs +++ b/tests/Pure.DI.UsageTests/BaseClassLibrary/ManualTaskScenario.cs @@ -3,8 +3,8 @@ $p=4 $d=Manually started tasks $h=By default, tasks are started automatically when they are injected. But you can override this behavior as shown in the example below. It is also recommended to add a binding for CancellationToken to be able to cancel the execution of a task. -$f=>[!IMPORTANT] -$f=>The method `Inject()`cannot be used outside of the binding setup. +$f=> [!IMPORTANT] +$f=> The method `Inject()`cannot be used outside of the binding setup. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Basics/FactoryScenario.cs b/tests/Pure.DI.UsageTests/Basics/FactoryScenario.cs index 165075955..15f1e0b3b 100644 --- a/tests/Pure.DI.UsageTests/Basics/FactoryScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/FactoryScenario.cs @@ -5,8 +5,8 @@ $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 injecting, objects give up control on embedding, and the whole process takes place when the object graph is created. -$f=>[!IMPORTANT] -$f=>The method `Inject()`cannot be used outside of the binding setup. +$f=> [!IMPORTANT] +$f=> The method `Inject()`cannot be used outside of the binding setup. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Basics/InjectionsOfAbstractionsScenario.cs b/tests/Pure.DI.UsageTests/Basics/InjectionsOfAbstractionsScenario.cs index 1b9ebca97..fa55e580c 100644 --- a/tests/Pure.DI.UsageTests/Basics/InjectionsOfAbstractionsScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/InjectionsOfAbstractionsScenario.cs @@ -5,7 +5,7 @@ $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=> 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= */ diff --git a/tests/Pure.DI.UsageTests/Generics/GenericAsyncCompositionRootsWithConstraintsScenario.cs b/tests/Pure.DI.UsageTests/Generics/GenericAsyncCompositionRootsWithConstraintsScenario.cs index cb6d2a141..03d700120 100644 --- a/tests/Pure.DI.UsageTests/Generics/GenericAsyncCompositionRootsWithConstraintsScenario.cs +++ b/tests/Pure.DI.UsageTests/Generics/GenericAsyncCompositionRootsWithConstraintsScenario.cs @@ -2,10 +2,10 @@ $v=true $p=5 $d=Generic async composition roots with constraints -$h=>[!IMPORTANT] -$h=>`Resolve' methods cannot be used to resolve generic composition roots. -$f=>[!IMPORTANT] -$f=>The method `Inject()`cannot be used outside of the binding setup. +$h=> [!IMPORTANT] +$h=> `Resolve' methods cannot be used to resolve generic composition roots. +$f=> [!IMPORTANT] +$f=> The method `Inject()`cannot be used outside of the binding setup. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Generics/GenericCompositionRootsWithConstraintsScenario.cs b/tests/Pure.DI.UsageTests/Generics/GenericCompositionRootsWithConstraintsScenario.cs index d821f6b0d..affffd966 100644 --- a/tests/Pure.DI.UsageTests/Generics/GenericCompositionRootsWithConstraintsScenario.cs +++ b/tests/Pure.DI.UsageTests/Generics/GenericCompositionRootsWithConstraintsScenario.cs @@ -2,10 +2,10 @@ $v=true $p=4 $d=Generic composition roots with constraints -$h=>[!IMPORTANT] -$h=>`Resolve' methods cannot be used to resolve generic composition roots. -$f=>[!IMPORTANT] -$f=>The method `Inject()`cannot be used outside of the binding setup. +$h=> [!IMPORTANT] +$h=> `Resolve' methods cannot be used to resolve generic composition roots. +$f=> [!IMPORTANT] +$f=> The method `Inject()`cannot be used outside of the binding setup. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs b/tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs index 016164e2b..3e0e6841b 100644 --- a/tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs +++ b/tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs @@ -3,10 +3,10 @@ $p=2 $d=Generic composition roots $h=Sometimes you want to be able to create composition roots with type parameters. In this case, the composition root can only be represented by a method. -$h=>[!IMPORTANT] -$h=>`Resolve()' methods cannot be used to resolve generic composition roots. -$f=>[!IMPORTANT] -$f=>The method `Inject()`cannot be used outside of the binding setup. +$h=> [!IMPORTANT] +$h=> `Resolve()' methods cannot be used to resolve generic composition roots. +$f=> [!IMPORTANT] +$f=> The method `Inject()`cannot be used outside of the binding setup. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs b/tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs index 15de26824..4f4929de2 100644 --- a/tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs +++ b/tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs @@ -3,8 +3,8 @@ $p=1 $d=Generics $h=Generic types are also supported. -$h=>[!IMPORTANT] -$h=>Instead of open generic types, as in classical DI container libraries, regular generic types with _marker_ types as type parameters are used here. Such "marker" types allow to define dependency graph more precisely. +$h=> [!IMPORTANT] +$h=> Instead of open generic types, as in classical DI container libraries, regular generic types with _marker_ types as type parameters are used here. Such "marker" types allow to define dependency graph more precisely. $h= $h=For the case of `IDependency`, `TT` is a _marker_ type, which allows the usual `IDependency` to be used instead of an open generic type like `IDependency<>`. This makes it easy to bind generic types by specifying _marker_ types such as `TT`, `TT1`, etc. as parameters of generic types: $f=Actually, the property _Root_ looks like: diff --git a/tests/Pure.DI.UsageTests/Lifetimes/AutoScopedScenario.cs b/tests/Pure.DI.UsageTests/Lifetimes/AutoScopedScenario.cs index 58c73380f..e82696d6f 100644 --- a/tests/Pure.DI.UsageTests/Lifetimes/AutoScopedScenario.cs +++ b/tests/Pure.DI.UsageTests/Lifetimes/AutoScopedScenario.cs @@ -3,8 +3,8 @@ $p=5 $d=Auto scoped $h=You can use the following example to automatically create a session when creating instances of a particular type: -$f=>[!IMPORTANT] -$f=>The method `Inject()`cannot be used outside of the binding setup. +$f=> [!IMPORTANT] +$f=> The method `Inject()`cannot be used outside of the binding setup. */ // ReSharper disable ClassNeverInstantiated.Local