Skip to content

Commit

Permalink
API Tag.On for special tag that allows injection by name
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolayPianikov committed Jun 10, 2024
1 parent c41bb8d commit 963cecb
Show file tree
Hide file tree
Showing 23 changed files with 253 additions and 127 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -42,9 +47,9 @@ class Service(
}

DI.Setup(nameof(Composition))
.Bind("*Service:dependency1", "*Consumer:myDep")
.Bind(Tag.On("*Service:dependency1", "*Consumer:myDep"))
.To<AbcDependency>()
.Bind("*Service:dependency2", "*Service:Dependency3")
.Bind(Tag.On("*Service:dependency2", "*Service:Dependency3"))
.To<XyzDependency>()
.Bind().To<Consumer<TT>>()
.Bind<IService>().To<Service>()
Expand All @@ -69,13 +74,11 @@ classDiagram
<<partial>>
+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()
}
Expand Down
36 changes: 20 additions & 16 deletions readme/name-tags.md → readme/tag-on-injection-site.md
Original file line number Diff line number Diff line change
@@ -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("<namespace>.<type>.<member>[: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<TKey, TValue>` 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<T>` class, the tag on the injection site would be ``MyNamespace.Consumer`1.Consumer:myDep``:


```c#
namespace MyNamespace;


interface IDependency;

class AbcDependency : IDependency;
Expand Down Expand Up @@ -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<AbcDependency>()
.Bind("MyNamespace.Service.Service:dependency2",
"MyNamespace.Service:Dependency3")
.Bind(
Tag.On(
"MyNamespace.Service.Service:dependency2",
"MyNamespace.Service:Dependency3"))
.To<XyzDependency>()
.Bind().To<Consumer<TT>>()
.Bind<IService>().To<Service>()
Expand Down Expand Up @@ -85,13 +90,12 @@ classDiagram
<<partial>>
+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()
}
Expand Down
10 changes: 6 additions & 4 deletions src/Pure.DI.Core/Components/Api.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,7 @@ internal enum RootKinds
/// <summary>
/// Represents well known tags.
/// </summary>
internal enum Tag
internal class Tag
{
/// <summary>
/// Unique tag.
Expand All @@ -1134,8 +1134,8 @@ internal enum Tag
/// </code>
/// </example>
/// </summary>
Unique,
public static readonly Tag Unique = new Tag();

/// <summary>
/// Tag of target implementation type.
/// <example>
Expand All @@ -1146,7 +1146,9 @@ internal enum Tag
/// </code>
/// </example>
/// </summary>
Type
public static readonly Tag Type = new Tag();

public static Tag On(params string[] names) => new Tag();
}

/// <summary>
Expand Down
13 changes: 7 additions & 6 deletions src/Pure.DI.Core/Core/BindingBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,14 @@ private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy<int> 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);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) + " ";

Expand Down
2 changes: 1 addition & 1 deletion src/Pure.DI.Core/Core/Code/CodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ private ImmutableArray<DpParameter> 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<TMdAttribute, T>(
Expand Down
6 changes: 3 additions & 3 deletions src/Pure.DI.Core/Core/Models/Injection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand All @@ -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);
}
6 changes: 3 additions & 3 deletions src/Pure.DI.Core/Core/Models/MdTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
};

Expand Down
20 changes: 0 additions & 20 deletions src/Pure.DI.Core/Core/Models/NameTagObject.cs

This file was deleted.

34 changes: 34 additions & 0 deletions src/Pure.DI.Core/Core/Models/TagOnObject.cs
Original file line number Diff line number Diff line change
@@ -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;
}
38 changes: 31 additions & 7 deletions src/Pure.DI.Core/Core/SemanticModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,37 +75,61 @@ 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<CompositionKind>(enumValueStr, out var compositionKindValue))
if (Enum.TryParse<CompositionKind>(valueStr, out var compositionKindValue))
{
return (T)(object)compositionKindValue;
}

break;

case nameof(Lifetime):
if (Enum.TryParse<Lifetime>(enumValueStr, out var lifetimeValue))
if (Enum.TryParse<Lifetime>(valueStr, out var lifetimeValue))
{
return (T)(object)lifetimeValue;
}

break;

case nameof(Tag):
if (Enum.TryParse<Tag>(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<string>(semanticModel, nameArg.Expression))
.Where(i => !string.IsNullOrWhiteSpace(i)).OfType<string>()
.ToArray();

return (T)MdTag.CreateNameTagValue(new WildcardMatcher(), true, names);
}
break;
}

break;
}
}

var optionalValue = semanticModel.GetConstantValue(node);
Expand All @@ -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);
}
}
Loading

0 comments on commit 963cecb

Please sign in to comment.