Skip to content

Commit

Permalink
A special tag that allows injection by name
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolayPianikov committed Jun 8, 2024
1 parent d45da68 commit a9ff0e1
Show file tree
Hide file tree
Showing 19 changed files with 831 additions and 174 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ 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)
- [Partial class](readme/partial-class.md)
- [Dependent compositions](readme/dependent-compositions.md)
Expand Down
20 changes: 10 additions & 10 deletions readme/ArrayDetails.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ classDiagram
class IService4 {
<<interface>>
}
CompositionRoot *-- Service1 : IService1
CompositionRoot *-- "3 " Service2Array : IService2
CompositionRoot *-- Service3 : IService3
CompositionRoot *-- "2 " Service4 : IService4
CompositionRoot *-- Service1 : IService1
CompositionRoot *-- "3 " Service2Array : IService2
CompositionRoot *-- Service3 : IService3
CompositionRoot *-- "2 " Service4 : IService4
Array ..> CompositionRoot : CompositionRoot TestPureDIByCR()
Service1 *-- Service2Array : IService2
Service2Array *-- ArrayᐸIService3ᐳ : ArrayᐸIService3ᐳ
Service3 *-- "2 " Service4 : IService4
Service3v2 *-- "2 " Service4 : IService4
Service3v3 *-- "2 " Service4 : IService4
Service3v4 *-- "2 " Service4 : IService4
Service1 *-- Service2Array : IService2
Service2Array *-- ArrayᐸIService3ᐳ : ArrayᐸIService3ᐳ
Service3 *-- "2 " Service4 : IService4
Service3v2 *-- "2 " Service4 : IService4
Service3v3 *-- "2 " Service4 : IService4
Service3v4 *-- "2 " Service4 : IService4
ArrayᐸIService3ᐳ *-- Service3 : IService3
ArrayᐸIService3ᐳ *-- Service3v2 : 2 IService3
ArrayᐸIService3ᐳ *-- Service3v3 : 3 IService3
Expand Down
20 changes: 10 additions & 10 deletions readme/EnumDetails.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ classDiagram
class IService4 {
<<interface>>
}
CompositionRoot *-- Service1 : IService1
CompositionRoot *-- "3 " Service2Enum : IService2
CompositionRoot *-- Service3 : IService3
CompositionRoot *-- "2 " Service4 : IService4
CompositionRoot *-- Service1 : IService1
CompositionRoot *-- "3 " Service2Enum : IService2
CompositionRoot *-- Service3 : IService3
CompositionRoot *-- "2 " Service4 : IService4
Enum ..> CompositionRoot : CompositionRoot TestPureDIByCR()
Service1 *-- Service2Enum : IService2
Service2Enum o-- "PerBlock" IEnumerableᐸIService3ᐳ : IEnumerableᐸIService3ᐳ
Service3 *-- "2 " Service4 : IService4
Service3v2 *-- "2 " Service4 : IService4
Service3v3 *-- "2 " Service4 : IService4
Service3v4 *-- "2 " Service4 : IService4
Service1 *-- Service2Enum : IService2
Service2Enum o-- "PerBlock" IEnumerableᐸIService3ᐳ : IEnumerableᐸIService3ᐳ
Service3 *-- "2 " Service4 : IService4
Service3v2 *-- "2 " Service4 : IService4
Service3v3 *-- "2 " Service4 : IService4
Service3v4 *-- "2 " Service4 : IService4
IEnumerableᐸIService3ᐳ *-- Service3 : IService3
IEnumerableᐸIService3ᐳ *-- Service3v2 : 2 IService3
IEnumerableᐸIService3ᐳ *-- Service3v3 : 3 IService3
Expand Down
16 changes: 8 additions & 8 deletions readme/FuncDetails.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ classDiagram
class IService4 {
<<interface>>
}
CompositionRoot *-- Service1 : IService1
CompositionRoot *-- "3 " Service2Func : IService2
CompositionRoot *-- Service3 : IService3
CompositionRoot *-- "2 " Service4 : IService4
CompositionRoot *-- Service1 : IService1
CompositionRoot *-- "3 " Service2Func : IService2
CompositionRoot *-- Service3 : IService3
CompositionRoot *-- "2 " Service4 : IService4
Func ..> CompositionRoot : CompositionRoot TestPureDIByCR()
Service1 *-- Service2Func : IService2
Service2Func o-- "PerBlock" FuncᐸIService3ᐳ : FuncᐸIService3ᐳ
Service3 *-- "2 " Service4 : IService4
FuncᐸIService3ᐳ *-- Service3 : IService3
Service1 *-- Service2Func : IService2
Service2Func o-- "PerBlock" FuncᐸIService3ᐳ : FuncᐸIService3ᐳ
Service3 *-- "2 " Service4 : IService4
FuncᐸIService3ᐳ *-- Service3 : IService3
```

### Generated code
Expand Down
14 changes: 7 additions & 7 deletions readme/SingletonDetails.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ classDiagram
class IService4 {
<<interface>>
}
CompositionRoot o-- "Scoped" Service1 : IService1
CompositionRoot *-- "3 " Service2 : IService2
CompositionRoot *-- Service3 : IService3
CompositionRoot o-- "2 Scoped" Service4 : IService4
CompositionRoot o-- "Scoped" Service1 : IService1
CompositionRoot *-- "3 " Service2 : IService2
CompositionRoot *-- Service3 : IService3
CompositionRoot o-- "2 Scoped" Service4 : IService4
Singleton ..> CompositionRoot : CompositionRoot TestPureDIByCR()
Service1 *-- Service2 : IService2
Service2 *-- "5 " Service3 : IService3
Service3 o-- "2 Scoped" Service4 : IService4
Service1 *-- Service2 : IService2
Service2 *-- "5 " Service3 : IService3
Service3 o-- "2 Scoped" Service4 : IService4
```

