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 089f4c6
Show file tree
Hide file tree
Showing 23 changed files with 215 additions and 176 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 Name](readme/tag-name.md)
- [Tag Name with wildcards](readme/tag-name-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,6 +1,8 @@
#### Name tag with wildcards
#### Tag Name 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/TagOnWithWildcardsScenario.cs)

The wildcards ‘*’ and ‘?’ are supported. You can also combine multiple tags in a single `Tag.On()` call.


```c#
Expand Down Expand Up @@ -42,9 +44,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 @@ -61,42 +63,4 @@ service.Dependency4.ShouldBeOfType<AbcDependency>();
```


Class diagram:

```mermaid
classDiagram
class Composition {
<<partial>>
+IService Root
}
AbcDependency --|> IDependency : "*Service﹕dependency1"
AbcDependency --|> IDependency : "*Consumer﹕myDep"
class AbcDependency {
+AbcDependency()
}
XyzDependency --|> IDependency : "*Service﹕dependency2"
XyzDependency --|> IDependency : "*Service﹕Dependency3"
class XyzDependency {
+XyzDependency()
}
Service --|> IService
class Service {
+Service(IDependency dependency1, IDependency dependency2, ConsumerᐸStringᐳ consumer)
+IDependency Dependency3
}
class ConsumerᐸStringᐳ {
+Consumer(IDependency myDep)
}
class IDependency {
<<interface>>
}
class IService {
<<interface>>
}
Composition ..> Service : IService Root
Service *-- AbcDependency : IDependency
Service *-- "2 " XyzDependency : IDependency
Service *-- ConsumerᐸStringᐳ : ConsumerᐸStringᐳ
ConsumerᐸStringᐳ *-- AbcDependency : IDependency
```

56 changes: 10 additions & 46 deletions readme/name-tags.md → readme/tag-name.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#### Name tags
#### Tag Name

[![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/TagOnScenario.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 name contains a string in the special format: the full name of the type member and the name of the target element to be injected, separated by ":". For example for the _MyNamespace_ namespace and the type _Class1_:

- `MyNamespace.Class1.Class1:state1 - the tag corresponds to the constructor argument named _state_ of type _MyNamespace.Class1_
- `MyNamespace.Class1.DoSomething:myArg` - the tag corresponds to the _myArg_ argument of the _DoSomething_ method
- `MyNamespace.Class1.MyData` - the tag corresponds to property or field _MyData_

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:

Expand Down Expand Up @@ -53,11 +53,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 All @@ -77,42 +79,4 @@ service.Dependency4.ShouldBeOfType<AbcDependency>();
> Although this approach can be useful for specifying exactly what to inject, it can be more expensive to maintain, so it is recommended to use attributes like `[Tag(...)]`.

Class diagram:

```mermaid
classDiagram
class Composition {
<<partial>>
+IService Root
}
AbcDependency --|> IDependency : "MyNamespace.Service.Service﹕dependency1"
AbcDependency --|> IDependency : "MyNamespace.Consumer`1.Consumer﹕myDep"
class AbcDependency {
+AbcDependency()
}
XyzDependency --|> IDependency : "MyNamespace.Service.Service﹕dependency2"
XyzDependency --|> IDependency : "MyNamespace.Service﹕Dependency3"
class XyzDependency {
+XyzDependency()
}
Service --|> IService
class Service {
+Service(IDependency dependency1, IDependency dependency2, ConsumerᐸStringᐳ consumer)
+IDependency Dependency3
}
class ConsumerᐸStringᐳ {
+Consumer(IDependency myDep)
}
class IDependency {
<<interface>>
}
class IService {
<<interface>>
}
Composition ..> Service : IService Root
Service *-- AbcDependency : IDependency
Service *-- "2 " XyzDependency : IDependency
Service *-- ConsumerᐸStringᐳ : ConsumerᐸStringᐳ
ConsumerᐸStringᐳ *-- AbcDependency : IDependency
```

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;
}
Loading

0 comments on commit 089f4c6

Please sign in to comment.