diff --git a/Pure.DI.sln.DotSettings b/Pure.DI.sln.DotSettings
index d5059bcb0..72627da10 100644
--- a/Pure.DI.sln.DotSettings
+++ b/Pure.DI.sln.DotSettings
@@ -287,6 +287,7 @@
True
True
True
+ True
True
True
True
diff --git a/build/ReadmeTarget.cs b/build/ReadmeTarget.cs
index 591f9703b..258d45360 100644
--- a/build/ReadmeTarget.cs
+++ b/build/ReadmeTarget.cs
@@ -323,7 +323,6 @@ private static async Task AddExample(string logsDirectory, string exampleSearchP
foreach (var generatedCodeFile in Directory.GetFiles(Path.Combine(logsDirectory, "Pure.DI", "Pure.DI.SourceGenerator"), exampleSearchPattern).OrderBy(i => i))
{
var ns = string.Join('.', Path.GetFileName(generatedCodeFile).Split('.').Reverse().Skip(3).Reverse()) + ".";
- var name = Path.GetFileName(generatedCodeFile).Split('.').Reverse().Skip(2).FirstOrDefault() ?? "Generated";
await examplesWriter.WriteLineAsync("The following partial class will be generated:");
await examplesWriter.WriteLineAsync("");
await examplesWriter.WriteLineAsync("```c#");
diff --git a/readme/advanced-interception.md b/readme/advanced-interception.md
index dc1cabf43..3965467fb 100644
--- a/readme/advanced-interception.md
+++ b/readme/advanced-interception.md
@@ -32,7 +32,7 @@ class Service(IDependency dependency) : IService
internal partial class Composition: IInterceptor
{
- private readonly List _log = new();
+ private readonly List _log = [];
private static readonly IProxyBuilder ProxyBuilder = new DefaultProxyBuilder();
private readonly IInterceptor[] _interceptors = [];
diff --git a/readme/tag-on-injection-site-with-wildcards.md b/readme/tag-on-injection-site-with-wildcards.md
index deb52b28c..dfa638835 100644
--- a/readme/tag-on-injection-site-with-wildcards.md
+++ b/readme/tag-on-injection-site-with-wildcards.md
@@ -47,9 +47,9 @@ class Service(
}
DI.Setup(nameof(Composition))
- .Bind(Tag.On("*Service:dependency1", "*Consumer:myDep"))
+ .Bind(Tag.On("*Service:Dependency3", "*Consumer:myDep"))
.To()
- .Bind(Tag.On("*Service:dependency2", "*Service:Dependency3"))
+ .Bind(Tag.On("*Service:dependency?"))
.To()
.Bind().To>()
.Bind().To()
@@ -59,9 +59,9 @@ DI.Setup(nameof(Composition))
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();
```
@@ -97,9 +97,9 @@ classDiagram
<>
}
Composition ..> Service : IService Root
- Service *-- AbcDependency : IDependency
Service *-- "2 " XyzDependency : IDependency
Service *-- ConsumerᐸStringᐳ : ConsumerᐸStringᐳ
+ Service *-- AbcDependency : IDependency
ConsumerᐸStringᐳ *-- AbcDependency : IDependency
```
diff --git a/readme/tag-on-injection-site.md b/readme/tag-on-injection-site.md
index 09e6dddf5..0d90cdfea 100644
--- a/readme/tag-on-injection-site.md
+++ b/readme/tag-on-injection-site.md
@@ -2,7 +2,7 @@
[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs)
-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_:
+Sometimes it is necessary to determine which binding will be used to inject explicitly. To do this, use a special tag created by calling the `Tag.On()` method. 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_:
- `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
@@ -57,9 +57,11 @@ class Service(
DI.Setup(nameof(Composition))
.Bind(
Tag.On("MyNamespace.Service.Service:dependency1"),
+ // Tag on injection site for generic type
Tag.On("MyNamespace.Consumer`1.Consumer:myDep"))
.To()
.Bind(
+ // Combined tag
Tag.On(
"MyNamespace.Service.Service:dependency2",
"MyNamespace.Service:Dependency3"))
diff --git a/readme/tag-unique.md b/readme/tag-unique.md
index e48d2a0b7..e5dc2fab6 100644
--- a/readme/tag-unique.md
+++ b/readme/tag-unique.md
@@ -203,11 +203,11 @@ classDiagram
+Service(IEnumerableᐸIDependencyᐸStringᐳᐳ dependencies)
}
class IEnumerableᐸIDependencyᐸStringᐳᐳ
- AbcDependencyᐸStringᐳ --|> IDependencyᐸStringᐳ : Unique tag 0
+ AbcDependencyᐸStringᐳ --|> IDependencyᐸStringᐳ : typeof(Pure.DI.UsageTests.Advanced.TagUniqueScenario.AbcDependency)
class AbcDependencyᐸStringᐳ {
+AbcDependency()
}
- XyzDependencyᐸStringᐳ --|> IDependencyᐸStringᐳ : Unique tag 1
+ XyzDependencyᐸStringᐳ --|> IDependencyᐸStringᐳ : typeof(Pure.DI.UsageTests.Advanced.TagUniqueScenario.XyzDependency)
class XyzDependencyᐸStringᐳ {
+XyzDependency()
}
@@ -219,7 +219,7 @@ classDiagram
}
Composition ..> ServiceᐸStringᐳ : IServiceᐸStringᐳ Root
ServiceᐸStringᐳ o-- "PerBlock" IEnumerableᐸIDependencyᐸStringᐳᐳ : IEnumerableᐸIDependencyᐸStringᐳᐳ
- IEnumerableᐸIDependencyᐸStringᐳᐳ *-- AbcDependencyᐸStringᐳ : Unique tag 0 IDependencyᐸStringᐳ
- IEnumerableᐸIDependencyᐸStringᐳᐳ *-- XyzDependencyᐸStringᐳ : Unique tag 1 IDependencyᐸStringᐳ
+ IEnumerableᐸIDependencyᐸStringᐳᐳ *-- AbcDependencyᐸStringᐳ : typeof(Pure.DI.UsageTests.Advanced.TagUniqueScenario.AbcDependency) IDependencyᐸStringᐳ
+ IEnumerableᐸIDependencyᐸStringᐳᐳ *-- XyzDependencyᐸStringᐳ : typeof(Pure.DI.UsageTests.Advanced.TagUniqueScenario.XyzDependency) IDependencyᐸStringᐳ
```
diff --git a/src/Pure.DI.Core/Components/Api.g.cs b/src/Pure.DI.Core/Components/Api.g.cs
index b21817bba..b6e5448f3 100644
--- a/src/Pure.DI.Core/Components/Api.g.cs
+++ b/src/Pure.DI.Core/Components/Api.g.cs
@@ -1122,6 +1122,8 @@ internal enum RootKinds
///
internal class Tag
{
+ private static readonly Tag Shared = new Tag();
+
///
/// Unique tag.
/// Begins the definition of the binding.
@@ -1134,7 +1136,7 @@ internal class Tag
///
///
///
- public static readonly Tag Unique = new Tag();
+ public static readonly Tag Unique = Shared;
///
/// Tag of target implementation type.
@@ -1146,9 +1148,22 @@ internal class Tag
///
///
///
- public static readonly Tag Type = new Tag();
+ public static readonly Tag Type = Shared;
- public static Tag On(params string[] names) => new Tag();
+ ///
+ /// This tag allows you to determine which binding will be used for explicit injection for a particular injection site.
+ ///
+ ///
+ /// DI.Setup("Composition")
+ /// .Bind(Tag.On("MyNamespace.Service.Service:dep"))
+ /// .To<Dependency>()
+ /// .Bind().To<Service>()
+ /// .Root<IService>("Root");
+ ///
+ ///
+ ///
+ /// Set of labels for inection each, must be specified in a special format: <namespace>.<type>.<member>[:argument]. The argument is specified only for the constructor and methods. The wildcards '*' and '?' are supported. All names are case-sensitive. The global namespace prefix 'global::' must be omitted.
+ public static Tag On(params string[] injectionSites) => Shared;
}
///
diff --git a/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs b/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs
index b20b2a516..65383f20e 100644
--- a/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs
+++ b/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs
@@ -135,6 +135,7 @@ public void ProcessInvocation(
ImmutableArray.Empty,
ImmutableArray.Empty,
ImmutableArray.Empty,
+ Array.Empty(),
comments.FilterHints(invocationComments).ToList()));
break;
@@ -512,28 +513,22 @@ private static void VisitUsingDirectives(
}
// ReSharper disable once SuggestBaseTypeForParameter
- private static void NotSupported(InvocationExpressionSyntax invocation)
- {
+ private static void NotSupported(InvocationExpressionSyntax invocation) =>
throw new CompileErrorException($"The {invocation} is not supported.", invocation.GetLocation(), LogId.ErrorInvalidMetadata);
- }
- private static IReadOnlyList BuildConstantArgs(SemanticModel semanticModel, SeparatedSyntaxList arguments)
- {
- return arguments
+ private static IReadOnlyList BuildConstantArgs(SemanticModel semanticModel, SeparatedSyntaxList arguments) =>
+ arguments
.SelectMany(a => semanticModel.GetConstantValues(a.Expression).Select(value => (value, a.Expression)))
.Select(a => a.value ?? throw new CompileErrorException(
$"{a.Expression} must be a non-null value of type {typeof(T)}.", a.Expression.GetLocation(), LogId.ErrorInvalidMetadata))
.ToList();
- }
- private static ImmutableArray BuildTags(SemanticModel semanticModel, IEnumerable arguments)
- {
- return arguments
+ private static ImmutableArray BuildTags(SemanticModel semanticModel, IEnumerable arguments) =>
+ arguments
.SelectMany(t => semanticModel.GetConstantValues(t.Expression))
.Select((tag, i) => new MdTag(i, tag))
.ToImmutableArray();
- }
-
+
private static CompositionName CreateCompositionName(string name, string ns, SyntaxNode source)
{
string className;
diff --git a/src/Pure.DI.Core/Core/AsyncDisposableSettings.cs b/src/Pure.DI.Core/Core/AsyncDisposableSettings.cs
index eb0570c48..55b0c0869 100644
--- a/src/Pure.DI.Core/Core/AsyncDisposableSettings.cs
+++ b/src/Pure.DI.Core/Core/AsyncDisposableSettings.cs
@@ -1,4 +1,5 @@
-namespace Pure.DI.Core;
+// ReSharper disable ClassNeverInstantiated.Global
+namespace Pure.DI.Core;
internal class AsyncDisposableSettings(ICache asyncDisposableTypes)
: IAsyncDisposableSettings
diff --git a/src/Pure.DI.Core/Core/BindingBuilder.cs b/src/Pure.DI.Core/Core/BindingBuilder.cs
index d44749e31..a18b8f297 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, i.Tags.ToList())).ToImmutableArray()}).ToImmutableArray(),
- _tags.Select(tag => BuildTag(tag, type, id, _tags)).ToImmutableArray(),
+ _contracts.Select(i => i with { Tags = i.Tags.Select(tag => BuildTag(tag, type, id)).ToImmutableArray()}).ToImmutableArray(),
+ _tags.Select(tag => BuildTag(tag, type, id)).ToImmutableArray(),
_lifetime ?? _defaultLifetime?.Lifetime,
_implementation,
_factory,
@@ -151,7 +151,7 @@ 1 when
}
}
- private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy id, IReadOnlyCollection tags)
+ private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy id)
{
if (type is null || tag.Value is null)
{
diff --git a/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs b/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs
index 1b7b40706..01a72ada7 100644
--- a/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs
+++ b/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs
@@ -26,7 +26,7 @@ public LinesBuilder Build(CompositionCode composition)
lines.AppendLine($"class {composition.Source.Source.Name.ClassName} {{");
using (lines.Indent())
{
- lines.AppendLine($"<>");
+ lines.AppendLine("<>");
foreach (var root in composition.Roots.OrderByDescending(i => i.IsPublic).ThenBy(i => i.Name))
{
lines.AppendLine($"{(root.IsPublic ? "+" : "-")}{FormatRoot(root)}");
@@ -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 TagOnObject ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}";
+ $"{(dependency.Injection.Tag is null or TagOn ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}";
private static string FormatTag(object? tag) =>
- tag is null or TagOnObject
+ tag is null or TagOn
? ""
: EscapeTag(tag) + " ";
diff --git a/src/Pure.DI.Core/Core/Code/CodeExtensions.cs b/src/Pure.DI.Core/Core/Code/CodeExtensions.cs
index 8543e3f1d..999fadbf0 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})",
- TagOnObject => defaultValue,
+ TagOn => defaultValue,
not null => tag.ToString(),
_ => defaultValue
};
diff --git a/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs b/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs
index e15e3c963..fbb0abbd4 100644
--- a/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs
+++ b/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs
@@ -50,7 +50,7 @@ public CompositionCode Build(CompositionCode composition)
code.AppendLine();
}
- AddDisposeAsyncPart(composition, code, false);
+ AddDisposeAsyncPart(code, false);
}
}
@@ -100,7 +100,7 @@ public CompositionCode Build(CompositionCode composition)
{
if (hasAsyncDisposable)
{
- AddDisposeAsyncPart(composition, code, true);
+ AddDisposeAsyncPart(code, true);
}
if (hasDisposable)
@@ -136,7 +136,7 @@ public CompositionCode Build(CompositionCode composition)
return composition with { MembersCount = membersCounter };
}
- private static void AddDisposeAsyncPart(CompositionCode composition, LinesBuilder code, bool makeAsyncCall)
+ private static void AddDisposeAsyncPart(LinesBuilder code, bool makeAsyncCall)
{
code.AppendLine($"case {Names.IAsyncDisposableInterfaceName} asyncDisposableInstance:");
using (code.Indent())
diff --git a/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs b/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs
index 5bd8d7122..edf3e8227 100644
--- a/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs
+++ b/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs
@@ -69,7 +69,6 @@ public void Build(BuildContext ctx, in DpFactory factory)
};
}
- var indent = new Indent(0);
var text = syntaxNode.GetText();
foreach (var textLine in text.Lines)
{
@@ -78,7 +77,7 @@ public void Build(BuildContext ctx, in DpFactory factory)
{
// When an injection marker
var (injection, argument) = resolvers.Current;
- using (code.Indent(indent.Value))
+ using (code.Indent())
{
ctx.StatementBuilder.Build(injectionsCtx with { Level = level, Variable = argument.Current, LockIsRequired = lockIsRequired }, argument);
code.AppendLine($"{(injection.DeclarationRequired ? $"{typeResolver.Resolve(argument.Current.Injection.Type)} " : "")}{injection.VariableName} = {ctx.BuildTools.OnInjected(ctx, argument.Current)};");
@@ -92,7 +91,6 @@ public void Build(BuildContext ctx, in DpFactory factory)
{
}
- indent = len / Formatting.IndentSize;
code.AppendLine(line);
}
}
diff --git a/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs b/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs
index f1683262d..c97e021be 100644
--- a/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs
+++ b/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs
@@ -2,8 +2,6 @@
// ReSharper disable InvertIf
namespace Pure.DI.Core.Code;
-using System.Runtime.CompilerServices;
-
internal sealed class FactoryRewriter(
IArguments arguments,
DpFactory factory,
diff --git a/src/Pure.DI.Core/Core/Code/NodeInfo.cs b/src/Pure.DI.Core/Core/Code/NodeInfo.cs
index 11316a76d..c74bc72b2 100644
--- a/src/Pure.DI.Core/Core/Code/NodeInfo.cs
+++ b/src/Pure.DI.Core/Core/Code/NodeInfo.cs
@@ -1,3 +1,4 @@
+// ReSharper disable ClassNeverInstantiated.Global
namespace Pure.DI.Core.Code;
internal class NodeInfo(IAsyncDisposableSettings asyncDisposableSettings) : INodeInfo
diff --git a/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs b/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs
index 074832e13..af253b4c6 100644
--- a/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs
+++ b/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs
@@ -21,7 +21,7 @@ public CompositionCode Build(CompositionCode composition)
membersCount++;
code.AppendLine($"private const string {Names.OfTypeFieldName} = \"{Names.OfTypeMessage} \";");
membersCount++;
- code.AppendLine("");
+ code.AppendLine();
code.AppendLine($"private class {Names.ResolverClassName}: {Names.ResolverInterfaceName}<{composition.Source.Source.Name.ClassName}, T>");
code.AppendLine("{");
diff --git a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs
index ecfeba0cc..fd7667f84 100644
--- a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs
+++ b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs
@@ -111,12 +111,12 @@ public IEnumerable TryBuild(
var isGenericOk = false;
foreach (var item in map)
{
- if (!Injection.EqualTags(injection.Tag, item.Key.Tag))
+ if (item.Key.Type is not INamedTypeSymbol { IsGenericType: true })
{
continue;
}
-
- if (item.Key.Type is not INamedTypeSymbol { IsGenericType: true })
+
+ if (!Injection.EqualTags(injection.Tag, item.Key.Tag))
{
continue;
}
diff --git a/src/Pure.DI.Core/Core/ExceptionHandler.cs b/src/Pure.DI.Core/Core/ExceptionHandler.cs
index 49b37b961..59eb9bfdf 100644
--- a/src/Pure.DI.Core/Core/ExceptionHandler.cs
+++ b/src/Pure.DI.Core/Core/ExceptionHandler.cs
@@ -1,4 +1,5 @@
-namespace Pure.DI.Core;
+// ReSharper disable ClassNeverInstantiated.Global
+namespace Pure.DI.Core;
internal class ExceptionHandler(ILogger logger)
: IExceptionHandler
diff --git a/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs b/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs
index 36e3cff2c..62e6dc3d5 100644
--- a/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs
+++ b/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs
@@ -138,7 +138,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?)) ?? CreateNameTag(field))));
+ GetAttribute(setup.TagAttributes, field, default(object?)) ?? CreateTagOn(setup.TagOn, field))));
}
}
@@ -157,7 +157,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?)) ?? CreateNameTag(property))));
+ GetAttribute(setup.TagAttributes, property, default(object?)) ?? CreateTagOn(setup.TagOn, property))));
}
}
@@ -251,16 +251,29 @@ private ImmutableArray GetParameters(
parameter,
new Injection(
GetAttribute(setup.TypeAttributes, parameter, typeConstructor?.Construct(compilation, type) ?? type),
- GetAttribute(setup.TagAttributes, parameter, default(object?)) ?? CreateNameTag(parameter))));
+ GetAttribute(setup.TagAttributes, parameter, default(object?)) ?? CreateTagOn(setup.TagOn, parameter))));
}
return dependenciesBuilder.MoveToImmutable();
}
- private object CreateNameTag(ISymbol symbol)
+ private object? CreateTagOn(IReadOnlyCollection tagOn, ISymbol symbol)
{
var memberName = symbol.ContainingSymbol.ToDisplayString(NameTagQualifiedFormat);
- return MdTag.CreateNameTagValue(wildcardMatcher, false, $"{memberName}:{symbol.Name}");
+ var injectionSite = $"{memberName}:{symbol.Name}";
+ var injectionSiteSpan = injectionSite.AsSpan();
+ foreach (var tagOnItem in tagOn)
+ {
+ foreach (var expression in tagOnItem.InjectionSites)
+ {
+ if (wildcardMatcher.Match(expression.AsSpan(), injectionSiteSpan))
+ {
+ return tagOnItem;
+ }
+ }
+ }
+
+ return default;
}
private T GetAttribute(
diff --git a/src/Pure.DI.Core/Core/LinesBuilder.cs b/src/Pure.DI.Core/Core/LinesBuilder.cs
index 203d9baa0..423f2717c 100644
--- a/src/Pure.DI.Core/Core/LinesBuilder.cs
+++ b/src/Pure.DI.Core/Core/LinesBuilder.cs
@@ -55,20 +55,19 @@ public void AppendLine(string line = "")
}
}
- public IDisposable Indent(int value = 1)
+ public IDisposable Indent()
{
-
- IncIndent(value);
- return Disposables.Create(() => DecIndent(value));
+ IncIndent();
+ return Disposables.Create(DecIndent);
}
- public void IncIndent(int value = 1)
+ public void IncIndent()
{
FlushLines();
_indent = new Indent(_indent.Value + 1);
}
- public void DecIndent(int value = 1)
+ public void DecIndent()
{
FlushLines();
_indent = new Indent(_indent.Value - 1);
diff --git a/src/Pure.DI.Core/Core/MetadataBuilder.cs b/src/Pure.DI.Core/Core/MetadataBuilder.cs
index 35f299ae0..28e199fdf 100644
--- a/src/Pure.DI.Core/Core/MetadataBuilder.cs
+++ b/src/Pure.DI.Core/Core/MetadataBuilder.cs
@@ -148,6 +148,15 @@ private void MergeSetups(IEnumerable setups, out MdSetup mergedSetup, b
cancellationToken.ThrowIfCancellationRequested();
}
+ var bindings = bindingsBuilder.ToImmutable();
+
+ var tagOn = bindings
+ .SelectMany(i => i.Contracts)
+ .SelectMany(binding => binding.Tags.Select(i => i.Value).OfType())
+ .Where(i => i.InjectionSites.Length > 0)
+ .Distinct()
+ .ToList();
+
mergedSetup = new MdSetup(
lastSetup?.SemanticModel!,
lastSetup?.Source!,
@@ -155,13 +164,14 @@ private void MergeSetups(IEnumerable setups, out MdSetup mergedSetup, b
usingDirectives.ToImmutableArray(),
kind,
settings,
- bindingsBuilder.ToImmutable(),
+ bindings,
rootsBuilder.ToImmutable(),
resolveDependsOn ? ImmutableArray.Empty : dependsOnBuilder.ToImmutable(),
typeAttributesBuilder.ToImmutable(),
tagAttributesBuilder.ToImmutable(),
ordinalAttributesBuilder.ToImmutable(),
accumulators.ToImmutable(),
+ tagOn,
comments);
}
}
\ No newline at end of file
diff --git a/src/Pure.DI.Core/Core/Models/Injection.cs b/src/Pure.DI.Core/Core/Models/Injection.cs
index 11b11afd9..a50b41277 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 TagOnObject ? $"({Tag.ValueToString()})" : "")}";
+ public override string ToString() => $"{Type}{(Tag != default && Tag is not TagOn ? $"({Tag.ValueToString()})" : "")}";
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Injection other) =>
@@ -19,9 +19,10 @@ public override int GetHashCode() =>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualTags(object? tag, object? otherTag) =>
- ReferenceEquals(tag, MdTag.ContextTag)
- || ReferenceEquals(otherTag, MdTag.ContextTag)
- || (tag is TagOnObject tagOn && tagOn.Equals(otherTag))
- || (otherTag is TagOnObject otherTagOn && otherTagOn.Equals(tag))
+ SpecialEqualTags(tag, otherTag) || SpecialEqualTags(otherTag, tag)
|| Equals(tag, otherTag);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool SpecialEqualTags(object? tag, object? otherTag) =>
+ ReferenceEquals(tag, MdTag.ContextTag) || (tag is TagOn tagOn && tagOn.Equals(otherTag));
}
\ No newline at end of file
diff --git a/src/Pure.DI.Core/Core/Models/MdSetup.cs b/src/Pure.DI.Core/Core/Models/MdSetup.cs
index 4e1b14ec9..f0c0fdc63 100644
--- a/src/Pure.DI.Core/Core/Models/MdSetup.cs
+++ b/src/Pure.DI.Core/Core/Models/MdSetup.cs
@@ -17,5 +17,6 @@ internal record MdSetup(
in ImmutableArray TagAttributes,
in ImmutableArray OrdinalAttributes,
in ImmutableArray Accumulators,
+ IReadOnlyCollection TagOn,
IReadOnlyCollection Comments,
ITypeConstructor? TypeConstructor = default);
\ 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 e11eca52b..47014e040 100644
--- a/src/Pure.DI.Core/Core/Models/MdTag.cs
+++ b/src/Pure.DI.Core/Core/Models/MdTag.cs
@@ -7,21 +7,24 @@ 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 CreateTypeTag(MdTag baseTag, ITypeSymbol? type) =>
+ baseTag with { Value = type };
- public static MdTag CreateUniqueTag(MdTag tag, int id) =>
- tag with { Value = new UniqueTag(id) };
+ public static MdTag CreateUniqueTag(MdTag baseTag, int id) =>
+ baseTag with { Value = CreateUniqueTagValue(id) };
- public static object CreateNameTagValue(IWildcardMatcher wildcardMatcher, bool isMetadata, params string[] names) =>
- new TagOnObject(wildcardMatcher, isMetadata, names);
+ private static object CreateUniqueTagValue(int id) =>
+ new UniqueTag(id);
+
+ public static object CreateTagOnValue(params string[] sites) =>
+ new TagOn(sites);
public override string ToString() =>
Value switch
{
null => "null",
string => $"\"{Value}\"",
- TagOnObject => "",
+ TagOn => "",
_ => Value.ToString()
};
diff --git a/src/Pure.DI.Core/Core/Models/TagOn.cs b/src/Pure.DI.Core/Core/Models/TagOn.cs
new file mode 100644
index 000000000..d0abcbe72
--- /dev/null
+++ b/src/Pure.DI.Core/Core/Models/TagOn.cs
@@ -0,0 +1,17 @@
+// ReSharper disable ConvertIfStatementToReturnStatement
+namespace Pure.DI.Core.Models;
+
+internal record TagOn(params string[] InjectionSites)
+{
+ public override string ToString() => $"TagOn(\"{string.Join(", ", InjectionSites.Select(i => $"{i}"))}\")";
+
+ public virtual bool Equals(TagOn? other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return StructuralComparisons.StructuralEqualityComparer.Equals(this, other.InjectionSites);
+ }
+
+ public override int GetHashCode() =>
+ StructuralComparisons.StructuralEqualityComparer.GetHashCode(InjectionSites);
+}
\ 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
deleted file mode 100644
index b6bf08d89..000000000
--- a/src/Pure.DI.Core/Core/Models/TagOnObject.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-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 680a577da..165f70eee 100644
--- a/src/Pure.DI.Core/Core/SemanticModelExtensions.cs
+++ b/src/Pure.DI.Core/Core/SemanticModelExtensions.cs
@@ -116,15 +116,17 @@ when memberAccessExpressionSyntax.IsKind(SyntaxKind.SimpleMemberAccessExpression
switch (invocationExpressionSyntax.Expression)
{
case MemberAccessExpressionSyntax { Name.Identifier.Text: nameof(Tag.On), Expression: IdentifierNameSyntax { Identifier.Text: nameof(Tag) } }:
- if (invocationExpressionSyntax.ArgumentList.Arguments is { } nameArgs)
+ if (invocationExpressionSyntax.ArgumentList.Arguments is var 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);
+ return (T)MdTag.CreateTagOnValue(names);
}
+
+ // ReSharper disable once HeuristicUnreachableCode
break;
}
diff --git a/src/Pure.DI.Core/Core/ValueStringBuilder.cs b/src/Pure.DI.Core/Core/ValueStringBuilder.cs
deleted file mode 100644
index 1d9aab5a6..000000000
--- a/src/Pure.DI.Core/Core/ValueStringBuilder.cs
+++ /dev/null
@@ -1,311 +0,0 @@
-namespace Pure.DI.Core;
-
-using System.Buffers;
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-internal ref struct ValueStringBuilder
-{
- private char[]? _arrayToReturnToPool;
- private Span _chars;
- private int _pos;
-
- public ValueStringBuilder(Span initialBuffer)
- {
- _arrayToReturnToPool = null;
- _chars = initialBuffer;
- _pos = 0;
- }
-
- public ValueStringBuilder(int initialCapacity)
- {
- _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity);
- _chars = _arrayToReturnToPool;
- _pos = 0;
- }
-
- public int Length
- {
- get => _pos;
- set
- {
- Debug.Assert(value >= 0);
- Debug.Assert(value <= _chars.Length);
- _pos = value;
- }
- }
-
- public int Capacity => _chars.Length;
-
- public void EnsureCapacity(int capacity)
- {
- // This is not expected to be called this with negative capacity
- Debug.Assert(capacity >= 0);
-
- // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception.
- if ((uint)capacity > (uint)_chars.Length)
- Grow(capacity - _pos);
- }
-
- ///
- /// Get a pinnable reference to the builder.
- /// Does not ensure there is a null char after
- /// This overload is pattern matched in the C# 7.3+ compiler so you can omit
- /// the explicit method call, and write eg "fixed (char* c = builder)"
- ///
- public ref char GetPinnableReference()
- {
- return ref MemoryMarshal.GetReference(_chars);
- }
-
- ///
- /// Get a pinnable reference to the builder.
- ///
- /// Ensures that the builder has a null char after
- public ref char GetPinnableReference(bool terminate)
- {
- if (terminate)
- {
- EnsureCapacity(Length + 1);
- _chars[Length] = '\0';
- }
- return ref MemoryMarshal.GetReference(_chars);
- }
-
- public ref char this[int index]
- {
- get
- {
- Debug.Assert(index < _pos);
- return ref _chars[index];
- }
- }
-
- public override string ToString()
- {
- string s = _chars.Slice(0, _pos).ToString();
- Dispose();
- return s;
- }
-
- /// Returns the underlying storage of the builder.
- public Span RawChars => _chars;
-
- ///
- /// Returns a span around the contents of the builder.
- ///
- /// Ensures that the builder has a null char after
- public ReadOnlySpan AsSpan(bool terminate)
- {
- if (terminate)
- {
- EnsureCapacity(Length + 1);
- _chars[Length] = '\0';
- }
- return _chars.Slice(0, _pos);
- }
-
- public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos);
- public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start);
- public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length);
-
- public bool TryCopyTo(Span destination, out int charsWritten)
- {
- if (_chars.Slice(0, _pos).TryCopyTo(destination))
- {
- charsWritten = _pos;
- Dispose();
- return true;
- }
- else
- {
- charsWritten = 0;
- Dispose();
- return false;
- }
- }
-
- public void Insert(int index, char value, int count)
- {
- if (_pos > _chars.Length - count)
- {
- Grow(count);
- }
-
- int remaining = _pos - index;
- _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
- _chars.Slice(index, count).Fill(value);
- _pos += count;
- }
-
- public void Insert(int index, string? s)
- {
- if (s == null)
- {
- return;
- }
-
- int count = s.Length;
-
- if (_pos > (_chars.Length - count))
- {
- Grow(count);
- }
-
- int remaining = _pos - index;
- _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
- s
-#if !NET
- .AsSpan()
-#endif
- .CopyTo(_chars.Slice(index));
- _pos += count;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Append(char c)
- {
- int pos = _pos;
- Span chars = _chars;
- if ((uint)pos < (uint)chars.Length)
- {
- chars[pos] = c;
- _pos = pos + 1;
- }
- else
- {
- GrowAndAppend(c);
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Append(string? s)
- {
- if (s == null)
- {
- return;
- }
-
- int pos = _pos;
- if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
- {
- _chars[pos] = s[0];
- _pos = pos + 1;
- }
- else
- {
- AppendSlow(s);
- }
- }
-
- private void AppendSlow(string s)
- {
- int pos = _pos;
- if (pos > _chars.Length - s.Length)
- {
- Grow(s.Length);
- }
-
- s
-#if !NET
- .AsSpan()
-#endif
- .CopyTo(_chars.Slice(pos));
- _pos += s.Length;
- }
-
- public void Append(char c, int count)
- {
- if (_pos > _chars.Length - count)
- {
- Grow(count);
- }
-
- Span dst = _chars.Slice(_pos, count);
- for (int i = 0; i < dst.Length; i++)
- {
- dst[i] = c;
- }
- _pos += count;
- }
-
- public void Append(scoped ReadOnlySpan value)
- {
- int pos = _pos;
- if (pos > _chars.Length - value.Length)
- {
- Grow(value.Length);
- }
-
- value.CopyTo(_chars.Slice(_pos));
- _pos += value.Length;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Span AppendSpan(int length)
- {
- int origPos = _pos;
- if (origPos > _chars.Length - length)
- {
- Grow(length);
- }
-
- _pos = origPos + length;
- return _chars.Slice(origPos, length);
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private void GrowAndAppend(char c)
- {
- Grow(1);
- Append(c);
- }
-
- ///
- /// Resize the internal buffer either by doubling current buffer size or
- /// by adding to
- /// whichever is greater.
- ///
- ///
- /// Number of chars requested beyond current position.
- ///
- [MethodImpl(MethodImplOptions.NoInlining)]
- private void Grow(int additionalCapacityBeyondPos)
- {
- Debug.Assert(additionalCapacityBeyondPos > 0);
- Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed.");
-
- const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength
-
- // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try
- // to double the size if possible, bounding the doubling to not go beyond the max array length.
- int newCapacity = (int)Math.Max(
- (uint)(_pos + additionalCapacityBeyondPos),
- Math.Min((uint)_chars.Length * 2, ArrayMaxLength));
-
- // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative.
- // This could also go negative if the actual required length wraps around.
- char[] poolArray = ArrayPool.Shared.Rent(newCapacity);
-
- _chars.Slice(0, _pos).CopyTo(poolArray);
-
- char[]? toReturn = _arrayToReturnToPool;
- _chars = _arrayToReturnToPool = poolArray;
- if (toReturn != null)
- {
- ArrayPool.Shared.Return(toReturn);
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Dispose()
- {
- char[]? toReturn = _arrayToReturnToPool;
- this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
- if (toReturn != null)
- {
- ArrayPool.Shared.Return(toReturn);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Pure.DI.Core/Core/WildcardMatcher.cs b/src/Pure.DI.Core/Core/WildcardMatcher.cs
index 71ae87f22..92bca9c48 100644
--- a/src/Pure.DI.Core/Core/WildcardMatcher.cs
+++ b/src/Pure.DI.Core/Core/WildcardMatcher.cs
@@ -225,81 +225,80 @@ public bool Match(
// characters (i.e. the epsilon transition).
goto MatchZero;
}
- else
- {
- // The remaining expression characters all match by consuming a character,
- // so we need to force the expression and state forward.
- currentState += 2;
- switch (useExtendedWildcards)
+ // The remaining expression characters all match by consuming a character,
+ // so we need to force the expression and state forward.
+ currentState += 2;
+
+ switch (useExtendedWildcards)
+ {
+ case true when expressionChar == '>':
{
- case true when expressionChar == '>':
+ // '>' (DOS_QM) is the most complicated. If the name is finished,
+ // we can match zero characters. If this name is a '.', we
+ // don't match, but look at the next expression. Otherwise
+ // we match a single character.
+ if (nameFinished || nameChar == '.')
+ goto NextExpressionCharacter;
+
+ currentMatches[currentMatch++] = currentState;
+ goto ExpressionFinished;
+ }
+
+ case true when expressionChar == '"':
+ {
+ // A '"' (DOS_DOT) can match either a period, or zero characters
+ // beyond the end of name.
+ if (nameFinished)
{
- // '>' (DOS_QM) is the most complicated. If the name is finished,
- // we can match zero characters. If this name is a '.', we
- // don't match, but look at the next expression. Otherwise
- // we match a single character.
- if (nameFinished || nameChar == '.')
- goto NextExpressionCharacter;
+ goto NextExpressionCharacter;
+ }
+ if (nameChar == '.')
+ {
currentMatches[currentMatch++] = currentState;
- goto ExpressionFinished;
}
+ goto ExpressionFinished;
+ }
- case true when expressionChar == '"':
+ default:
+ {
+ if (expressionChar == '\\')
{
- // A '"' (DOS_DOT) can match either a period, or zero characters
- // beyond the end of name.
- if (nameFinished)
- {
- goto NextExpressionCharacter;
- }
- else if (nameChar == '.')
+ // Escape character, try to move the expression forward again and match literally.
+ if (++expressionOffset == expression.Length)
{
- currentMatches[currentMatch++] = currentState;
+ currentMatches[currentMatch++] = maxState;
+ goto ExpressionFinished;
}
- goto ExpressionFinished;
+
+ currentState = expressionOffset * 2 + 2;
+ expressionChar = expression[expressionOffset];
}
-
- default:
- {
- if (expressionChar == '\\')
- {
- // Escape character, try to move the expression forward again and match literally.
- if (++expressionOffset == expression.Length)
- {
- currentMatches[currentMatch++] = maxState;
- goto ExpressionFinished;
- }
-
- currentState = expressionOffset * 2 + 2;
- expressionChar = expression[expressionOffset];
- }
- // From this point on a name character is required to even
- // continue, let alone make a match.
- if (nameFinished)
- {
- goto ExpressionFinished;
- }
+ // From this point on a name character is required to even
+ // continue, let alone make a match.
+ if (nameFinished)
+ {
+ goto ExpressionFinished;
+ }
- if (expressionChar == '?')
+ if (expressionChar == '?')
+ {
+ // If this expression was a '?' we can match it once.
+ currentMatches[currentMatch++] = currentState;
+ }
+ else {
+ if (ignoreCase
+ ? char.ToUpperInvariant(expressionChar) == char.ToUpperInvariant(nameChar)
+ : expressionChar == nameChar)
{
- // If this expression was a '?' we can match it once.
+ // Matched a non-wildcard character
currentMatches[currentMatch++] = currentState;
}
- else {
- if (ignoreCase
- ? char.ToUpperInvariant(expressionChar) == char.ToUpperInvariant(nameChar)
- : expressionChar == nameChar)
- {
- // Matched a non-wildcard character
- currentMatches[currentMatch++] = currentState;
- }
- }
-
- goto ExpressionFinished;
}
+
+ goto ExpressionFinished;
}
}
diff --git a/tests/Pure.DI.IntegrationTests/TagsTests.cs b/tests/Pure.DI.IntegrationTests/TagsTests.cs
index 7f6d632e9..8e70c0fbb 100644
--- a/tests/Pure.DI.IntegrationTests/TagsTests.cs
+++ b/tests/Pure.DI.IntegrationTests/TagsTests.cs
@@ -590,6 +590,55 @@ public static void Main()
result.StdOut.ShouldBe(["Sample.Service"], result);
}
+ [Fact]
+ public async Task ShouldSupportTagOnWhenDecorator()
+ {
+ // Given
+
+ // When
+ var result = await """
+namespace Sample
+{
+ using System;
+ using Pure.DI;
+ using Sample;
+
+ internal interface IService { }
+
+ internal class BaseService: IService
+ {
+ }
+
+ internal class Service: IService
+ {
+ public Service(IService baseService) { }
+ }
+
+ internal partial class Composition
+ {
+ void Setup() =>
+ DI.Setup("Composition")
+ .Bind(Tag.On("*.Service:baseService")).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 ShouldSupportTagOnWhenGenericInCtor()
{
@@ -945,5 +994,100 @@ public static void Main()
result.Success.ShouldBeTrue(result);
result.StdOut.ShouldBe(["Sample.Service", "Sample.Dep"], result);
}
+
+ [Fact]
+ public async Task ShouldSupportTagOnForComplex()
+ {
+ // Given
+
+ // When
+ var result = await """
+namespace Sample
+{
+ using System;
+ using Pure.DI;
+ using Sample;
+
+ interface IDependency { };
+
+ class AbcDependency : IDependency { };
+
+ class XyzDependency : IDependency { };
+
+ class Consumer
+ {
+ public Consumer(IDependency myDep)
+ {
+ Dependency = myDep;
+ }
+
+ public IDependency Dependency { get; }
+ }
+
+ interface IService
+ {
+ IDependency Dependency1 { get; }
+
+ IDependency Dependency2 { get; }
+
+ IDependency Dependency3 { get; }
+
+ IDependency Dependency4 { get; }
+ }
+
+ class Service : IService
+ {
+ private readonly Consumer _consumer;
+
+ public Service(IDependency dependency1,
+ IDependency dependency2,
+ Consumer consumer)
+ {
+ _consumer = consumer;
+ Dependency1 = dependency1;
+ Dependency2 = dependency2;
+ }
+
+ public IDependency Dependency1 { get; }
+
+ public IDependency Dependency2 { get; }
+
+ public required IDependency Dependency3 { init; get; }
+
+ public IDependency Dependency4 => _consumer.Dependency;
+ }
+
+ internal partial class Composition
+ {
+ void Setup() =>
+ DI.Setup("Composition")
+ .Bind(Tag.On("*Service:Dependency3", "*Consumer:myDep"))
+ .To()
+ .Bind(Tag.On("*Service:dependency?"))
+ .To()
+ .Bind().To>()
+ .Bind().To()
+ .Root("Root");
+ }
+
+ public class Program
+ {
+ public static void Main()
+ {
+ var composition = new Composition();
+ var service = composition.Root;
+ Console.WriteLine(service.Dependency1);
+ Console.WriteLine(service.Dependency2);
+ Console.WriteLine(service.Dependency3);
+ Console.WriteLine(service.Dependency4);
+ }
+ }
+}
+""".RunAsync(new Options(LanguageVersion.CSharp11));
+
+ // Then
+ result.Success.ShouldBeTrue(result);
+ result.StdOut.ShouldBe(["Sample.XyzDependency", "Sample.XyzDependency", "Sample.AbcDependency", "Sample.AbcDependency"], result);
+ }
#endif
}
\ No newline at end of file
diff --git a/tests/Pure.DI.Tests/InjectionTests.cs b/tests/Pure.DI.Tests/InjectionTests.cs
deleted file mode 100644
index a051f007b..000000000
--- a/tests/Pure.DI.Tests/InjectionTests.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-namespace Pure.DI.Tests;
-
-using System.Diagnostics.CodeAnalysis;
-using Core;
-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();
- private static readonly WildcardMatcherStub WildcardMatcher = new();
-
- [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 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 [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
- {
- [SuppressMessage("Performance", "CA1822:Mark members as static")]
- public bool Match(
- ReadOnlySpan expression,
- ReadOnlySpan name,
- bool ignoreCase = false,
- bool useExtendedWildcards = false) =>
- (expression.Length == 1 && expression[0] == '*') || expression.SequenceEqual(name);
- }
-}
\ No newline at end of file
diff --git a/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs
index 7e22b05bd..a7142ae23 100644
--- a/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs
+++ b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs
@@ -2,7 +2,7 @@
$v=true
$p=5
$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=Sometimes it is necessary to determine which binding will be used to inject explicitly. To do this, use a special tag created by calling the `Tag.On()` method. 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=- `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
@@ -85,7 +85,7 @@ public void Run()
Tag.On("MyNamespace.Consumer`1.Consumer:myDep"))
.To()
.Bind(
- // Combined tag on injection sites
+ // Combined tag
Tag.On(
"MyNamespace.Service.Service:dependency2",
"MyNamespace.Service:Dependency3"))
diff --git a/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs
index 6dccde808..36a571ad5 100644
--- a/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs
+++ b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs
@@ -26,7 +26,7 @@ namespace MyNamespace2;
interface IDependency;
class AbcDependency : IDependency;
-
+
class XyzDependency : IDependency;
class Consumer(IDependency myDep)
diff --git a/tests/Pure.DI.UsageTests/Attributes/CustomAttributesScenario.cs b/tests/Pure.DI.UsageTests/Attributes/CustomAttributesScenario.cs
index a01bd4b79..70baf218e 100644
--- a/tests/Pure.DI.UsageTests/Attributes/CustomAttributesScenario.cs
+++ b/tests/Pure.DI.UsageTests/Attributes/CustomAttributesScenario.cs
@@ -15,6 +15,7 @@
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable ArrangeTypeModifiers
// ReSharper disable MemberCanBePrivate.Global
+// ReSharper disable UnusedTypeParameter
#pragma warning disable CS9113 // Parameter is unread.
namespace Pure.DI.UsageTests.Attributes.CustomAttributesScenario;
diff --git a/tests/Pure.DI.UsageTests/Interception/AdvancedInterceptionScenario.cs b/tests/Pure.DI.UsageTests/Interception/AdvancedInterceptionScenario.cs
index 6f4aede63..45d323844 100644
--- a/tests/Pure.DI.UsageTests/Interception/AdvancedInterceptionScenario.cs
+++ b/tests/Pure.DI.UsageTests/Interception/AdvancedInterceptionScenario.cs
@@ -45,7 +45,7 @@ public void ServiceCall() { }
internal partial class Composition: IInterceptor
{
- private readonly List _log = new();
+ private readonly List _log = [];
private static readonly IProxyBuilder ProxyBuilder = new DefaultProxyBuilder();
private readonly IInterceptor[] _interceptors = [];