### Generated code
Expand Down
14 changes: 7 additions & 7 deletions readme/TransientDetails.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ classDiagram
class IService4 {
<<interface>>
}
CompositionRoot *-- Service1 : IService1
CompositionRoot *-- "3 " Service2 : IService2
CompositionRoot *-- Service3 : IService3
CompositionRoot *-- "2 " Service4 : IService4
CompositionRoot *-- Service1 : IService1
CompositionRoot *-- "3 " Service2 : IService2
CompositionRoot *-- Service3 : IService3
CompositionRoot *-- "2 " Service4 : IService4
Transient ..> CompositionRoot : CompositionRoot TestPureDIByCR()
Service1 *-- Service2 : IService2
Service2 *-- "5 " Service3 : IService3
Service3 *-- "2 " Service4 : IService4
Service1 *-- Service2 : IService2
Service2 *-- "5 " Service3 : IService3
Service3 *-- "2 " Service4 : IService4
```

### Generated code
Expand Down
110 changes: 110 additions & 0 deletions readme/name-tags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#### Name tags

[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/NameTagsScenario.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_:

- `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_

>[!NOTE]
>For generic types, the name also contains the number of type parameters, for example, for `IDictionary<TKey, TValue>` the type name would be `IDictionary`2`.

```c#
interface IDependency;

class AbcDependency : IDependency;

class XyzDependency : IDependency;

class Consumer<T>(IDependency myDep)
{
public IDependency Dependency { get; } = myDep;
}

interface IService
{
IDependency Dependency1 { get; }

IDependency Dependency2 { get; }

IDependency Dependency3 { get; }

IDependency Dependency4 { get; }
}

class Service(
IDependency dependency1,
IDependency dependency2,
Consumer<string> consumer)
: IService
{
public IDependency Dependency1 { get; } = dependency1;

public IDependency Dependency2 { get; } = dependency2;

public required IDependency Dependency3 { init; get; }

public IDependency Dependency4 => consumer.Dependency;
}

DI.Setup(nameof(Composition))
.Bind(
"MyNamespace.Service.Service:dependency1",
"MyNamespace.Consumer`1.Consumer:myDep")
.To<AbcDependency>()
.Bind("MyNamespace.Service.Service:dependency2",
"MyNamespace.Service:Dependency3")
.To<XyzDependency>()
.Bind().To<Consumer<TT>>()
.Bind<IService>().To<Service>()

// Specifies to create the composition root named "Root"
.Root<IService>("Root");

var composition = new Composition();
var service = composition.Root;
service.Dependency1.ShouldBeOfType<AbcDependency>();
service.Dependency2.ShouldBeOfType<XyzDependency>();
service.Dependency3.ShouldBeOfType<XyzDependency>();
service.Dependency4.ShouldBeOfType<AbcDependency>();
```

>[!WARNING]
>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 : "Pure.DI.UsageTests.Basics.NameTagsScenario.Service.Service:dependency1"
AbcDependency --|> IDependency
class AbcDependency {
+AbcDependency()
}
XyzDependency --|> IDependency : "Pure.DI.UsageTests.Basics.NameTagsScenario.Service.Service:dependency2"
class XyzDependency {
+XyzDependency()
}
Service --|> IService
class Service {
+Service(IDependency dependency1, IDependency dependency2, IDependency dependency3)
}
class IDependency {
<<interface>>
}
class IService {
<<interface>>
}
Composition ..> Service : IService Root
Service *-- "2 " AbcDependency : IDependency
Service o-- "Singleton" XyzDependency : IDependency
```

10 changes: 5 additions & 5 deletions src/Pure.DI.Core/Core/BindingBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ 1 when
source,
setup,
semanticModel,
_contracts.Select(i => i with { Tags = i.Tags.Select(tag => BuildTag(tag, type, id)).ToImmutableArray()}).ToImmutableArray(),
_tags.Select(tag => BuildTag(tag, type, id)).ToImmutableArray(),
_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(),
_lifetime ?? _defaultLifetime?.Lifetime,
_implementation,
_factory,
Expand All @@ -151,7 +151,7 @@ 1 when
}
}

