From e0239a4db0b54121123379041603545ee1188d3d Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Thu, 16 May 2024 11:56:17 +0300 Subject: [PATCH] The Bind method without parameters should only add bindings to first-level abstractions for the type --- ...eric-composition-roots-with-constraints.md | 2 ++ readme/generic-composition-roots.md | 4 ++-- readme/resolve-methods.md | 8 +++---- readme/root-binding.md | 2 +- readme/simplified-binding.md | 6 +---- src/Pure.DI.Core/Core/BaseSymbolsProvider.cs | 24 +++++++++++++++---- src/Pure.DI.Core/Core/BindingBuilder.cs | 13 ++++++++-- src/Pure.DI.Core/Core/Code/BuildTools.cs | 3 +-- src/Pure.DI.Core/Core/IBaseSymbolsProvider.cs | 5 +++- src/Pure.DI.Core/Core/MetadataValidator.cs | 2 +- .../Basics/ResolveMethodsScenario.cs | 6 ++--- .../Basics/RootBindScenario.cs | 2 +- .../Basics/SimplifiedBindingScenario.cs | 3 ++- ...CompositionRootsWithConstraintsScenario.cs | 1 + .../GenericsCompositionRootsScenario.cs | 4 ++-- 15 files changed, 55 insertions(+), 30 deletions(-) diff --git a/readme/generic-composition-roots-with-constraints.md b/readme/generic-composition-roots-with-constraints.md index d80862255..4b63e4be7 100644 --- a/readme/generic-composition-roots-with-constraints.md +++ b/readme/generic-composition-roots-with-constraints.md @@ -54,6 +54,8 @@ var service = composition.GetMyRoot(); var someOtherService = composition.GetOtherService(); ``` +:warning: `Resolve' methods cannot be used to resolve generic composition roots. + The following partial class will be generated: ```c# diff --git a/readme/generic-composition-roots.md b/readme/generic-composition-roots.md index eef42d486..504a11a8d 100644 --- a/readme/generic-composition-roots.md +++ b/readme/generic-composition-roots.md @@ -2,7 +2,7 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs) -A generic composition root is represented by a method. +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. ```c# @@ -49,7 +49,7 @@ var service = composition.GetMyRoot(); var someOtherService = composition.GetOtherService(); ``` -When a generic composition root is used, `Resolve` methods cannot be used to resolve them. +:warning: `Resolve' methods cannot be used to resolve generic composition roots. The following partial class will be generated: diff --git a/readme/resolve-methods.md b/readme/resolve-methods.md index d450358ec..3f2a7e956 100644 --- a/readme/resolve-methods.md +++ b/readme/resolve-methods.md @@ -36,10 +36,10 @@ var service1 = composition.Resolve(); var service2 = composition.Resolve(typeof(IService)); var service3 = composition.Resolve(typeof(IService), null); -// Resolve by tag +// Resolve by "My Tag" tag // The next 3 lines of code do the same thing too: -var otherService1 = composition.Resolve("Other"); -var otherService2 = composition.Resolve(typeof(IService),"Other"); +var otherService1 = composition.Resolve("My Tag"); +var otherService2 = composition.Resolve(typeof(IService),"My Tag"); var otherService3 = composition.OtherService; // Gets the composition through the public root ``` @@ -218,7 +218,7 @@ classDiagram class Service { +Service(IDependency dependency) } - OtherService --|> IService : "Other" + OtherService --|> IService : "My Tag" class OtherService { +OtherService() } diff --git a/readme/root-binding.md b/readme/root-binding.md index 674438e96..51bb7dead 100644 --- a/readme/root-binding.md +++ b/readme/root-binding.md @@ -2,7 +2,7 @@ [![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Basics/RootBindScenario.cs) -You might want to register some services as roots. You can use `RootBind()` method in order to reduce repetitions. The registration `composition.RootBind().To()` is an equivalent to `composition.Bind().To().Root()`. +In general, it is recommended to define one composition root for the entire application. But sometimes it is necessary to have multiple roots. To simplify the definition of composition roots, a "hybrid" API method `RootBind(string rootName)` was added. It allows you to define a binding and at the same time the root of the composition. You can it in order to reduce repetitions. The registration `composition.RootBind().To()` is an equivalent to `composition.Bind().To().Root()`. ```c# diff --git a/readme/simplified-binding.md b/readme/simplified-binding.md index bcf788ca9..ce93dd5ac 100644 --- a/readme/simplified-binding.md +++ b/readme/simplified-binding.md @@ -37,7 +37,7 @@ class Service( : IService; // Specifies to create a partial class "Composition" -DI.Setup("Composition") +DI.Setup(nameof(Composition)) // Begins the binding definition for the implementation type itself, // and if the implementation is not an abstract class or structure, // for all abstract but NOT special types that are directly implemented. @@ -132,7 +132,6 @@ classDiagram Dependency --|> IDependency Dependency --|> IOtherDependency Dependency --|> IEnumerableᐸStringᐳ - Dependency --|> IDependencyBase class Dependency { +Dependency() } @@ -149,9 +148,6 @@ classDiagram class IEnumerableᐸStringᐳ { <> } - class IDependencyBase { - <> - } class IService { <> } diff --git a/src/Pure.DI.Core/Core/BaseSymbolsProvider.cs b/src/Pure.DI.Core/Core/BaseSymbolsProvider.cs index 9ea697a84..e061c5d43 100644 --- a/src/Pure.DI.Core/Core/BaseSymbolsProvider.cs +++ b/src/Pure.DI.Core/Core/BaseSymbolsProvider.cs @@ -3,18 +3,32 @@ namespace Pure.DI.Core; internal class BaseSymbolsProvider : IBaseSymbolsProvider { - public IEnumerable GetBaseSymbols(ITypeSymbol symbol, int deep = int.MaxValue) + public IEnumerable GetBaseSymbols( + ITypeSymbol symbol, + Func predicate, + int maxDeepness = int.MaxValue) => + GetBaseSymbols(symbol, predicate, maxDeepness, 0); + + private static IEnumerable GetBaseSymbols( + ITypeSymbol symbol, + Func predicate, + int maxDeepness, + int deepness) { - if (deep < 0) + if (deepness > maxDeepness) { yield break; } - deep--; while (true) { - yield return symbol; - foreach (var type in symbol.Interfaces.SelectMany(i => GetBaseSymbols(i, deep))) + if (predicate(symbol, deepness)) + { + yield return symbol; + } + + deepness++; + foreach (var type in symbol.Interfaces.SelectMany(i => GetBaseSymbols(i, predicate, maxDeepness, deepness))) { yield return type; } diff --git a/src/Pure.DI.Core/Core/BindingBuilder.cs b/src/Pure.DI.Core/Core/BindingBuilder.cs index a89226292..6fb1870ae 100644 --- a/src/Pure.DI.Core/Core/BindingBuilder.cs +++ b/src/Pure.DI.Core/Core/BindingBuilder.cs @@ -86,8 +86,17 @@ public MdBinding Build(MdSetup setup) if (type is { SpecialType: SpecialType.None, TypeKind: TypeKind.Class, IsAbstract: false }) { baseSymbols = baseSymbolsProvider - .GetBaseSymbols(type, 1) - .Where(i => i.IsAbstract && i.SpecialType == SpecialType.None); + .GetBaseSymbols(type, (i, deepness) => deepness switch + { + 0 => true, + 1 when + type.TypeKind != TypeKind.Interface + && !type.IsAbstract + && (i.TypeKind == TypeKind.Interface || i.IsAbstract) + && i.SpecialType == SpecialType.None + => true, + _ => false + }, 1); } var contracts = new HashSet(baseSymbols, SymbolEqualityComparer.Default) diff --git a/src/Pure.DI.Core/Core/Code/BuildTools.cs b/src/Pure.DI.Core/Core/Code/BuildTools.cs index 9c57edfe9..c03862fc6 100644 --- a/src/Pure.DI.Core/Core/Code/BuildTools.cs +++ b/src/Pure.DI.Core/Core/Code/BuildTools.cs @@ -65,8 +65,7 @@ public IEnumerable OnCreated(BuildContext ctx, Variable variable) } var baseTypes = - baseSymbolsProvider.GetBaseSymbols(variable.InstanceType) - .Concat(Enumerable.Repeat(variable.InstanceType, 1)) + baseSymbolsProvider.GetBaseSymbols(variable.InstanceType, (_, _) => true) .ToImmutableHashSet(SymbolEqualityComparer.Default); var code = new LinesBuilder(); diff --git a/src/Pure.DI.Core/Core/IBaseSymbolsProvider.cs b/src/Pure.DI.Core/Core/IBaseSymbolsProvider.cs index 44cef1ea8..0ec034dd0 100644 --- a/src/Pure.DI.Core/Core/IBaseSymbolsProvider.cs +++ b/src/Pure.DI.Core/Core/IBaseSymbolsProvider.cs @@ -2,5 +2,8 @@ internal interface IBaseSymbolsProvider { - IEnumerable GetBaseSymbols(ITypeSymbol symbol, int deep = int.MaxValue); + IEnumerable GetBaseSymbols( + ITypeSymbol symbol, + Func predicate, + int maxDeepness = int.MaxValue); } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/MetadataValidator.cs b/src/Pure.DI.Core/Core/MetadataValidator.cs index 77aabf039..42317d260 100644 --- a/src/Pure.DI.Core/Core/MetadataValidator.cs +++ b/src/Pure.DI.Core/Core/MetadataValidator.cs @@ -131,7 +131,7 @@ private bool Validate(MdSetup setup, in MdBinding binding) var severityOfNotImplementedContract = setup.Hints.SeverityOfNotImplementedContract; if (severityOfNotImplementedContract > DiagnosticSeverity.Hidden) { - var supportedContracts = new HashSet(baseSymbolsProvider.GetBaseSymbols(implementationType), SymbolEqualityComparer.Default) + var supportedContracts = new HashSet(baseSymbolsProvider.GetBaseSymbols(implementationType, (_, _) => true), SymbolEqualityComparer.Default) { implementationType }; diff --git a/tests/Pure.DI.UsageTests/Basics/ResolveMethodsScenario.cs b/tests/Pure.DI.UsageTests/Basics/ResolveMethodsScenario.cs index 9dbe4e8f7..b0dc11787 100644 --- a/tests/Pure.DI.UsageTests/Basics/ResolveMethodsScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/ResolveMethodsScenario.cs @@ -54,10 +54,10 @@ public void Run() var service2 = composition.Resolve(typeof(IService)); var service3 = composition.Resolve(typeof(IService), null); - // Resolve by tag + // Resolve by "My Tag" tag // The next 3 lines of code do the same thing too: - var otherService1 = composition.Resolve("Other"); - var otherService2 = composition.Resolve(typeof(IService),"Other"); + var otherService1 = composition.Resolve("My Tag"); + var otherService2 = composition.Resolve(typeof(IService),"My Tag"); var otherService3 = composition.OtherService; // Gets the composition through the public root // } service1.ShouldBeOfType(); diff --git a/tests/Pure.DI.UsageTests/Basics/RootBindScenario.cs b/tests/Pure.DI.UsageTests/Basics/RootBindScenario.cs index 5b3df23b8..991449d9e 100644 --- a/tests/Pure.DI.UsageTests/Basics/RootBindScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/RootBindScenario.cs @@ -2,7 +2,7 @@ $v=true $p=17 $d=Root binding -$h=You might want to register some services as roots. You can use `RootBind()` method in order to reduce repetitions. The registration `composition.RootBind().To()` is an equivalent to `composition.Bind().To().Root()`. +$h=In general, it is recommended to define one composition root for the entire application. But sometimes it is necessary to have multiple roots. To simplify the definition of composition roots, a "hybrid" API method `RootBind(string rootName)` was added. It allows you to define a binding and at the same time the root of the composition. You can it in order to reduce repetitions. The registration `composition.RootBind().To()` is an equivalent to `composition.Bind().To().Root()`. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Basics/SimplifiedBindingScenario.cs b/tests/Pure.DI.UsageTests/Basics/SimplifiedBindingScenario.cs index c34d90408..17e9f1502 100644 --- a/tests/Pure.DI.UsageTests/Basics/SimplifiedBindingScenario.cs +++ b/tests/Pure.DI.UsageTests/Basics/SimplifiedBindingScenario.cs @@ -46,6 +46,7 @@ // ReSharper disable UnusedVariable // ReSharper disable UnusedMember.Local // ReSharper disable ArrangeTypeMemberModifiers +// ReSharper disable ClassNeverInstantiated.Global #pragma warning disable CS9113 // Parameter is unread. namespace Pure.DI.UsageTests.Basics.SimplifiedBindingScenario; @@ -92,7 +93,7 @@ public void Run() // Resolve = Off // { // Specifies to create a partial class "Composition" - DI.Setup("Composition") + DI.Setup(nameof(Composition)) // Begins the binding definition for the implementation type itself, // and if the implementation is not an abstract class or structure, // for all abstract but NOT special types that are directly implemented. diff --git a/tests/Pure.DI.UsageTests/Generics/GenericCompositionRootsWithConstraintsScenario.cs b/tests/Pure.DI.UsageTests/Generics/GenericCompositionRootsWithConstraintsScenario.cs index 226b6a7e7..19aab5754 100644 --- a/tests/Pure.DI.UsageTests/Generics/GenericCompositionRootsWithConstraintsScenario.cs +++ b/tests/Pure.DI.UsageTests/Generics/GenericCompositionRootsWithConstraintsScenario.cs @@ -2,6 +2,7 @@ $v=true $p=4 $d=Generic composition roots with constraints +$f=:warning: `Resolve' methods cannot be used to resolve generic composition roots. */ // ReSharper disable ClassNeverInstantiated.Local diff --git a/tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs b/tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs index 90414a607..67e138c82 100644 --- a/tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs +++ b/tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs @@ -2,8 +2,8 @@ $v=true $p=2 $d=Generic composition roots -$h=A generic composition root is represented by a method. -$f=When a generic composition root is used, `Resolve` methods cannot be used to resolve them. +$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. +$f=:warning: `Resolve' methods cannot be used to resolve generic composition roots. */ // ReSharper disable ClassNeverInstantiated.Local