From 446197a7e3b31dc218b30b356a8ff0a15591d89f Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Mon, 10 Jun 2024 11:09:15 +0300 Subject: [PATCH] API Tag.On for special tag that allows injection by name --- README.md | 12 +-- ...> tag-on-injection-site-with-wildcards.md} | 19 +++-- ...{name-tags.md => tag-on-injection-site.md} | 36 +++++---- src/Pure.DI.Core/Components/Api.g.cs | 10 ++- src/Pure.DI.Core/Core/BindingBuilder.cs | 13 ++-- .../Core/Code/ClassDiagramBuilder.cs | 4 +- src/Pure.DI.Core/Core/Code/CodeExtensions.cs | 2 +- .../ImplementationDependencyNodeBuilder.cs | 2 +- src/Pure.DI.Core/Core/Models/Injection.cs | 6 +- src/Pure.DI.Core/Core/Models/MdTag.cs | 6 +- src/Pure.DI.Core/Core/Models/NameTagObject.cs | 20 ----- src/Pure.DI.Core/Core/Models/TagOnObject.cs | 34 +++++++++ .../Core/SemanticModelExtensions.cs | 38 ++++++++-- tests/Pure.DI.IntegrationTests/TagsTests.cs | 76 +++++++++++++++++-- tests/Pure.DI.Tests/InjectionTests.cs | 29 +++---- .../Advanced/PartialClassScenario.cs | 2 +- .../Advanced/SeveralPartialClassesScenario.cs | 2 +- ...nario.cs => TagOnInjectionSiteScenario.cs} | 31 +++++--- ...agOnInjectionSiteWithWildcardsScenario.cs} | 16 ++-- ...ckingAsyncDisposableInDelegatesScenario.cs | 2 +- .../TrackingAsyncDisposableScenario.cs | 2 +- .../TrackingDisposableInDelegatesScenario.cs | 2 +- .../Advanced/TrackingDisposableScenario.cs | 2 +- 23 files changed, 246 insertions(+), 120 deletions(-) rename readme/{name-tag-with-wildcards.md => tag-on-injection-site-with-wildcards.md} (82%) rename readme/{name-tags.md => tag-on-injection-site.md} (62%) delete mode 100644 src/Pure.DI.Core/Core/Models/NameTagObject.cs create mode 100644 src/Pure.DI.Core/Core/Models/TagOnObject.cs rename tests/Pure.DI.UsageTests/Advanced/{NameTagsScenario.cs => TagOnInjectionSiteScenario.cs} (60%) rename tests/Pure.DI.UsageTests/Advanced/{NameTagsWithWildcardsScenario.cs => TagOnInjectionSiteWithWildcardsScenario.cs} (81%) diff --git a/README.md b/README.md index 387293347..b26b7cc67 100644 --- a/README.md +++ b/README.md @@ -269,17 +269,17 @@ 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) -- [Name tag with wildcards](readme/name-tag-with-wildcards.md) -- [Partial class](readme/partial-class.md) +- [Tag on injection site](readme/tag-on-injection-site.md) +- [Tag on injection site with wildcards](readme/tag-on-injection-site-with-wildcards.md) - [Dependent compositions](readme/dependent-compositions.md) - [Accumulators](readme/accumulators.md) - [Global compositions](readme/global-compositions.md) -- [Tracking async disposable instances in delegates](readme/tracking-async-disposable-instances-in-delegates.md) -- [Tracking disposable instances in delegates](readme/tracking-disposable-instances-in-delegates.md) +- [Partial class](readme/partial-class.md) +- [A few partial classes](readme/a-few-partial-classes.md) - [Tracking disposable instances per a composition root](readme/tracking-disposable-instances-per-a-composition-root.md) +- [Tracking disposable instances in delegates](readme/tracking-disposable-instances-in-delegates.md) - [Tracking async disposable instances per a composition root](readme/tracking-async-disposable-instances-per-a-composition-root.md) +- [Tracking async disposable instances in delegates](readme/tracking-async-disposable-instances-in-delegates.md) ### Applications - Console - [Schrödinger's cat](readme/Console.md) diff --git a/readme/name-tag-with-wildcards.md b/readme/tag-on-injection-site-with-wildcards.md similarity index 82% rename from readme/name-tag-with-wildcards.md rename to readme/tag-on-injection-site-with-wildcards.md index 283503aa3..deb52b28c 100644 --- a/readme/name-tag-with-wildcards.md +++ b/readme/tag-on-injection-site-with-wildcards.md @@ -1,9 +1,14 @@ -#### Name tag with wildcards +#### Tag on injection site with wildcards -[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/NameTagsWithWildcardsScenario.cs) +[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs) + +The wildcards ‘*’ and ‘?’ are supported. ```c# +namespace MyNamespace2; + + interface IDependency; class AbcDependency : IDependency; @@ -42,9 +47,9 @@ class Service( } DI.Setup(nameof(Composition)) - .Bind("*Service:dependency1", "*Consumer:myDep") + .Bind(Tag.On("*Service:dependency1", "*Consumer:myDep")) .To() - .Bind("*Service:dependency2", "*Service:Dependency3") + .Bind(Tag.On("*Service:dependency2", "*Service:Dependency3")) .To() .Bind().To>() .Bind().To() @@ -69,13 +74,11 @@ classDiagram <> +IService Root } - AbcDependency --|> IDependency : "*Service﹕dependency1" - AbcDependency --|> IDependency : "*Consumer﹕myDep" + AbcDependency --|> IDependency class AbcDependency { +AbcDependency() } - XyzDependency --|> IDependency : "*Service﹕dependency2" - XyzDependency --|> IDependency : "*Service﹕Dependency3" + XyzDependency --|> IDependency class XyzDependency { +XyzDependency() } diff --git a/readme/name-tags.md b/readme/tag-on-injection-site.md similarity index 62% rename from readme/name-tags.md rename to readme/tag-on-injection-site.md index b4e1d334e..09e6dddf5 100644 --- a/readme/name-tags.md +++ b/readme/tag-on-injection-site.md @@ -1,19 +1,22 @@ -#### Name tags +#### Tag on injection site -[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/NameTagsScenario.cs) +[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.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_: +Tag on injection site is specified in a special format: `Tag.On("..[:argument]")`. The argument is specified only for the constructor and methods. For example, for namespace _MyNamespace_ and 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_ +- `Tag.On("MyNamespace.Class1.Class1:state1") - the tag corresponds to the constructor argument named _state_ of type _MyNamespace.Class1_ +- `Tag.On("MyNamespace.Class1.DoSomething:myArg")` - the tag corresponds to the _myArg_ argument of the _DoSomething_ method +- `Tag.On("MyNamespace.Class1.MyData")` - the tag corresponds to property or field _MyData_ -The wildcards ‘*’ and ‘?’ are supported. All names are case sensitive. The global namespace prefix `global::` must be omitted. +The wildcards `*` and `?` are supported. All names are case-sensitive. The global namespace prefix `global::` must be omitted. You can also combine multiple tags in a single `Tag.On("...", "...")` call. -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: +For generic types, the type name also contains the number of type parameters, e.g., for the `myDep` constructor argument of the `Consumer` class, the tag on the injection site would be ``MyNamespace.Consumer`1.Consumer:myDep``: ```c# +namespace MyNamespace; + + interface IDependency; class AbcDependency : IDependency; @@ -53,11 +56,13 @@ class Service( DI.Setup(nameof(Composition)) .Bind( - "MyNamespace.Service.Service:dependency1", - "MyNamespace.Consumer`1.Consumer:myDep") + Tag.On("MyNamespace.Service.Service:dependency1"), + Tag.On("MyNamespace.Consumer`1.Consumer:myDep")) .To() - .Bind("MyNamespace.Service.Service:dependency2", - "MyNamespace.Service:Dependency3") + .Bind( + Tag.On( + "MyNamespace.Service.Service:dependency2", + "MyNamespace.Service:Dependency3")) .To() .Bind().To>() .Bind().To() @@ -85,13 +90,12 @@ classDiagram <> +IService Root } - AbcDependency --|> IDependency : "MyNamespace.Service.Service﹕dependency1" - AbcDependency --|> IDependency : "MyNamespace.Consumer`1.Consumer﹕myDep" + AbcDependency --|> IDependency + AbcDependency --|> IDependency class AbcDependency { +AbcDependency() } - XyzDependency --|> IDependency : "MyNamespace.Service.Service﹕dependency2" - XyzDependency --|> IDependency : "MyNamespace.Service﹕Dependency3" + XyzDependency --|> IDependency class XyzDependency { +XyzDependency() } diff --git a/src/Pure.DI.Core/Components/Api.g.cs b/src/Pure.DI.Core/Components/Api.g.cs index 2c78a6434..b21817bba 100644 --- a/src/Pure.DI.Core/Components/Api.g.cs +++ b/src/Pure.DI.Core/Components/Api.g.cs @@ -1120,7 +1120,7 @@ internal enum RootKinds /// /// Represents well known tags. /// - internal enum Tag + internal class Tag { /// /// Unique tag. @@ -1134,8 +1134,8 @@ internal enum Tag /// /// /// - Unique, - + public static readonly Tag Unique = new Tag(); + /// /// Tag of target implementation type. /// @@ -1146,7 +1146,9 @@ internal enum Tag /// /// /// - Type + public static readonly Tag Type = new Tag(); + + public static Tag On(params string[] names) => new Tag(); } /// diff --git a/src/Pure.DI.Core/Core/BindingBuilder.cs b/src/Pure.DI.Core/Core/BindingBuilder.cs index ed173caca..d44749e31 100644 --- a/src/Pure.DI.Core/Core/BindingBuilder.cs +++ b/src/Pure.DI.Core/Core/BindingBuilder.cs @@ -161,13 +161,14 @@ private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy id, IReadO if (tag.Value is Tag tagVal) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - switch (tagVal) + if (tagVal == Tag.Type) { - case Tag.Type: - return MdTag.CreateTypeTag(tag, type); - - case Tag.Unique: - return MdTag.CreateUniqueTag(tag, id.Value); + return MdTag.CreateTypeTag(tag, type); + } + + if (tagVal == Tag.Unique) + { + 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 7d57f32e4..1b7b40706 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 is null or NameTagObject ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}"; + $"{(dependency.Injection.Tag is null or TagOnObject ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}"; private static string FormatTag(object? tag) => - tag is null or NameTagObject + tag is null or TagOnObject ? "" : EscapeTag(tag) + " "; diff --git a/src/Pure.DI.Core/Core/Code/CodeExtensions.cs b/src/Pure.DI.Core/Core/Code/CodeExtensions.cs index ba4e9254b..8543e3f1d 100644 --- a/src/Pure.DI.Core/Core/Code/CodeExtensions.cs +++ b/src/Pure.DI.Core/Core/Code/CodeExtensions.cs @@ -22,7 +22,7 @@ public static string ValueToString(this object? tag, string defaultValue = "null char ch => $"'{ch.ToString()}'", Enum en => $"{en.GetType()}.{en}", ITypeSymbol => $"typeof({tag})", - NameTagObject => defaultValue, + TagOnObject => 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 2cec7f399..36e3cff2c 100644 --- a/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs +++ b/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs @@ -260,7 +260,7 @@ private ImmutableArray GetParameters( private object CreateNameTag(ISymbol symbol) { var memberName = symbol.ContainingSymbol.ToDisplayString(NameTagQualifiedFormat); - return MdTag.CreateNameTagValue(wildcardMatcher, $"{memberName}:{symbol.Name}"); + return MdTag.CreateNameTagValue(wildcardMatcher, false, $"{memberName}:{symbol.Name}"); } private T GetAttribute( diff --git a/src/Pure.DI.Core/Core/Models/Injection.cs b/src/Pure.DI.Core/Core/Models/Injection.cs index f9700c28b..11b11afd9 100644 --- a/src/Pure.DI.Core/Core/Models/Injection.cs +++ b/src/Pure.DI.Core/Core/Models/Injection.cs @@ -7,7 +7,7 @@ internal readonly record struct Injection( ITypeSymbol Type, object? Tag) { - public override string ToString() => $"{Type}{(Tag != default && Tag is not NameTagObject ? $"({Tag.ValueToString()})" : "")}"; + public override string ToString() => $"{Type}{(Tag != default && Tag is not TagOnObject ? $"({Tag.ValueToString()})" : "")}"; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Injection other) => @@ -21,7 +21,7 @@ public override int GetHashCode() => public static bool EqualTags(object? tag, object? otherTag) => ReferenceEquals(tag, MdTag.ContextTag) || ReferenceEquals(otherTag, MdTag.ContextTag) - || (tag is NameTagObject nameTagObject && nameTagObject.Equals(otherTag)) - || (otherTag is NameTagObject otherNameTagObject && otherNameTagObject.Equals(tag)) + || (tag is TagOnObject tagOn && tagOn.Equals(otherTag)) + || (otherTag is TagOnObject otherTagOn && otherTagOn.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 329e606e1..e11eca52b 100644 --- a/src/Pure.DI.Core/Core/Models/MdTag.cs +++ b/src/Pure.DI.Core/Core/Models/MdTag.cs @@ -13,15 +13,15 @@ public static MdTag CreateTypeTag(MdTag tag, ITypeSymbol? type) => public static MdTag CreateUniqueTag(MdTag tag, int id) => tag with { Value = new UniqueTag(id) }; - public static object CreateNameTagValue(IWildcardMatcher wildcardMatcher, string name) => - new NameTagObject(wildcardMatcher, name); + public static object CreateNameTagValue(IWildcardMatcher wildcardMatcher, bool isMetadata, params string[] names) => + new TagOnObject(wildcardMatcher, isMetadata, names); public override string ToString() => Value switch { null => "null", string => $"\"{Value}\"", - NameTagObject => "", + TagOnObject => "", _ => Value.ToString() }; diff --git a/src/Pure.DI.Core/Core/Models/NameTagObject.cs b/src/Pure.DI.Core/Core/Models/NameTagObject.cs deleted file mode 100644 index 905d5d1ef..000000000 --- a/src/Pure.DI.Core/Core/Models/NameTagObject.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Pure.DI.Core.Models; - -internal class NameTagObject(IWildcardMatcher wildcardMatcher, 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 => wildcardMatcher.Match(str.AsSpan(), name.AsSpan()), - NameTagObject => true, - _ => false - }; - } - - public override int GetHashCode() => name.GetHashCode(); -} \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/Models/TagOnObject.cs b/src/Pure.DI.Core/Core/Models/TagOnObject.cs new file mode 100644 index 000000000..b6bf08d89 --- /dev/null +++ b/src/Pure.DI.Core/Core/Models/TagOnObject.cs @@ -0,0 +1,34 @@ +namespace Pure.DI.Core.Models; + +internal class TagOnObject(IWildcardMatcher wildcardMatcher, bool isMetadata, params string[] names) +{ + private bool IsMetadata => isMetadata; + private string[] Names => names; + + public override string ToString() => $"NameTag(\"{string.Join(", ", names.Select(i => $"{i}"))}\")"; + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return true; + if (ReferenceEquals(this, obj)) return true; + return obj switch + { + TagOnObject nameTag => Equals(nameTag), + _ => IsMetadata + }; + } + + private bool Equals(TagOnObject nameTag) + { + if (!IsMetadata && !nameTag.IsMetadata) + { + return true; + } + + return isMetadata + ? nameTag.Names.Length == 1 && Names.Any(name => wildcardMatcher.Match(name.AsSpan(), nameTag.Names[0].AsSpan())) + : Names.Length == 1 && nameTag.Names.Any(name => wildcardMatcher.Match(name.AsSpan(), Names[0].AsSpan())); + } + + public override int GetHashCode() => 0; +} \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/SemanticModelExtensions.cs b/src/Pure.DI.Core/Core/SemanticModelExtensions.cs index 3b2f52ab7..680a577da 100644 --- a/src/Pure.DI.Core/Core/SemanticModelExtensions.cs +++ b/src/Pure.DI.Core/Core/SemanticModelExtensions.cs @@ -75,11 +75,11 @@ when memberAccessExpressionSyntax.IsKind(SyntaxKind.SimpleMemberAccessExpression { if (memberAccessExpressionSyntax.Expression is IdentifierNameSyntax classIdentifierName) { - var enumValueStr = memberAccessExpressionSyntax.Name.Identifier.Text; + var valueStr = memberAccessExpressionSyntax.Name.Identifier.Text; switch (classIdentifierName.Identifier.Text) { case nameof(CompositionKind): - if (Enum.TryParse(enumValueStr, out var compositionKindValue)) + if (Enum.TryParse(valueStr, out var compositionKindValue)) { return (T)(object)compositionKindValue; } @@ -87,7 +87,7 @@ when memberAccessExpressionSyntax.IsKind(SyntaxKind.SimpleMemberAccessExpression break; case nameof(Lifetime): - if (Enum.TryParse(enumValueStr, out var lifetimeValue)) + if (Enum.TryParse(valueStr, out var lifetimeValue)) { return (T)(object)lifetimeValue; } @@ -95,17 +95,41 @@ when memberAccessExpressionSyntax.IsKind(SyntaxKind.SimpleMemberAccessExpression break; case nameof(Tag): - if (Enum.TryParse(enumValueStr, out var tagValue)) + switch (valueStr) { - return (T)(object)tagValue; + case nameof(Tag.Type): + return (T)(object)Tag.Type; + + case nameof(Tag.Unique): + return (T)(object)Tag.Unique; } - + break; } } break; } + + case InvocationExpressionSyntax invocationExpressionSyntax: + { + switch (invocationExpressionSyntax.Expression) + { + case MemberAccessExpressionSyntax { Name.Identifier.Text: nameof(Tag.On), Expression: IdentifierNameSyntax { Identifier.Text: nameof(Tag) } }: + if (invocationExpressionSyntax.ArgumentList.Arguments is { } nameArgs) + { + var names = nameArgs + .Select(nameArg => GetConstantValue(semanticModel, nameArg.Expression)) + .Where(i => !string.IsNullOrWhiteSpace(i)).OfType() + .ToArray(); + + return (T)MdTag.CreateNameTagValue(new WildcardMatcher(), true, names); + } + break; + } + + break; + } } var optionalValue = semanticModel.GetConstantValue(node); @@ -125,6 +149,6 @@ when memberAccessExpressionSyntax.IsKind(SyntaxKind.SimpleMemberAccessExpression return (T)typeOfOperation.TypeOperand; } - throw new CompileErrorException($"{node} must be a constant value of type {typeof(T)}.", node.GetLocation(), LogId.ErrorInvalidMetadata); + throw new CompileErrorException($"{node} must be a constant value of type {typeof(T)} or a special API call.", node.GetLocation(), LogId.ErrorInvalidMetadata); } } \ No newline at end of file diff --git a/tests/Pure.DI.IntegrationTests/TagsTests.cs b/tests/Pure.DI.IntegrationTests/TagsTests.cs index cfb58a446..9509302da 100644 --- a/tests/Pure.DI.IntegrationTests/TagsTests.cs +++ b/tests/Pure.DI.IntegrationTests/TagsTests.cs @@ -520,7 +520,7 @@ internal partial class Composition { void Setup() => DI.Setup("Composition") - .Bind("Sample.Service.Service:dep").To() + .Bind(Tag.On("Sample.Service.Service:dep")).To() .Bind().To() .Root("Root"); } @@ -569,7 +569,7 @@ internal partial class Composition { void Setup() => DI.Setup("Composition") - .Bind("Sample.Service.Service:dep").To() + .Bind(Tag.On("Sample.Service.Service:dep")).To() .Bind().To() .Root("Root"); } @@ -621,7 +621,7 @@ internal partial class Composition void Setup() => DI.Setup("Composition") .Hint(Hint.Resolve, "Off") - .Bind("Sample.Service`1.Service:dep").To() + .Bind(Tag.On("Sample.Service`1.Service:dep")).To() .Bind().To>() .Root>("Root"); } @@ -686,7 +686,71 @@ internal partial class Composition { void Setup() => DI.Setup("Composition") - .Bind("Sample.Service.Service:dep", "Sample.Service.Service:abc", "Sample.Service:MyProp").To() + .Bind(Tag.On("Sample.Service.Service:dep"), Tag.On("Sample.Service.Service:abc"), Tag.On("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 ShouldSupportSeveralTagNamesAsArray() + { + // 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(Tag.On("Sample.Service.Service:dep", "Sample.Service.Service:abc", "Sample.Service:MyProp")).To() .Bind().To() .Root("Root"); } @@ -750,7 +814,7 @@ internal partial class Composition { void Setup() => DI.Setup("Composition") - .Bind("Sample.Service.Service:*", "Sample.Service:MyProp").To() + .Bind(Tag.On("Sample.Service.Service:*"), Tag.On("Sample.Service:MyProp")).To() .Bind().To() .Root("Root"); } @@ -812,7 +876,7 @@ internal partial class Composition { void Setup() => DI.Setup("Composition") - .Bind("Sample.Service:MyProp").To() + .Bind(Tag.On("Sample.Service:MyProp")).To() .Bind().To() .Root("Root"); } diff --git a/tests/Pure.DI.Tests/InjectionTests.cs b/tests/Pure.DI.Tests/InjectionTests.cs index 59ae8352a..a051f007b 100644 --- a/tests/Pure.DI.Tests/InjectionTests.cs +++ b/tests/Pure.DI.Tests/InjectionTests.cs @@ -40,20 +40,23 @@ internal void ShouldImplementEqualityMethods(int id, Injection injection1, Injec 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 NameTagObject(WildcardMatcher,"Abc")), true]; - yield return [6, new Injection(TypeSymbol1, new NameTagObject(WildcardMatcher, "Abc")), new Injection(TypeSymbol1, new NameTagObject(WildcardMatcher, "Abc")), true]; - yield return [7, new Injection(TypeSymbol1, "Abc"), new Injection(TypeSymbol1, new NameTagObject(WildcardMatcher,"Abc")), true]; - yield return [7, new Injection(TypeSymbol1, "*"), new Injection(TypeSymbol1, new NameTagObject(WildcardMatcher,"Abc")), true]; - yield return [8, new Injection(TypeSymbol1, new NameTagObject(WildcardMatcher,"Abc")), new Injection(TypeSymbol1, MdTag.ContextTag), true]; - yield return [14, new Injection(TypeSymbol1, new NameTagObject(WildcardMatcher,"Abc")), new Injection(TypeSymbol1, new NameTagObject(WildcardMatcher, "Xyz")), true]; + yield return [5, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), true]; + yield return [6, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), true]; + yield return [7, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "*")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), true]; + yield return [8, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol1, MdTag.ContextTag), true]; + yield return [9, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Xyz", "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), true]; + yield return [10, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "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 NameTagObject(WildcardMatcher,"Abc")), new Injection(TypeSymbol2, new NameTagObject(WildcardMatcher, "Abc")), false]; - yield return [15, new Injection(TypeSymbol1, new NameTagObject(WildcardMatcher,"Abc")), new Injection(TypeSymbol2, MdTag.ContextTag), false]; - yield return [16, new Injection(TypeSymbol1, "Xyz"), new Injection(TypeSymbol1, new NameTagObject(WildcardMatcher,"Abc")), false]; + yield return [50, new Injection(TypeSymbol1, null), new Injection(TypeSymbol2, 1), false]; + yield return [51, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, 1), false]; + yield return [52, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, "Abc"), false]; + yield return [53, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Xyz")), false]; + yield return [54, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol2, MdTag.ContextTag), false]; + yield return [55, new Injection(TypeSymbol1, "Xyz"), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), false]; + yield return [56, new Injection(TypeSymbol1, null), new Injection(TypeSymbol2, null), false]; + yield return [57, new Injection(TypeSymbol1, "*"), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), false]; + yield return [58, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Xyz")), false]; + yield return [59, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Xyz")), false]; } private class WildcardMatcherStub: IWildcardMatcher diff --git a/tests/Pure.DI.UsageTests/Advanced/PartialClassScenario.cs b/tests/Pure.DI.UsageTests/Advanced/PartialClassScenario.cs index 30830b9b0..78352d6d4 100644 --- a/tests/Pure.DI.UsageTests/Advanced/PartialClassScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/PartialClassScenario.cs @@ -1,6 +1,6 @@ /* $v=true -$p=6 +$p=10 $d=Partial class $h=A partial class can contain setup code. $f=The partial class is also useful for specifying access modifiers to the generated class. diff --git a/tests/Pure.DI.UsageTests/Advanced/SeveralPartialClassesScenario.cs b/tests/Pure.DI.UsageTests/Advanced/SeveralPartialClassesScenario.cs index 119e58d48..5c6837115 100644 --- a/tests/Pure.DI.UsageTests/Advanced/SeveralPartialClassesScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/SeveralPartialClassesScenario.cs @@ -1,6 +1,6 @@ /* $v=true -$p=6 +$p=11 $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/Advanced/NameTagsScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs similarity index 60% rename from tests/Pure.DI.UsageTests/Advanced/NameTagsScenario.cs rename to tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs index 054eabcdb..7e22b05bd 100644 --- a/tests/Pure.DI.UsageTests/Advanced/NameTagsScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs @@ -1,16 +1,16 @@ /* $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_: +$d=Tag on injection site +$h=Tag on injection site is specified in a special format: `Tag.On("..[:argument]")`. The argument is specified only for the constructor and methods. For example, for namespace _MyNamespace_ and 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=- `Tag.On("MyNamespace.Class1.Class1:state1") - the tag corresponds to the constructor argument named _state_ of type _MyNamespace.Class1_ +$h=- `Tag.On("MyNamespace.Class1.DoSomething:myArg")` - the tag corresponds to the _myArg_ argument of the _DoSomething_ method +$h=- `Tag.On("MyNamespace.Class1.MyData")` - the tag corresponds to property or field _MyData_ $h= -$h=The wildcards ‘*’ and ‘?’ are supported. All names are case sensitive. The global namespace prefix `global::` must be omitted. +$h=The wildcards `*` and `?` are supported. All names are case-sensitive. The global namespace prefix `global::` must be omitted. You can also combine multiple tags in a single `Tag.On("...", "...")` call. $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: +$h=For generic types, the type name also contains the number of type parameters, e.g., for the `myDep` constructor argument of the `Consumer` class, the tag on the injection site would be ``MyNamespace.Consumer`1.Consumer:myDep``: $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(...)]`. */ @@ -23,8 +23,11 @@ // 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; @@ -77,11 +80,15 @@ public void Run() // { DI.Setup(nameof(Composition)) .Bind( - "MyNamespace.Service.Service:dependency1", - "MyNamespace.Consumer`1.Consumer:myDep") + Tag.On("MyNamespace.Service.Service:dependency1"), + // Tag on injection site for generic type + Tag.On("MyNamespace.Consumer`1.Consumer:myDep")) .To() - .Bind("MyNamespace.Service.Service:dependency2", - "MyNamespace.Service:Dependency3") + .Bind( + // Combined tag on injection sites + Tag.On( + "MyNamespace.Service.Service:dependency2", + "MyNamespace.Service:Dependency3")) .To() .Bind().To>() .Bind().To() @@ -96,6 +103,6 @@ public void Run() service.Dependency3.ShouldBeOfType(); service.Dependency4.ShouldBeOfType(); // } - composition.SaveClassDiagram("NameTagsScenario"); + composition.SaveClassDiagram("TagOnInjectionSiteScenario"); } } \ No newline at end of file diff --git a/tests/Pure.DI.UsageTests/Advanced/NameTagsWithWildcardsScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs similarity index 81% rename from tests/Pure.DI.UsageTests/Advanced/NameTagsWithWildcardsScenario.cs rename to tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs index 9e2477c46..6dccde808 100644 --- a/tests/Pure.DI.UsageTests/Advanced/NameTagsWithWildcardsScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs @@ -1,7 +1,8 @@ /* $v=true $p=6 -$d=Name tag with wildcards +$d=Tag on injection site with wildcards +$h=The wildcards ‘*’ and ‘?’ are supported. */ // ReSharper disable ClassNeverInstantiated.Local @@ -12,8 +13,11 @@ // ReSharper disable UnusedAutoPropertyAccessor.Global // ReSharper disable UnusedTypeParameter #pragma warning disable CS9113 // Parameter is unread. +// { namespace MyNamespace2; +// } + using Pure.DI; using Pure.DI.UsageTests; using Xunit; @@ -65,9 +69,9 @@ public void Run() // Resolve = Off // { DI.Setup(nameof(Composition)) - .Bind("*Service:dependency1", "*Consumer:myDep") + .Bind(Tag.On("*Service:Dependency3", "*Consumer:myDep")) .To() - .Bind("*Service:dependency2", "*Service:Dependency3") + .Bind(Tag.On("*Service:dependency?")) .To() .Bind().To>() .Bind().To() @@ -77,11 +81,11 @@ public void Run() var composition = new Composition(); var service = composition.Root; - service.Dependency1.ShouldBeOfType(); + service.Dependency1.ShouldBeOfType(); service.Dependency2.ShouldBeOfType(); - service.Dependency3.ShouldBeOfType(); + service.Dependency3.ShouldBeOfType(); service.Dependency4.ShouldBeOfType(); // } - composition.SaveClassDiagram("NameTagsWithWildcardsScenario"); + composition.SaveClassDiagram("TagOnInjectionSiteWithWildcardsScenario"); } } \ No newline at end of file diff --git a/tests/Pure.DI.UsageTests/Advanced/TrackingAsyncDisposableInDelegatesScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TrackingAsyncDisposableInDelegatesScenario.cs index 216c460d8..e4e86fc5b 100644 --- a/tests/Pure.DI.UsageTests/Advanced/TrackingAsyncDisposableInDelegatesScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/TrackingAsyncDisposableInDelegatesScenario.cs @@ -1,6 +1,6 @@ /* $v=true -$p=16 +$p=103 $d=Tracking async disposable instances in delegates */ diff --git a/tests/Pure.DI.UsageTests/Advanced/TrackingAsyncDisposableScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TrackingAsyncDisposableScenario.cs index f19db7420..0147560f1 100644 --- a/tests/Pure.DI.UsageTests/Advanced/TrackingAsyncDisposableScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/TrackingAsyncDisposableScenario.cs @@ -1,6 +1,6 @@ /* $v=true -$p=17 +$p=102 $d=Tracking async disposable instances per a composition root */ diff --git a/tests/Pure.DI.UsageTests/Advanced/TrackingDisposableInDelegatesScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TrackingDisposableInDelegatesScenario.cs index dd8ca0831..ca468e69b 100644 --- a/tests/Pure.DI.UsageTests/Advanced/TrackingDisposableInDelegatesScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/TrackingDisposableInDelegatesScenario.cs @@ -1,6 +1,6 @@ /* $v=true -$p=16 +$p=101 $d=Tracking disposable instances in delegates */ diff --git a/tests/Pure.DI.UsageTests/Advanced/TrackingDisposableScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TrackingDisposableScenario.cs index 0da36e2b4..2e5a732e2 100644 --- a/tests/Pure.DI.UsageTests/Advanced/TrackingDisposableScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/TrackingDisposableScenario.cs @@ -1,6 +1,6 @@ /* $v=true -$p=16 +$p=100 $d=Tracking disposable instances per a composition root */