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 27a5b35
Show file tree
Hide file tree
Showing 35 changed files with 875 additions and 216 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
4 changes: 2 additions & 2 deletions readme/auto-scoped.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ var service2 = program.CreateService();
service1.Dependency.ShouldNotBe(service2.Dependency);
```

>[!IMPORTANT]
>The method `Inject()`cannot be used outside of the binding setup.
> [!IMPORTANT]
> The method `Inject()`cannot be used outside of the binding setup.
The following partial class will be generated:

Expand Down
4 changes: 2 additions & 2 deletions readme/factory.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ service.Dependency.IsInitialized.ShouldBeTrue();
```

This approach is more expensive to maintain, but allows you to create objects more flexibly by passing them some state and introducing dependencies. As in the case of automatic dependency injecting, objects give up control on embedding, and the whole process takes place when the object graph is created.
>[!IMPORTANT]
>The method `Inject()`cannot be used outside of the binding setup.
> [!IMPORTANT]
> The method `Inject()`cannot be used outside of the binding setup.
The following partial class will be generated:

Expand Down
8 changes: 4 additions & 4 deletions readme/generic-async-composition-roots-with-constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Generics/GenericAsyncCompositionRootsWithConstraintsScenario.cs)

>[!IMPORTANT]
>`Resolve' methods cannot be used to resolve generic composition roots.
> [!IMPORTANT]
> `Resolve' methods cannot be used to resolve generic composition roots.

```c#
Expand Down Expand Up @@ -59,8 +59,8 @@ var service = await composition.GetMyRootAsync<Stream, double>(CancellationToken
var someOtherService = await composition.GetOtherServiceAsync<BinaryReader>(CancellationToken.None);
```

>[!IMPORTANT]
>The method `Inject()`cannot be used outside of the binding setup.
> [!IMPORTANT]
> The method `Inject()`cannot be used outside of the binding setup.
The following partial class will be generated:

Expand Down
8 changes: 4 additions & 4 deletions readme/generic-composition-roots-with-constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Generics/GenericCompositionRootsWithConstraintsScenario.cs)

>[!IMPORTANT]
>`Resolve' methods cannot be used to resolve generic composition roots.
> [!IMPORTANT]
> `Resolve' methods cannot be used to resolve generic composition roots.

```c#
Expand Down Expand Up @@ -57,8 +57,8 @@ var service = composition.GetMyRoot<Stream, double>();
var someOtherService = composition.GetOtherService<BinaryReader>();
```

>[!IMPORTANT]
>The method `Inject()`cannot be used outside of the binding setup.
> [!IMPORTANT]
> The method `Inject()`cannot be used outside of the binding setup.
The following partial class will be generated:

Expand Down
8 changes: 4 additions & 4 deletions readme/generic-composition-roots.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Generics/GenericsCompositionRootsScenario.cs)

Sometimes you want to be able to create composition roots with type parameters. In this case, the composition root can only be represented by a method.
>[!IMPORTANT]
>`Resolve()' methods cannot be used to resolve generic composition roots.
> [!IMPORTANT]
> `Resolve()' methods cannot be used to resolve generic composition roots.

```c#
Expand Down Expand Up @@ -51,8 +51,8 @@ var service = composition.GetMyRoot<int>();
var someOtherService = composition.GetOtherService<string>();
```

>[!IMPORTANT]
>The method `Inject()`cannot be used outside of the binding setup.
> [!IMPORTANT]
> The method `Inject()`cannot be used outside of the binding setup.
The following partial class will be generated:

Expand Down
4 changes: 2 additions & 2 deletions readme/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Generics/GenericsScenario.cs)

Generic types are also supported.
>[!IMPORTANT]
>Instead of open generic types, as in classical DI container libraries, regular generic types with _marker_ types as type parameters are used here. Such "marker" types allow to define dependency graph more precisely.
> [!IMPORTANT]
> Instead of open generic types, as in classical DI container libraries, regular generic types with _marker_ types as type parameters are used here. Such "marker" types allow to define dependency graph more precisely.
For the case of `IDependency<TT>`, `TT` is a _marker_ type, which allows the usual `IDependency<TT>` to be used instead of an open generic type like `IDependency<>`. This makes it easy to bind generic types by specifying _marker_ types such as `TT`, `TT1`, etc. as parameters of generic types:

Expand Down
2 changes: 1 addition & 1 deletion readme/injections-of-abstractions.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ root.Run();

Usually the biggest block in the setup is the chain of bindings, which describes which implementation corresponds to which abstraction. This is necessary so that the code generator can build a composition of objects using only NOT abstract types. This is true because the cornerstone of DI technology implementation is the principle of abstraction-based programming rather than concrete class-based programming. Thanks to it, it is possible to replace one concrete implementation by another. And each implementation can correspond to an arbitrary number of abstractions.
> [!TIP]
>Even if the binding is not defined, there is no problem with the injection, but obviously under the condition that the consumer requests an injection NOT of abstract type.
> Even if the binding is not defined, there is no problem with the injection, but obviously under the condition that the consumer requests an injection NOT of abstract type.

The following partial class will be generated:
Expand Down
4 changes: 2 additions & 2 deletions readme/manually-started-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ var service = composition.GetRoot(cancellationTokenSource.Token);
await service.RunAsync(cancellationTokenSource.Token);
```

>[!IMPORTANT]
>The method `Inject()`cannot be used outside of the binding setup.
> [!IMPORTANT]
> The method `Inject()`cannot be used outside of the binding setup.
The following partial class will be generated:

Expand Down
111 changes: 111 additions & 0 deletions readme/name-tags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#### 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_

All names are case-sensitive. The global namespace prefix `global::` must be omitted.

>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:

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

0 comments on commit 27a5b35

Please sign in to comment.