-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
46cee16
commit 5f0aaa9
Showing
32 changed files
with
553 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
#### Smart tags | ||
|
||
[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Basics/SmartTagsScenario.cs) | ||
|
||
When you need to compose a large composition of objects, you may need a large number of tags. Strings or other constant values are not always convenient to use, because there can be infinitely many variants of numbers or strings. And if you specify one value in the binding, you can make a mistake and specify another value in the dependency, which will lead to a compilation error. The solution to this problem is to create an enumerable type and use its values as tags. _Pure.DI_ makes it easier to solve this problem. | ||
|
||
When you specify a tag in a binding and the compiler can't determine what that value is, _Pure.DI_ will automatically create a constant for it inside the `Pure.DI.Tag` type. For the example below, the set of constants would look like this: | ||
|
||
```c# | ||
namespace Pure.DI | ||
{ | ||
internal partial class Tag | ||
{ | ||
public const string Abc = "Abc"; | ||
public const string Xyz = "Xyz"; | ||
} | ||
} | ||
``` | ||
In this way you can apply refactoring in the development environment. And also changes of tags in bindings will be automatically checked by the compiler. This will reduce the number of errors. | ||
The example below also uses the `using static Pure.DI.Tag;` directive to access tags in `Pure.DI.Tag` without specifying a type name: | ||
|
||
|
||
```c# | ||
using static Pure.DI.Tag; | ||
using static Pure.DI.Lifetime; | ||
|
||
interface IDependency; | ||
|
||
class AbcDependency : IDependency; | ||
|
||
class XyzDependency : IDependency; | ||
|
||
class Dependency : IDependency; | ||
|
||
interface IService | ||
{ | ||
IDependency Dependency1 { get; } | ||
|
||
IDependency Dependency2 { get; } | ||
|
||
IDependency Dependency3 { get; } | ||
} | ||
|
||
class Service( | ||
[Tag(Abc)] IDependency dependency1, | ||
[Tag(Xyz)] IDependency dependency2, | ||
IDependency dependency3) | ||
: IService | ||
{ | ||
public IDependency Dependency1 { get; } = dependency1; | ||
|
||
public IDependency Dependency2 { get; } = dependency2; | ||
|
||
public IDependency Dependency3 { get; } = dependency3; | ||
} | ||
|
||
DI.Setup(nameof(Composition)) | ||
// The `default` tag is used to resolve dependencies | ||
// when the tag was not specified by the consumer | ||
.Bind<IDependency>(Abc, default).To<AbcDependency>() | ||
.Bind<IDependency>(Xyz).As(Singleton).To<XyzDependency>() | ||
.Bind<IService>().To<Service>() | ||
|
||
// "XyzRoot" is root name, Xyz is tag | ||
.Root<IDependency>("XyzRoot", Xyz) | ||
|
||
// 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.Dependency2.ShouldBe(composition.XyzRoot); | ||
service.Dependency3.ShouldBeOfType<AbcDependency>(); | ||
``` | ||
|
||
The following partial class will be generated: | ||
|
||
```c# | ||
partial class Composition | ||
{ | ||
private readonly Composition _root; | ||
private readonly Lock _lock; | ||
|
||
private XyzDependency? _singletonXyzDependency44; | ||
|
||
[OrdinalAttribute(256)] | ||
public Composition() | ||
{ | ||
_root = this; | ||
_lock = new Lock(); | ||
} | ||
|
||
internal Composition(Composition parentScope) | ||
{ | ||
_root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root; | ||
_lock = _root._lock; | ||
} | ||
|
||
public IDependency XyzRoot | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
get | ||
{ | ||
if (_root._singletonXyzDependency44 is null) | ||
{ | ||
using (_lock.EnterScope()) | ||
{ | ||
if (_root._singletonXyzDependency44 is null) | ||
{ | ||
_root._singletonXyzDependency44 = new XyzDependency(); | ||
} | ||
} | ||
} | ||
|
||
return _root._singletonXyzDependency44!; | ||
} | ||
} | ||
|
||
public IService Root | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
get | ||
{ | ||
if (_root._singletonXyzDependency44 is null) | ||
{ | ||
using (_lock.EnterScope()) | ||
{ | ||
if (_root._singletonXyzDependency44 is null) | ||
{ | ||
_root._singletonXyzDependency44 = new XyzDependency(); | ||
} | ||
} | ||
} | ||
|
||
return new Service(new AbcDependency(), _root._singletonXyzDependency44!, new AbcDependency()); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Class diagram: | ||
|
||
```mermaid | ||
--- | ||
config: | ||
class: | ||
hideEmptyMembersBox: true | ||
--- | ||
classDiagram | ||
Service --|> IService | ||
XyzDependency --|> IDependency : "Xyz" | ||
AbcDependency --|> IDependency : "Abc" | ||
AbcDependency --|> IDependency | ||
Composition ..> Service : IService Root | ||
Composition ..> XyzDependency : IDependency XyzRoot | ||
Service *-- AbcDependency : "Abc" IDependency | ||
Service o-- "Singleton" XyzDependency : "Xyz" IDependency | ||
Service *-- AbcDependency : IDependency | ||
namespace Pure.DI.UsageTests.Basics.SmartTagsScenario { | ||
class AbcDependency { | ||
+AbcDependency() | ||
} | ||
class Composition { | ||
<<partial>> | ||
+IService Root | ||
+IDependency XyzRoot | ||
} | ||
class IDependency { | ||
<<interface>> | ||
} | ||
class IService { | ||
<<interface>> | ||
} | ||
class Service { | ||
+Service(IDependency dependency1, IDependency dependency2, IDependency dependency3) | ||
} | ||
class XyzDependency { | ||
+XyzDependency() | ||
} | ||
} | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
namespace Pure.DI.Core.Code; | ||
|
||
internal class TagClassBuilder( | ||
IInformation information, | ||
ISmartTags smartTags, | ||
IFormatter formatter, | ||
IComments comments, | ||
CancellationToken cancellationToken) | ||
: IBuilder<TagContext, TagCode> | ||
{ | ||
public TagCode Build(TagContext tagContext) | ||
{ | ||
var tagToDependencies = ( | ||
from composition in tagContext.Compositions | ||
from dependency in composition.Source.Graph.Edges | ||
where dependency.Injection.Tag is string | ||
group (composition, dependency) by (string)dependency.Injection.Tag! | ||
).ToDictionary(i => i.Key, i => i.ToList()); | ||
|
||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
var code = new LinesBuilder(); | ||
var tags = smartTags.GetAll(); | ||
// ReSharper disable once InvertIf | ||
code.AppendLine("// <auto-generated/>"); | ||
code.AppendLine($"// by {information.Description}"); | ||
|
||
code.AppendLine($"namespace {Names.GeneratorName}"); | ||
code.AppendLine("{"); | ||
using (code.Indent()) | ||
{ | ||
code.AppendLine($"internal partial class {nameof(Tag)}"); | ||
code.AppendLine("{"); | ||
using (code.Indent()) | ||
{ | ||
var isFirst = true; | ||
foreach (var tag in tags) | ||
{ | ||
if (isFirst) | ||
{ | ||
isFirst = false; | ||
} | ||
else | ||
{ | ||
code.AppendLine(); | ||
} | ||
|
||
code.AppendLine("/// <summary>"); | ||
code.AppendLine($"/// Atomically generated smart tag with value {comments.Escape(tag.Name.ValueToString())}."); | ||
if (tagToDependencies.TryGetValue(tag.Name, out var dependencies)) | ||
{ | ||
code.AppendLine("/// Used by:"); | ||
code.AppendLine("/// <br/>"); | ||
var groupByComposition = dependencies.GroupBy(i => i.composition.Source.Source.Name.FullName); | ||
foreach (var compositionGroup in groupByComposition) | ||
{ | ||
code.AppendLine("/// <br/>"); | ||
code.AppendLine($"/// class {formatter.FormatRef(compositionGroup.Key)}"); | ||
code.AppendLine("/// <list type=\"bullet\">"); | ||
foreach (var (_, (_, dependencyNode, injection, target)) in compositionGroup | ||
.OrderBy(i => i.dependency.Target.Binding.Id) | ||
.ThenBy(i => i.dependency.Source.Binding.Id)) | ||
{ | ||
var tagStr = comments.Escape(injection.Tag != null && injection.Tag is not MdTagOnSites ? $"({injection.Tag})" : ""); | ||
code.AppendLine($"/// <item>{formatter.FormatRef(target.Type)} <-- {formatter.FormatRef(injection.Type)}{tagStr} -- {formatter.FormatRef(dependencyNode.Type)} </item>"); | ||
} | ||
|
||
code.AppendLine("/// </list>"); | ||
} | ||
|
||
|
||
} | ||
|
||
code.AppendLine("/// </summary>"); | ||
code.AppendLine($"public const string {tag.Name} = {tag.Name.ValueToString()};"); | ||
} | ||
} | ||
|
||
code.AppendLine("}"); | ||
} | ||
|
||
code.AppendLine("}"); | ||
|
||
return new TagCode(code); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
namespace Pure.DI.Core.Code; | ||
|
||
internal record TagContext( | ||
IReadOnlyCollection<CompositionCode> Compositions); |
Oops, something went wrong.