private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy<int> id)
private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy<int> id, IReadOnlyCollection<MdTag> tags)
{
if (type is null || tag.Value is null)
{
Expand All @@ -164,10 +164,10 @@ private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy<int> id)
switch (tagVal)
{
case Tag.Type:
return tag with { Value = type };
return MdTag.CreateTypeTag(tag, type);

case Tag.Unique:
return tag with { Value = new UniqueTag(id.Value) };
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 == default ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}";
$"{(dependency.Injection.Tag is null or MdTag.NameTagObject ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}";

private static string FormatTag(object? tag) =>
tag != default ? $"{tag.ValueToString("").Replace("\"", "\\\"")} " : "";
tag is null or MdTag.NameTagObject ? "" : $"{tag.ValueToString("").Replace("\"", "\\\"")} ";

private static string FormatTags(IEnumerable<object?> tags) =>
string.Join(", ", tags.Distinct().Select(FormatTag).OrderBy(i => i));
Expand Down
1 change: 1 addition & 0 deletions src/Pure.DI.Core/Core/Code/CodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static string ValueToString(this object? tag, string defaultValue = "null
char ch => $"'{ch.ToString()}'",
Enum en => $"{en.GetType()}.{en}",
ITypeSymbol => $"typeof({tag})",
MdTag.NameTagObject => defaultValue,
not null => tag.ToString(),
_ => defaultValue
};
Expand Down
42 changes: 38 additions & 4 deletions src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,41 @@
// ReSharper disable ClassNeverInstantiated.Global
namespace Pure.DI.Core;

using ITypeSymbol = Microsoft.CodeAnalysis.ITypeSymbol;
using System.Reflection;

internal sealed class ImplementationDependencyNodeBuilder(
ILogger<ImplementationDependencyNodeBuilder> logger,
IBuilder<DpImplementation, IEnumerable<DpImplementation>> implementationVariantsBuilder)
: IBuilder<MdSetup, IEnumerable<DependencyNode>>
{
private static readonly SymbolDisplayFormat NameTagQualifiedFormat;

static ImplementationDependencyNodeBuilder()
{
NameTagQualifiedFormat = new SymbolDisplayFormat(
genericsOptions:
SymbolDisplayGenericsOptions.IncludeTypeParameters,
typeQualificationStyle:
SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
memberOptions:
SymbolDisplayMemberOptions.IncludeType |
SymbolDisplayMemberOptions.IncludeContainingType,
miscellaneousOptions:
SymbolDisplayMiscellaneousOptions.UseSpecialTypes
| SymbolDisplayMiscellaneousOptions.CollapseTupleTypes
);

var qualifiedNameArityFormat = NameTagQualifiedFormat.GetType().GetField("QualifiedNameArityFormat", BindingFlags.Static | BindingFlags.NonPublic);
var format = (SymbolDisplayFormat?)qualifiedNameArityFormat?.GetValue(null)
?? new SymbolDisplayFormat(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);

NameTagQualifiedFormat =
format
.WithGenericsOptions(SymbolDisplayGenericsOptions.IncludeTypeParameters)
.WithMemberOptions(SymbolDisplayMemberOptions.IncludeType | SymbolDisplayMemberOptions.IncludeContainingType)
.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.CollapseTupleTypes);
}

public IEnumerable<DependencyNode> Build(MdSetup setup)
{
var injectionsWalker = new DependenciesToInjectionsCountWalker();
Expand Down Expand Up @@ -109,7 +137,7 @@ public IEnumerable<DependencyNode> Build(MdSetup setup)
ordinal,
new Injection(
GetAttribute(setup.TypeAttributes, field, setup.TypeConstructor?.Construct(compilation, type) ?? type),
GetAttribute(setup.TagAttributes, field, default(object?)))));
GetAttribute(setup.TagAttributes, field, default(object?)) ?? CreateNameTag(field))));
}
}

Expand All @@ -128,7 +156,7 @@ public IEnumerable<DependencyNode> Build(MdSetup setup)
ordinal,
new Injection(
GetAttribute(setup.TypeAttributes, property, setup.TypeConstructor?.Construct(compilation, type) ?? type),
GetAttribute(setup.TagAttributes, property, default(object?)))));
GetAttribute(setup.TagAttributes, property, default(object?)) ?? CreateNameTag(property))));
}
}

Expand Down Expand Up @@ -222,11 +250,17 @@ private ImmutableArray<DpParameter> GetParameters(
parameter,
new Injection(
GetAttribute(setup.TypeAttributes, parameter, typeConstructor?.Construct(compilation, type) ?? type),
GetAttribute(setup.TagAttributes, parameter, default(object?)))));
GetAttribute(setup.TagAttributes, parameter, default(object?)) ?? CreateNameTag(parameter))));
}

return dependenciesBuilder.MoveToImmutable();
}

private static object CreateNameTag(ISymbol symbol)
{
var memberName = symbol.ContainingSymbol.ToDisplayString(NameTagQualifiedFormat);
return MdTag.CreateNameTagValue($"{memberName}:{symbol.Name}");
}

private T GetAttribute<TMdAttribute, T>(
in ImmutableArray<TMdAttribute> attributeMetadata,
Expand Down
Loading

0 comments on commit a9ff0e1

Please sign in to comment.