diff --git a/README.md b/README.md index 85ee8e3e..af784090 100644 --- a/README.md +++ b/README.md @@ -274,9 +274,12 @@ dotnet run ### Hints - [Resolve hint](readme/resolve-hint.md) - [ThreadSafe hint](readme/threadsafe-hint.md) -- [OnDependencyInjection hint](readme/ondependencyinjection-hint.md) -- [OnCannotResolve hint](readme/oncannotresolve-hint.md) -- [OnNewInstance hint](readme/onnewinstance-hint.md) +- [OnDependencyInjection regular expression hint](readme/ondependencyinjection-regular-expression-hint.md) +- [OnDependencyInjection wildcard hint](readme/ondependencyinjection-wildcard-hint.md) +- [OnCannotResolve regular expression hint](readme/oncannotresolve-regular-expression-hint.md) +- [OnCannotResolve wildcard hint](readme/oncannotresolve-wildcard-hint.md) +- [OnNewInstance regular expression hint](readme/onnewinstance-regular-expression-hint.md) +- [OnNewInstance wildcard hint](readme/onnewinstance-wildcard-hint.md) - [ToString hint](readme/tostring-hint.md) - [Check for a root](readme/check-for-a-root.md) ### Advanced @@ -590,19 +593,29 @@ DI.Setup("Composition") | [OnNewInstance](#onnewinstance-hint) | _On_ or _Off_ | 9.0 | _Off_ | | [OnNewInstancePartial](#onnewinstance-hint) | _On_ or _Off_ | | _On_ | | [OnNewInstanceImplementationTypeNameRegularExpression](#onnewinstanceimplementationtypenameregularexpression-hint) | Regular expression | | .+ | +| [OnNewInstanceImplementationTypeNameWildcard](#onnewinstanceimplementationtypenamewildcard-hint) | Wildcard | | * | | [OnNewInstanceTagRegularExpression](#onnewinstancetagregularexpression-hint) | Regular expression | | .+ | +| [OnNewInstanceTagWildcard](#onnewinstancetagwildcard-hint) | Wildcard | | * | | [OnNewInstanceLifetimeRegularExpression](#onnewinstancelifetimeregularexpression-hint) | Regular expression | | .+ | +| [OnNewInstanceLifetimeWildcard](#onnewinstancelifetimewildcard-hint) | Wildcard | | * | | [OnDependencyInjection](#ondependencyinjection-hint) | _On_ or _Off_ | 9.0 | _Off_ | | [OnDependencyInjectionPartial](#ondependencyinjectionpartial-hint) | _On_ or _Off_ | | _On_ | | [OnDependencyInjectionImplementationTypeNameRegularExpression](#OnDependencyInjectionImplementationTypeNameRegularExpression-Hint) | Regular expression | | .+ | +| [OnDependencyInjectionImplementationTypeNameWildcard](#OnDependencyInjectionImplementationTypeNameWildcard-Hint) | Wildcard | | * | | [OnDependencyInjectionContractTypeNameRegularExpression](#ondependencyinjectioncontracttypenameregularexpression-hint) | Regular expression | | .+ | +| [OnDependencyInjectionContractTypeNameWildcard](#ondependencyinjectioncontracttypenameWildcard-hint) | Wildcard | | * | | [OnDependencyInjectionTagRegularExpression](#ondependencyinjectiontagregularexpression-hint) | Regular expression | | .+ | +| [OnDependencyInjectionTagWildcard](#ondependencyinjectiontagWildcard-hint) | Wildcard | | * | | [OnDependencyInjectionLifetimeRegularExpression](#ondependencyinjectionlifetimeregularexpression-hint) | Regular expression | | .+ | +| [OnDependencyInjectionLifetimeWildcard](#ondependencyinjectionlifetimeWildcard-hint) | Wildcard | | * | | [OnCannotResolve](#oncannotresolve-hint) | _On_ or _Off_ | 9.0 | _Off_ | | [OnCannotResolvePartial](#oncannotresolvepartial-hint) | _On_ or _Off_ | | _On_ | | [OnCannotResolveContractTypeNameRegularExpression](#oncannotresolvecontracttypenameregularexpression-hint) | Regular expression | | .+ | +| [OnCannotResolveContractTypeNameWildcard](#oncannotresolvecontracttypenameцildcard-hint) | Wildcard | | * | | [OnCannotResolveTagRegularExpression](#oncannotresolvetagregularexpression-hint) | Regular expression | | .+ | +| [OnCannotResolveTagWildcard](#oncannotresolvetagWildcard-hint) | Wildcard | | * | | [OnCannotResolveLifetimeRegularExpression](#oncannotresolvelifetimeregularexpression-hint) | Regular expression | | .+ | +| [OnCannotResolveLifetimeWildcard](#oncannotresolvelifetimeWildcard-hint) | Wildcard | | * | | [OnNewRoot](#onnewroot-hint) | _On_ or _Off_ | | _Off_ | | [OnNewRootPartial](#onnewrootpartial-hint) | _On_ or _Off_ | | _On_ | | [ToString](#tostring-hint) | _On_ or _Off_ | | _Off_ | @@ -650,14 +663,26 @@ Determines whether to generate the _OnNewInstance_ partial method. By default, t This is a regular expression for filtering by instance type name. This hint is useful when _OnNewInstance_ is in _On_ state and it is necessary to limit the set of types for which the _OnNewInstance_ method will be called. +### OnNewInstanceImplementationTypeNameWildcard Hint + +This is a Wildcard for filtering by instance type name. This hint is useful when _OnNewInstance_ is in _On_ state and it is necessary to limit the set of types for which the _OnNewInstance_ method will be called. + ### OnNewInstanceTagRegularExpression Hint This is a regular expression for filtering by _tag_. This hint is also useful when _OnNewInstance_ is in _On_ state and it is necessary to limit the set of _tags_ for which the _OnNewInstance_ method will be called. +### OnNewInstanceTagWildcard Hint + +This is a wildcard for filtering by _tag_. This hint is also useful when _OnNewInstance_ is in _On_ state and it is necessary to limit the set of _tags_ for which the _OnNewInstance_ method will be called. + ### OnNewInstanceLifetimeRegularExpression Hint This is a regular expression for filtering by _lifetime_. This hint is also useful when _OnNewInstance_ is in _On_ state and it is necessary to restrict the set of _life_ times for which the _OnNewInstance_ method will be called. +### OnNewInstanceLifetimeWildcard Hint + +This is a wildcard for filtering by _lifetime_. This hint is also useful when _OnNewInstance_ is in _On_ state and it is necessary to restrict the set of _life_ times for which the _OnNewInstance_ method will be called. + ### OnDependencyInjection Hint Determines whether to use the _OnDependencyInjection_ partial method when the _OnDependencyInjection_ hint is ```On``` to control dependency injection. By default it is ```On```. @@ -689,18 +714,34 @@ To minimize performance loss when calling _OnDependencyInjection_, use the three This is a regular expression for filtering by instance type name. This hint is useful when _OnDependencyInjection_ is in _On_ state and it is necessary to restrict the set of types for which the _OnDependencyInjection_ method will be called. +### OnDependencyInjectionImplementationTypeNameWildcard Hint + +This is a wildcard for filtering by instance type name. This hint is useful when _OnDependencyInjection_ is in _On_ state and it is necessary to restrict the set of types for which the _OnDependencyInjection_ method will be called. + ### OnDependencyInjectionContractTypeNameRegularExpression Hint This is a regular expression for filtering by the name of the resolving type. This hint is also useful when _OnDependencyInjection_ is in _On_ state and it is necessary to limit the set of permissive types for which the _OnDependencyInjection_ method will be called. +### OnDependencyInjectionContractTypeNameWildcard Hint + +This is a wildcard for filtering by the name of the resolving type. This hint is also useful when _OnDependencyInjection_ is in _On_ state and it is necessary to limit the set of permissive types for which the _OnDependencyInjection_ method will be called. + ### OnDependencyInjectionTagRegularExpression Hint This is a regular expression for filtering by _tag_. This hint is also useful when _OnDependencyInjection_ is in the _On_ state and you want to limit the set of _tags_ for which the _OnDependencyInjection_ method will be called. +### OnDependencyInjectionTagWildcard Hint + +This is a wildcard for filtering by _tag_. This hint is also useful when _OnDependencyInjection_ is in the _On_ state and you want to limit the set of _tags_ for which the _OnDependencyInjection_ method will be called. + ### OnDependencyInjectionLifetimeRegularExpression Hint This is a regular expression for filtering by _lifetime_. This hint is also useful when _OnDependencyInjection_ is in _On_ state and it is necessary to restrict the set of _lifetime_ for which the _OnDependencyInjection_ method will be called. +### OnDependencyInjectionLifetimeWildcard Hint + +This is a wildcard for filtering by _lifetime_. This hint is also useful when _OnDependencyInjection_ is in _On_ state and it is necessary to restrict the set of _lifetime_ for which the _OnDependencyInjection_ method will be called. + ### OnCannotResolve Hint Determines whether to use the `OnCannotResolve(...)` partial method to handle a scenario in which an instance cannot be resolved. By default, this partial method is not generated. Because of the return value, it cannot have an empty body and must be overridden at creation. @@ -756,14 +797,26 @@ DI.Setup("Composition") This is a regular expression for filtering by the name of the resolving type. This hint is also useful when _OnCannotResolve_ is in _On_ state and it is necessary to limit the set of resolving types for which the _OnCannotResolve_ method will be called. +### OnCannotResolveContractTypeNameWildcard Hint + +This is a wildcard for filtering by the name of the resolving type. This hint is also useful when _OnCannotResolve_ is in _On_ state and it is necessary to limit the set of resolving types for which the _OnCannotResolve_ method will be called. + ### OnCannotResolveTagRegularExpression Hint This is a regular expression for filtering by _tag_. This hint is also useful when _OnCannotResolve_ is in _On_ state and it is necessary to limit the set of _tags_ for which the _OnCannotResolve_ method will be called. +### OnCannotResolveTagWildcard Hint + +This is a wildcard for filtering by _tag_. This hint is also useful when _OnCannotResolve_ is in _On_ state and it is necessary to limit the set of _tags_ for which the _OnCannotResolve_ method will be called. + ### OnCannotResolveLifetimeRegularExpression Hint This is a regular expression for filtering by _lifetime_. This hint is also useful when _OnCannotResolve_ is in the _On_ state and it is necessary to restrict the set of _lives_ for which the _OnCannotResolve_ method will be called. +### OnCannotResolveLifetimeWildcard Hint + +This is a wildcard for filtering by _lifetime_. This hint is also useful when _OnCannotResolve_ is in the _On_ state and it is necessary to restrict the set of _lives_ for which the _OnCannotResolve_ method will be called. + ### ToString Hint Determines whether to generate the _ToString()_ method. This method provides a class diagram in [mermaid](https://mermaid.js.org/) format. To see this diagram, just call the ToString method and copy the text to [this site](https://mermaid.live/). diff --git a/readme/FooterTemplate.md b/readme/FooterTemplate.md index e6faf339..bf51f8f1 100644 --- a/readme/FooterTemplate.md +++ b/readme/FooterTemplate.md @@ -282,19 +282,29 @@ DI.Setup("Composition") | [OnNewInstance](#onnewinstance-hint) | _On_ or _Off_ | 9.0 | _Off_ | | [OnNewInstancePartial](#onnewinstance-hint) | _On_ or _Off_ | | _On_ | | [OnNewInstanceImplementationTypeNameRegularExpression](#onnewinstanceimplementationtypenameregularexpression-hint) | Regular expression | | .+ | +| [OnNewInstanceImplementationTypeNameWildcard](#onnewinstanceimplementationtypenamewildcard-hint) | Wildcard | | * | | [OnNewInstanceTagRegularExpression](#onnewinstancetagregularexpression-hint) | Regular expression | | .+ | +| [OnNewInstanceTagWildcard](#onnewinstancetagwildcard-hint) | Wildcard | | * | | [OnNewInstanceLifetimeRegularExpression](#onnewinstancelifetimeregularexpression-hint) | Regular expression | | .+ | +| [OnNewInstanceLifetimeWildcard](#onnewinstancelifetimewildcard-hint) | Wildcard | | * | | [OnDependencyInjection](#ondependencyinjection-hint) | _On_ or _Off_ | 9.0 | _Off_ | | [OnDependencyInjectionPartial](#ondependencyinjectionpartial-hint) | _On_ or _Off_ | | _On_ | | [OnDependencyInjectionImplementationTypeNameRegularExpression](#OnDependencyInjectionImplementationTypeNameRegularExpression-Hint) | Regular expression | | .+ | +| [OnDependencyInjectionImplementationTypeNameWildcard](#OnDependencyInjectionImplementationTypeNameWildcard-Hint) | Wildcard | | * | | [OnDependencyInjectionContractTypeNameRegularExpression](#ondependencyinjectioncontracttypenameregularexpression-hint) | Regular expression | | .+ | +| [OnDependencyInjectionContractTypeNameWildcard](#ondependencyinjectioncontracttypenameWildcard-hint) | Wildcard | | * | | [OnDependencyInjectionTagRegularExpression](#ondependencyinjectiontagregularexpression-hint) | Regular expression | | .+ | +| [OnDependencyInjectionTagWildcard](#ondependencyinjectiontagWildcard-hint) | Wildcard | | * | | [OnDependencyInjectionLifetimeRegularExpression](#ondependencyinjectionlifetimeregularexpression-hint) | Regular expression | | .+ | +| [OnDependencyInjectionLifetimeWildcard](#ondependencyinjectionlifetimeWildcard-hint) | Wildcard | | * | | [OnCannotResolve](#oncannotresolve-hint) | _On_ or _Off_ | 9.0 | _Off_ | | [OnCannotResolvePartial](#oncannotresolvepartial-hint) | _On_ or _Off_ | | _On_ | | [OnCannotResolveContractTypeNameRegularExpression](#oncannotresolvecontracttypenameregularexpression-hint) | Regular expression | | .+ | +| [OnCannotResolveContractTypeNameWildcard](#oncannotresolvecontracttypenameцildcard-hint) | Wildcard | | * | | [OnCannotResolveTagRegularExpression](#oncannotresolvetagregularexpression-hint) | Regular expression | | .+ | +| [OnCannotResolveTagWildcard](#oncannotresolvetagWildcard-hint) | Wildcard | | * | | [OnCannotResolveLifetimeRegularExpression](#oncannotresolvelifetimeregularexpression-hint) | Regular expression | | .+ | +| [OnCannotResolveLifetimeWildcard](#oncannotresolvelifetimeWildcard-hint) | Wildcard | | * | | [OnNewRoot](#onnewroot-hint) | _On_ or _Off_ | | _Off_ | | [OnNewRootPartial](#onnewrootpartial-hint) | _On_ or _Off_ | | _On_ | | [ToString](#tostring-hint) | _On_ or _Off_ | | _Off_ | @@ -342,14 +352,26 @@ Determines whether to generate the _OnNewInstance_ partial method. By default, t This is a regular expression for filtering by instance type name. This hint is useful when _OnNewInstance_ is in _On_ state and it is necessary to limit the set of types for which the _OnNewInstance_ method will be called. +### OnNewInstanceImplementationTypeNameWildcard Hint + +This is a Wildcard for filtering by instance type name. This hint is useful when _OnNewInstance_ is in _On_ state and it is necessary to limit the set of types for which the _OnNewInstance_ method will be called. + ### OnNewInstanceTagRegularExpression Hint This is a regular expression for filtering by _tag_. This hint is also useful when _OnNewInstance_ is in _On_ state and it is necessary to limit the set of _tags_ for which the _OnNewInstance_ method will be called. +### OnNewInstanceTagWildcard Hint + +This is a wildcard for filtering by _tag_. This hint is also useful when _OnNewInstance_ is in _On_ state and it is necessary to limit the set of _tags_ for which the _OnNewInstance_ method will be called. + ### OnNewInstanceLifetimeRegularExpression Hint This is a regular expression for filtering by _lifetime_. This hint is also useful when _OnNewInstance_ is in _On_ state and it is necessary to restrict the set of _life_ times for which the _OnNewInstance_ method will be called. +### OnNewInstanceLifetimeWildcard Hint + +This is a wildcard for filtering by _lifetime_. This hint is also useful when _OnNewInstance_ is in _On_ state and it is necessary to restrict the set of _life_ times for which the _OnNewInstance_ method will be called. + ### OnDependencyInjection Hint Determines whether to use the _OnDependencyInjection_ partial method when the _OnDependencyInjection_ hint is ```On``` to control dependency injection. By default it is ```On```. @@ -381,18 +403,34 @@ To minimize performance loss when calling _OnDependencyInjection_, use the three This is a regular expression for filtering by instance type name. This hint is useful when _OnDependencyInjection_ is in _On_ state and it is necessary to restrict the set of types for which the _OnDependencyInjection_ method will be called. +### OnDependencyInjectionImplementationTypeNameWildcard Hint + +This is a wildcard for filtering by instance type name. This hint is useful when _OnDependencyInjection_ is in _On_ state and it is necessary to restrict the set of types for which the _OnDependencyInjection_ method will be called. + ### OnDependencyInjectionContractTypeNameRegularExpression Hint This is a regular expression for filtering by the name of the resolving type. This hint is also useful when _OnDependencyInjection_ is in _On_ state and it is necessary to limit the set of permissive types for which the _OnDependencyInjection_ method will be called. +### OnDependencyInjectionContractTypeNameWildcard Hint + +This is a wildcard for filtering by the name of the resolving type. This hint is also useful when _OnDependencyInjection_ is in _On_ state and it is necessary to limit the set of permissive types for which the _OnDependencyInjection_ method will be called. + ### OnDependencyInjectionTagRegularExpression Hint This is a regular expression for filtering by _tag_. This hint is also useful when _OnDependencyInjection_ is in the _On_ state and you want to limit the set of _tags_ for which the _OnDependencyInjection_ method will be called. +### OnDependencyInjectionTagWildcard Hint + +This is a wildcard for filtering by _tag_. This hint is also useful when _OnDependencyInjection_ is in the _On_ state and you want to limit the set of _tags_ for which the _OnDependencyInjection_ method will be called. + ### OnDependencyInjectionLifetimeRegularExpression Hint This is a regular expression for filtering by _lifetime_. This hint is also useful when _OnDependencyInjection_ is in _On_ state and it is necessary to restrict the set of _lifetime_ for which the _OnDependencyInjection_ method will be called. +### OnDependencyInjectionLifetimeWildcard Hint + +This is a wildcard for filtering by _lifetime_. This hint is also useful when _OnDependencyInjection_ is in _On_ state and it is necessary to restrict the set of _lifetime_ for which the _OnDependencyInjection_ method will be called. + ### OnCannotResolve Hint Determines whether to use the `OnCannotResolve(...)` partial method to handle a scenario in which an instance cannot be resolved. By default, this partial method is not generated. Because of the return value, it cannot have an empty body and must be overridden at creation. @@ -448,14 +486,26 @@ DI.Setup("Composition") This is a regular expression for filtering by the name of the resolving type. This hint is also useful when _OnCannotResolve_ is in _On_ state and it is necessary to limit the set of resolving types for which the _OnCannotResolve_ method will be called. +### OnCannotResolveContractTypeNameWildcard Hint + +This is a wildcard for filtering by the name of the resolving type. This hint is also useful when _OnCannotResolve_ is in _On_ state and it is necessary to limit the set of resolving types for which the _OnCannotResolve_ method will be called. + ### OnCannotResolveTagRegularExpression Hint This is a regular expression for filtering by _tag_. This hint is also useful when _OnCannotResolve_ is in _On_ state and it is necessary to limit the set of _tags_ for which the _OnCannotResolve_ method will be called. +### OnCannotResolveTagWildcard Hint + +This is a wildcard for filtering by _tag_. This hint is also useful when _OnCannotResolve_ is in _On_ state and it is necessary to limit the set of _tags_ for which the _OnCannotResolve_ method will be called. + ### OnCannotResolveLifetimeRegularExpression Hint This is a regular expression for filtering by _lifetime_. This hint is also useful when _OnCannotResolve_ is in the _On_ state and it is necessary to restrict the set of _lives_ for which the _OnCannotResolve_ method will be called. +### OnCannotResolveLifetimeWildcard Hint + +This is a wildcard for filtering by _lifetime_. This hint is also useful when _OnCannotResolve_ is in the _On_ state and it is necessary to restrict the set of _lives_ for which the _OnCannotResolve_ method will be called. + ### ToString Hint Determines whether to generate the _ToString()_ method. This method provides a class diagram in [mermaid](https://mermaid.js.org/) format. To see this diagram, just call the ToString method and copy the text to [this site](https://mermaid.live/). diff --git a/readme/oncannotresolve-hint.md b/readme/oncannotresolve-regular-expression-hint.md similarity index 94% rename from readme/oncannotresolve-hint.md rename to readme/oncannotresolve-regular-expression-hint.md index 6bf452fc..d78d7ac1 100644 --- a/readme/oncannotresolve-hint.md +++ b/readme/oncannotresolve-regular-expression-hint.md @@ -1,6 +1,6 @@ -#### OnCannotResolve hint +#### OnCannotResolve regular expression hint -[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Hints/OnCannotResolveHintScenario.cs) +[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Hints/OnCannotResolveRegularExpressionHintScenario.cs) Hints are used to fine-tune code generation. The _OnCannotResolve_ hint determines whether to generate a partial `OnCannotResolve(...)` method to handle a scenario where an instance which cannot be resolved. In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnCannotResolveContractTypeNameRegularExpression = string`. @@ -133,7 +133,7 @@ classDiagram Composition ..> Service : IService Root Service *-- Dependency : IDependency Dependency *-- String : String - namespace Pure.DI.UsageTests.Hints.OnCannotResolveHintScenario { + namespace Pure.DI.UsageTests.Hints.OnCannotResolveRegularExpressionHintScenario { class Composition { <> +IService Root diff --git a/readme/oncannotresolve-wildcard-hint.md b/readme/oncannotresolve-wildcard-hint.md new file mode 100644 index 00000000..f64dbfac --- /dev/null +++ b/readme/oncannotresolve-wildcard-hint.md @@ -0,0 +1,159 @@ +#### OnCannotResolve wildcard hint + +[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Hints/OnCannotResolveWildcardHintScenario.cs) + +Hints are used to fine-tune code generation. The _OnCannotResolve_ hint determines whether to generate a partial `OnCannotResolve(...)` method to handle a scenario where an instance which cannot be resolved. +In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnCannotResolveContractTypeNameWildcard = string`. + + +```c# +using Shouldly; +using Pure.DI; +using static Pure.DI.Hint; + +// OnCannotResolveContractTypeNameWildcard = string +DI.Setup(nameof(Composition)) + .Hint(OnCannotResolve, "On") + .Bind().To() + .Bind().To() + .Root("Root"); + +var composition = new Composition(); +var service = composition.Root; +service.Dependency.ToString().ShouldBe("My name"); + + +interface IDependency; + +class Dependency(string name) : IDependency +{ + public override string ToString() => name; +} + +interface IService +{ + IDependency Dependency { get; } +} + +class Service(IDependency dependency) : IService +{ + public IDependency Dependency { get; } = dependency; +} + +partial class Composition +{ + private partial T OnCannotResolve( + object? tag, + Lifetime lifetime) + { + if (typeof(T) == typeof(string)) + { + return (T)(object)"My name"; + } + + throw new InvalidOperationException("Cannot resolve."); + } +} +``` + +
+Running this code sample locally + +- Make sure you have the [.NET SDK 9.0](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) or later is installed +```bash +dotnet --list-sdk +``` +- Create a net9.0 (or later) console application +```bash +dotnet new console -n Sample +``` +- Add references to NuGet packages + - [Pure.DI](https://www.nuget.org/packages/Pure.DI) + - [Shouldly](https://www.nuget.org/packages/Shouldly) +```bash +dotnet add package Pure.DI +dotnet add package Shouldly +``` +- Copy the example code into the _Program.cs_ file + +You are ready to run the example 🚀 +```bash +dotnet run +``` + +
+ +The `OnCannotResolveContractTypeNameWildcard` hint helps define the set of types that require manual dependency resolution. You can use it to specify a wildcard to filter the full type name. +For more hints, see [this](README.md#setup-hints) page. + +The following partial class will be generated: + +```c# +partial class Composition +{ + private readonly Composition _root; + + [OrdinalAttribute(256)] + public Composition() + { + _root = this; + } + + internal Composition(Composition parentScope) + { + _root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root; + } + + public IService Root + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + string transientString2 = OnCannotResolve(null, Lifetime.Transient); + return new Service(new Dependency(transientString2)); + } + } + + + private partial T OnCannotResolve(object? tag, Lifetime lifetime); +} +``` + +Class diagram: + +```mermaid +--- + config: + class: + hideEmptyMembersBox: true +--- +classDiagram + Service --|> IService + Dependency --|> IDependency + Composition ..> Service : IService Root + Service *-- Dependency : IDependency + Dependency *-- String : String + namespace Pure.DI.UsageTests.Hints.OnCannotResolveWildcardHintScenario { + class Composition { + <> + +IService Root + } + class Dependency { + +Dependency(String name) + } + class IDependency { + <> + } + class IService { + <> + } + class Service { + +Service(IDependency dependency) + } + } + namespace System { + class String { + } + } +``` + diff --git a/readme/ondependencyinjection-hint.md b/readme/ondependencyinjection-regular-expression-hint.md similarity index 94% rename from readme/ondependencyinjection-hint.md rename to readme/ondependencyinjection-regular-expression-hint.md index a1359d5b..89e3801f 100644 --- a/readme/ondependencyinjection-hint.md +++ b/readme/ondependencyinjection-regular-expression-hint.md @@ -1,6 +1,6 @@ -#### OnDependencyInjection hint +#### OnDependencyInjection regular expression hint -[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionHintScenario.cs) +[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionRegularExpressionHintScenario.cs) Hints are used to fine-tune code generation. The _OnDependencyInjection_ hint determines whether to generate partial _OnDependencyInjection_ method to control of dependency injection. In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnDependencyInjection = On`. @@ -134,7 +134,7 @@ classDiagram Composition ..> Service : IService GetRoot(int id) Service *-- Dependency : IDependency Dependency o-- Int32 : Argument "id" - namespace Pure.DI.UsageTests.Hints.OnDependencyInjectionHintScenario { + namespace Pure.DI.UsageTests.Hints.OnDependencyInjectionRegularExpressionHintScenario { class Composition { <> +IService GetRoot(int id) diff --git a/readme/ondependencyinjection-wildcard-hint.md b/readme/ondependencyinjection-wildcard-hint.md new file mode 100644 index 00000000..af1efd72 --- /dev/null +++ b/readme/ondependencyinjection-wildcard-hint.md @@ -0,0 +1,162 @@ +#### OnDependencyInjection wildcard hint + +[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionWildcardHintScenario.cs) + +Hints are used to fine-tune code generation. The _OnDependencyInjection_ hint determines whether to generate partial _OnDependencyInjection_ method to control of dependency injection. +In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnDependencyInjection = On`. + + +```c# +using Shouldly; +using Pure.DI; +using static Pure.DI.Hint; + +// OnDependencyInjection = On +DI.Setup(nameof(Composition)) + .Hint(OnDependencyInjectionContractTypeNameWildcard, "*IDependency") + .RootArg("id") + .Bind().To() + .Bind().To() + .Root("GetRoot"); + +var log = new List(); +var composition = new Composition(log); +var service = composition.GetRoot(33); + +log.ShouldBe(["Dependency injected"]); + +interface IDependency; + +record Dependency(int Id) : IDependency; + +interface IService +{ + IDependency Dependency { get; } +} + +class Service(IDependency dependency) : IService +{ + public IDependency Dependency { get; } = dependency; +} + +partial class Composition +{ + private readonly List _log = []; + + public Composition(List log) : this() => + _log = log; + + private partial T OnDependencyInjection( + in T value, + object? tag, + Lifetime lifetime) + { + _log.Add($"{value?.GetType().Name} injected"); + return value; + } +} +``` + +
+Running this code sample locally + +- Make sure you have the [.NET SDK 9.0](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) or later is installed +```bash +dotnet --list-sdk +``` +- Create a net9.0 (or later) console application +```bash +dotnet new console -n Sample +``` +- Add references to NuGet packages + - [Pure.DI](https://www.nuget.org/packages/Pure.DI) + - [Shouldly](https://www.nuget.org/packages/Shouldly) +```bash +dotnet add package Pure.DI +dotnet add package Shouldly +``` +- Copy the example code into the _Program.cs_ file + +You are ready to run the example 🚀 +```bash +dotnet run +``` + +
+ +The `OnDependencyInjectionContractTypeNameWildcard` hint helps identify the set of types that require injection control. You can use it to specify a wildcard to filter the full name of a type. +For more hints, see [this](README.md#setup-hints) page. + +The following partial class will be generated: + +```c# +partial class Composition +{ + private readonly Composition _root; + + [OrdinalAttribute(128)] + public Composition() + { + _root = this; + } + + internal Composition(Composition parentScope) + { + _root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IService GetRoot(int id) + { + return new Service(OnDependencyInjection(new Dependency(id), null, Lifetime.Transient)); + } + + + private partial T OnDependencyInjection(in T value, object? tag, Lifetime lifetime); +} +``` + +Class diagram: + +```mermaid +--- + config: + class: + hideEmptyMembersBox: true +--- +classDiagram + Service --|> IService + Dependency --|> IDependency + Dependency --|> IEquatableᐸDependencyᐳ + Composition ..> Service : IService GetRoot(int id) + Service *-- Dependency : IDependency + Dependency o-- Int32 : Argument "id" + namespace Pure.DI.UsageTests.Hints.OnDependencyInjectionWildcardHintScenario { + class Composition { + <> + +IService GetRoot(int id) + } + class Dependency { + <> + +Dependency(Int32 Id) + } + class IDependency { + <> + } + class IService { + <> + } + class Service { + +Service(IDependency dependency) + } + } + namespace System { + class IEquatableᐸDependencyᐳ { + <> + } + class Int32 { + <> + } + } +``` + diff --git a/readme/onnewinstance-hint.md b/readme/onnewinstance-regular-expression-hint.md similarity index 88% rename from readme/onnewinstance-hint.md rename to readme/onnewinstance-regular-expression-hint.md index 4747b900..76caac78 100644 --- a/readme/onnewinstance-hint.md +++ b/readme/onnewinstance-regular-expression-hint.md @@ -1,6 +1,6 @@ -#### OnNewInstance hint +#### OnNewInstance regular expression hint -[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Hints/OnNewInstanceHintScenario.cs) +[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Hints/OnNewInstanceRegularExpressionHintScenario.cs) Hints are used to fine-tune code generation. The _OnNewInstance_ hint determines whether to generate partial _OnNewInstance_ method. In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnNewInstance = On`. @@ -13,8 +13,9 @@ using static Pure.DI.Hint; DI.Setup(nameof(Composition)) .Hint(OnNewInstance, "On") + .Hint(OnNewInstanceLifetimeRegularExpression, "(Singleton|PerBlock)") .Bind().As(Lifetime.Singleton).To() - .Bind().To() + .Bind().As(Lifetime.PerBlock).To() .Root("Root"); var log = new List(); @@ -134,9 +135,9 @@ partial class Composition } } - Service transientService0 = new Service(_root._singletonDependency43); - OnNewInstance(ref transientService0, null, Lifetime.Transient); - return transientService0; + Service perBlockService0 = new Service(_root._singletonDependency43); + OnNewInstance(ref perBlockService0, null, Lifetime.PerBlock); + return perBlockService0; } } @@ -158,7 +159,7 @@ classDiagram Dependency --|> IDependency Composition ..> Service : IService Root Service o-- "Singleton" Dependency : IDependency - namespace Pure.DI.UsageTests.Hints.OnNewInstanceHintScenario { + namespace Pure.DI.UsageTests.Hints.OnNewInstanceRegularExpressionHintScenario { class Composition { <> +IService Root diff --git a/readme/onnewinstance-wildcard-hint.md b/readme/onnewinstance-wildcard-hint.md new file mode 100644 index 00000000..7263e320 --- /dev/null +++ b/readme/onnewinstance-wildcard-hint.md @@ -0,0 +1,176 @@ +#### OnNewInstance wildcard hint + +[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Hints/OnNewInstanceWildcardHintScenario.cs) + +Hints are used to fine-tune code generation. The _OnNewInstance_ hint determines whether to generate partial _OnNewInstance_ method. +In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnNewInstance = On`. + + +```c# +using Shouldly; +using Pure.DI; +using static Pure.DI.Hint; + +DI.Setup(nameof(Composition)) + .Hint(OnNewInstance, "On") + .Hint(OnNewInstanceImplementationTypeNameWildcard, "*Service") + .Bind().As(Lifetime.Singleton).To() + .Bind().To() + .Root("Root"); + +var log = new List(); +var composition = new Composition(log); +var service1 = composition.Root; +var service2 = composition.Root; + +log.ShouldBe([ + "Service created", + "Service created"]); + +interface IDependency; + +class Dependency : IDependency +{ + public override string ToString() => nameof(Dependency); +} + +interface IService +{ + IDependency Dependency { get; } +} + +class Service(IDependency dependency) : IService +{ + public IDependency Dependency { get; } = dependency; + + public override string ToString() => nameof(Service); +} + +internal partial class Composition +{ + private readonly List _log = []; + + public Composition(List log) : this() => + _log = log; + + partial void OnNewInstance( + ref T value, + object? tag, + Lifetime lifetime) => + _log.Add($"{typeof(T).Name} created"); +} +``` + +
+Running this code sample locally + +- Make sure you have the [.NET SDK 9.0](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) or later is installed +```bash +dotnet --list-sdk +``` +- Create a net9.0 (or later) console application +```bash +dotnet new console -n Sample +``` +- Add references to NuGet packages + - [Pure.DI](https://www.nuget.org/packages/Pure.DI) + - [Shouldly](https://www.nuget.org/packages/Shouldly) +```bash +dotnet add package Pure.DI +dotnet add package Shouldly +``` +- Copy the example code into the _Program.cs_ file + +You are ready to run the example 🚀 +```bash +dotnet run +``` + +
+ +The `OnNewInstanceImplementationTypeNameWildcard` hint helps you define a set of implementation types that require instance creation control. You can use it to specify a wildcard to filter bindings by implementation name. +For more hints, see [this](README.md#setup-hints) page. + +The following partial class will be generated: + +```c# +partial class Composition +{ + private readonly Composition _root; + private readonly Lock _lock; + + private Dependency? _singletonDependency43; + + [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 IService Root + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (_root._singletonDependency43 is null) + { + using (_lock.EnterScope()) + { + if (_root._singletonDependency43 is null) + { + _root._singletonDependency43 = new Dependency(); + } + } + } + + Service transientService0 = new Service(_root._singletonDependency43); + OnNewInstance(ref transientService0, null, Lifetime.Transient); + return transientService0; + } + } + + + partial void OnNewInstance(ref T value, object? tag, Lifetime lifetime); +} +``` + +Class diagram: + +```mermaid +--- + config: + class: + hideEmptyMembersBox: true +--- +classDiagram + Service --|> IService + Dependency --|> IDependency + Composition ..> Service : IService Root + Service o-- "Singleton" Dependency : IDependency + namespace Pure.DI.UsageTests.Hints.OnNewInstanceWildcardHintScenario { + class Composition { + <> + +IService Root + } + class Dependency { + +Dependency() + } + class IDependency { + <> + } + class IService { + <> + } + class Service { + +Service(IDependency dependency) + } + } +``` + diff --git a/src/Pure.DI.Core/Components/Api.g.cs b/src/Pure.DI.Core/Components/Api.g.cs index f03e6c36..0f01ac03 100644 --- a/src/Pure.DI.Core/Components/Api.g.cs +++ b/src/Pure.DI.Core/Components/Api.g.cs @@ -199,6 +199,26 @@ internal enum Hint /// OnNewInstanceImplementationTypeNameRegularExpression, + /// + /// The wildcard to filter OnNewInstance by the instance type name. "*" by default. + /// + /// + /// // OnNewInstanceImplementationTypeNameWildcard = *Dependency + /// DI.Setup("Composition") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ /// or using the API call : + /// + /// DI.Setup("Composition") + /// .Hint(Hint.OnNewInstanceImplementationTypeNameWildcard, "*Dependency") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ ///
+ /// + OnNewInstanceImplementationTypeNameWildcard, + /// /// The regular expression to filter OnNewInstance by the tag. ".+" by default. /// @@ -219,6 +239,26 @@ internal enum Hint /// OnNewInstanceTagRegularExpression, + /// + /// The wildcard to filter OnNewInstance by the tag. "*" by default. + /// + /// + /// // OnNewInstanceTagWildcard = *IDependency + /// DI.Setup("Composition") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ /// or using the API call : + /// + /// DI.Setup("Composition") + /// .Hint(Hint.OnNewInstanceTagWildcard, "*IDependency") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ ///
+ /// + OnNewInstanceTagWildcard, + /// /// The regular expression to filter OnNewInstance by the lifetime. ".+" by default. /// @@ -239,6 +279,26 @@ internal enum Hint /// OnNewInstanceLifetimeRegularExpression, + /// + /// The wildcard to filter OnNewInstance by the lifetime. "*" by default. + /// + /// + /// // OnNewInstanceLifetimeWildcard = *Singleton + /// DI.Setup("Composition") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ /// or using the API call : + /// + /// DI.Setup("Composition") + /// .Hint(Hint.OnNewInstanceLifetimeWildcard, "*Singleton") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ ///
+ /// + OnNewInstanceLifetimeWildcard, + /// /// On or Off. Determines whether to use partial OnDependencyInjection method to control of dependency injection. Off by default. /// @@ -299,6 +359,26 @@ internal enum Hint /// OnDependencyInjectionImplementationTypeNameRegularExpression, + /// + /// The wildcard to filter OnDependencyInjection by the instance type name. "*" by default. + /// + /// + /// // OnDependencyInjectionImplementationTypeNameWildcard = *Dependency + /// DI.Setup("Composition") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ /// or using the API call : + /// + /// DI.Setup("Composition") + /// .Hint(Hint.OnDependencyInjectionImplementationTypeNameWildcard, "*Dependency") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ ///
+ /// + OnDependencyInjectionImplementationTypeNameWildcard, + /// /// The regular expression to filter OnDependencyInjection by the resolving type name. ".+" by default. /// @@ -319,6 +399,26 @@ internal enum Hint /// OnDependencyInjectionContractTypeNameRegularExpression, + /// + /// The wildcard to filter OnDependencyInjection by the resolving type name. "*" by default. + /// + /// + /// // OnDependencyInjectionContractTypeNameWildcard = *IDependency + /// DI.Setup("Composition") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ /// or using the API call : + /// + /// DI.Setup("Composition") + /// .Hint(Hint.OnDependencyInjectionContractTypeName, "*IDependency") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ ///
+ /// + OnDependencyInjectionContractTypeNameWildcard, + /// /// The regular expression to filter OnDependencyInjection by the tag. ".+" by default. /// @@ -339,6 +439,26 @@ internal enum Hint /// OnDependencyInjectionTagRegularExpression, + /// + /// The wildcard to filter OnDependencyInjection by the tag. "*" by default. + /// + /// + /// // OnDependencyInjectionTagWildcard = MyTag + /// DI.Setup("Composition") + /// .Bind<IDependency>("MyTag").To<Dependency>(); + /// + ///
+ /// or using the API call : + /// + /// DI.Setup("Composition") + /// .Hint(Hint.OnDependencyInjectionTagWildcard, "MyTag") + /// .Bind<IDependency>("MyTag").To<Dependency>(); + /// + ///
+ ///
+ /// + OnDependencyInjectionTagWildcard, + /// /// The regular expression to filter OnDependencyInjection by the lifetime. ".+" by default. /// @@ -359,6 +479,26 @@ internal enum Hint /// OnDependencyInjectionLifetimeRegularExpression, + /// + /// The wildcard to filter OnDependencyInjection by the lifetime. ".+" by default. + /// + /// + /// // OnDependencyInjectionLifetimeWildcard = *Singleton + /// DI.Setup("Composition") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ /// or using the API call : + /// + /// DI.Setup("Composition") + /// .Hint(Hint.OnDependencyInjectionLifetimeWildcard, "*Singleton") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ ///
+ /// + OnDependencyInjectionLifetimeWildcard, + /// /// On or Off. Determines whether to use a partial OnCannotResolve<T>(...) method to handle a scenario in which the dependency cannot be resolved. Off by default. /// @@ -419,6 +559,26 @@ internal enum Hint /// OnCannotResolveContractTypeNameRegularExpression, + /// + /// The wildcard to filter OnCannotResolve by the resolving type name. "*" by default. + /// + /// + /// // OnCannotResolveContractTypeNameWildcard = *OtherType + /// DI.Setup("Composition") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ /// or using the API call : + /// + /// DI.Setup("Composition") + /// .Hint(Hint.OnCannotResolveContractTypeNameWildcard, "*OtherType") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ ///
+ /// + OnCannotResolveContractTypeNameWildcard, + /// /// The regular expression to filter OnCannotResolve by the tag. ".+" by default. /// @@ -439,6 +599,26 @@ internal enum Hint /// OnCannotResolveTagRegularExpression, + /// + /// The wildcard to filter OnCannotResolve by the tag. "*" by default. + /// + /// + /// // OnCannotResolveTagWildcard = MyTag + /// DI.Setup("Composition") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ /// or using the API call : + /// + /// DI.Setup("Composition") + /// .Hint(Hint.OnCannotResolveTagWildcard, "MyTag") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ ///
+ /// + OnCannotResolveTagWildcard, + /// /// The regular expression to filter OnCannotResolve by the lifetime. ".+" by default. /// @@ -459,6 +639,26 @@ internal enum Hint /// OnCannotResolveLifetimeRegularExpression, + /// + /// The wildcard to filter OnCannotResolve by the lifetime. "*" by default. + /// + /// + /// // OnCannotResolveLifetimeWildcard = *Singleton + /// DI.Setup("Composition") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ /// or using the API call : + /// + /// DI.Setup("Composition") + /// .Hint(Hint.OnCannotResolveLifetimeWildcard, "*Singleton") + /// .Bind<IDependency>().To<Dependency>(); + /// + ///
+ ///
+ /// + OnCannotResolveLifetimeWildcard, + /// /// On or Off. Determines whether to use a static partial OnNewRoot<T>(...) method to handle the new Composition root registration event. Off by default. /// diff --git a/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs b/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs index 7468d2a0..ac37eee4 100644 --- a/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs +++ b/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs @@ -67,7 +67,7 @@ MemberAccessExpressionSyntax memberAccess when memberAccess.Kind() == SyntaxKind if (type is INamedTypeSymbol symbol) { if (symbol.TypeArguments.Length > 1 - && symbolNames.GetDisplayName(symbol.TypeArguments[0]) != Names.ContextInterfaceName) + && symbolNames.GetGlobalName(symbol.TypeArguments[0]) != Names.IContextTypeName) { switch (invocation.ArgumentList.Arguments[0].Expression) { diff --git a/src/Pure.DI.Core/Core/Attributes.cs b/src/Pure.DI.Core/Core/Attributes.cs index 9c912b45..60f6c831 100644 --- a/src/Pure.DI.Core/Core/Attributes.cs +++ b/src/Pure.DI.Core/Core/Attributes.cs @@ -115,7 +115,7 @@ private IReadOnlyList GetAttributes(ISymbol member, INamedTypeSym return false; } - return symbolNames.GetDisplayName(unboundTypeSymbol) == symbolNames.GetDisplayName(attributeType); + return symbolNames.GetGlobalName(unboundTypeSymbol) == symbolNames.GetGlobalName(attributeType); }) .ToArray(); diff --git a/src/Pure.DI.Core/Core/Code/BuildTools.cs b/src/Pure.DI.Core/Core/Code/BuildTools.cs index a27354c4..07ef0848 100644 --- a/src/Pure.DI.Core/Core/Code/BuildTools.cs +++ b/src/Pure.DI.Core/Core/Code/BuildTools.cs @@ -70,13 +70,22 @@ private string OnInjectedInternal(BuildContext ctx, Variable variable) } var tag = GetTag(ctx, variable); - // ReSharper disable once ConvertIfStatementToReturnStatement - if (!filter.IsMeetRegularExpression( + var typeName = new Lazy(() => typeResolver.Resolve(variable.Setup, variable.InstanceType).Name); + var contractName = new Lazy(() => symbolNames.GetName(variable.ContractType)); + var tagName = new Lazy(() => tag.ValueToString()); + var lifetimeName = new Lazy(() => variable.Node.Lifetime.ValueToString()); + if (!filter.IsMeetRegularExpressions( ctx.DependencyGraph.Source, - (Hint.OnDependencyInjectionImplementationTypeNameRegularExpression, typeResolver.Resolve(variable.Setup, variable.InstanceType).Name), - (Hint.OnDependencyInjectionContractTypeNameRegularExpression, symbolNames.GetDisplayName(variable.ContractType)), - (Hint.OnDependencyInjectionTagRegularExpression, tag.ValueToString()), - (Hint.OnDependencyInjectionLifetimeRegularExpression, variable.Node.Lifetime.ValueToString()))) + (Hint.OnDependencyInjectionImplementationTypeNameRegularExpression, typeName), + (Hint.OnDependencyInjectionContractTypeNameRegularExpression, contractName), + (Hint.OnDependencyInjectionTagRegularExpression, tagName), + (Hint.OnDependencyInjectionLifetimeRegularExpression, lifetimeName)) + || !filter.IsMeetWildcards( + ctx.DependencyGraph.Source, + (Hint.OnDependencyInjectionImplementationTypeNameWildcard, typeName), + (Hint.OnDependencyInjectionContractTypeNameWildcard, contractName), + (Hint.OnDependencyInjectionTagWildcard, tagName), + (Hint.OnDependencyInjectionLifetimeWildcard, lifetimeName))) { return variableCode; } @@ -128,11 +137,19 @@ public IEnumerable OnCreated(BuildContext ctx, Variable variable) } var tag = GetTag(ctx, variable); - if (!filter.IsMeetRegularExpression( + var typeName = new Lazy(() => symbolNames.GetName(variable.Node.Type)); + var tagName = new Lazy(() => tag.ValueToString()); + var lifetimeName = new Lazy(() => variable.Node.Lifetime.ValueToString()); + if (!filter.IsMeetRegularExpressions( + ctx.DependencyGraph.Source, + (Hint.OnNewInstanceImplementationTypeNameRegularExpression, typeName), + (Hint.OnNewInstanceTagRegularExpression, tagName), + (Hint.OnNewInstanceLifetimeRegularExpression, lifetimeName)) + || !filter.IsMeetWildcards( ctx.DependencyGraph.Source, - (Hint.OnNewInstanceImplementationTypeNameRegularExpression, variable.Node.Type.ToString()), - (Hint.OnNewInstanceTagRegularExpression, tag.ValueToString()), - (Hint.OnNewInstanceLifetimeRegularExpression, variable.Node.Lifetime.ValueToString()))) + (Hint.OnNewInstanceImplementationTypeNameWildcard, typeName), + (Hint.OnNewInstanceTagWildcard, tagName), + (Hint.OnNewInstanceLifetimeWildcard, lifetimeName))) { return code.Lines; } diff --git a/src/Pure.DI.Core/Core/Code/CompositionClassBuilder.cs b/src/Pure.DI.Core/Core/Code/CompositionClassBuilder.cs index 1d2a0bf3..0b92912f 100644 --- a/src/Pure.DI.Core/Core/Code/CompositionClassBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/CompositionClassBuilder.cs @@ -70,12 +70,12 @@ public CompositionCode Build(CompositionCode composition) var implementingInterfaces = new List(); if (composition.TotalDisposablesCount > 0) { - implementingInterfaces.Add(Names.IDisposableInterfaceName); + implementingInterfaces.Add(Names.IDisposableTypeName); } if (composition.AsyncDisposableCount > 0) { - implementingInterfaces.Add(Names.IAsyncDisposableInterfaceName); + implementingInterfaces.Add(Names.IAsyncDisposableTypeName); } code.AppendLine($"partial class {name.ClassName}{(implementingInterfaces.Count > 0 ? ": " + string.Join(", ", implementingInterfaces) : "")}"); diff --git a/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs b/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs index 3b0fcbb3..9b34998e 100644 --- a/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs @@ -72,7 +72,7 @@ public CompositionCode Build(CompositionCode composition) code.AppendLine("/// The disposable instance."); code.AppendLine("/// Exception occurring during disposal."); code.AppendLine("/// The actual type of instance being disposed of."); - code.AppendLine($"partial void {Names.OnDisposeExceptionMethodName}(T disposableInstance, Exception exception) where T : {Names.IDisposableInterfaceName};"); + code.AppendLine($"partial void {Names.OnDisposeExceptionMethodName}(T disposableInstance, Exception exception) where T : {Names.IDisposableTypeName};"); membersCounter++; // ReSharper disable once InvertIf @@ -132,7 +132,7 @@ public CompositionCode Build(CompositionCode composition) code.AppendLine("/// The disposable instance."); code.AppendLine("/// Exception occurring during disposal."); code.AppendLine("/// The actual type of instance being disposed of."); - code.AppendLine($"partial void {Names.OnDisposeAsyncExceptionMethodName}(T asyncDisposableInstance, Exception exception) where T : {Names.IAsyncDisposableInterfaceName};"); + code.AppendLine($"partial void {Names.OnDisposeAsyncExceptionMethodName}(T asyncDisposableInstance, Exception exception) where T : {Names.IAsyncDisposableTypeName};"); membersCounter++; } @@ -141,7 +141,7 @@ public CompositionCode Build(CompositionCode composition) private static void AddDisposeAsyncPart(LinesBuilder code, bool makeAsyncCall) { - code.AppendLine($"case {Names.IAsyncDisposableInterfaceName} asyncDisposableInstance:"); + code.AppendLine($"case {Names.IAsyncDisposableTypeName} asyncDisposableInstance:"); using (code.Indent()) { code.AppendLine("try"); @@ -181,7 +181,7 @@ private static void AddDisposeAsyncPart(LinesBuilder code, bool makeAsyncCall) private static void AddDisposePart(LinesBuilder code) { - code.AppendLine($"case {Names.IDisposableInterfaceName} disposableInstance:"); + code.AppendLine($"case {Names.IDisposableTypeName} disposableInstance:"); using (code.Indent()) { code.AppendLine("try"); diff --git a/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs b/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs index 3915dc14..29f19534 100644 --- a/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs @@ -70,7 +70,7 @@ public void Build(BuildContext ctx, in DpFactory factory) ExpressionSyntax? value = null; var type = memberResolver.ContractType; ExpressionSyntax instance = member.IsStatic - ? SyntaxFactory.ParseTypeName(symbolNames.GetDisplayName(type)) + ? SyntaxFactory.ParseTypeName(symbolNames.GetGlobalName(type)) : SyntaxFactory.IdentifierName(DefaultInstanceValueName); switch (member) @@ -102,7 +102,7 @@ public void Build(BuildContext ctx, in DpFactory factory) argType = bindingTypeConstructor.Construct(setup, binding.SemanticModel.Compilation, argType); } - var typeName = symbolNames.GetDisplayName(argType); + var typeName = symbolNames.GetGlobalName(argType); typeArgs.Add(SyntaxFactory.ParseTypeName(typeName)); } diff --git a/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs b/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs index 956dfb57..f5324455 100644 --- a/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs +++ b/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs @@ -256,7 +256,7 @@ private bool TryInitialize( return Visit(SyntaxFactory.ParseExpression($" {variable.Injection.Tag.ValueToString()}")); case nameof(IContext.ConsumerTypes): - var consumers = variable.Info.GetTargetNodes().Select(targetNode => $"typeof({symbolNames.GetDisplayName(targetNode.Type)})").ToList(); + var consumers = variable.Info.GetTargetNodes().Select(targetNode => $"typeof({symbolNames.GetGlobalName(targetNode.Type)})").ToList(); if (consumers.Count == 0 && _ctx is not null) { consumers.Add($"typeof({_ctx.DependencyGraph.Source.Name.FullName})"); diff --git a/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs b/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs index 99a72146..cf55c905 100644 --- a/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs @@ -23,11 +23,11 @@ public CompositionCode Build(CompositionCode composition) membersCount++; code.AppendLine(); - code.AppendLine($"private class {Names.ResolverClassName}: {Names.ResolverInterfaceName}<{composition.Source.Source.Name.ClassName}, T>"); + code.AppendLine($"private class {Names.ResolverClassName}: {Names.IResolverTypeName}<{composition.Source.Source.Name.ClassName}, T>"); code.AppendLine("{"); using (code.Indent()) { - code.AppendLine($"public static {Names.ResolverInterfaceName}<{composition.Source.Source.Name.ClassName}, T> {Names.ResolverPropertyName} = new {Names.ResolverClassName}();"); + code.AppendLine($"public static {Names.IResolverTypeName}<{composition.Source.Source.Name.ClassName}, T> {Names.ResolverPropertyName} = new {Names.ResolverClassName}();"); code.AppendLine(); code.AppendLine($"public virtual T {Names.ResolveMethodName}({composition.Source.Source.Name.ClassName} composite)"); code.AppendLine("{"); @@ -60,7 +60,7 @@ public CompositionCode Build(CompositionCode composition) var objectTypeName = ""; if (resolver.Type.IsValueType) { - objectTypeName = $"{Names.ResolverInterfaceName}<{composition.Source.Source.Name.ClassName}, object>"; + objectTypeName = $"{Names.IResolverTypeName}<{composition.Source.Source.Name.ClassName}, object>"; baseTypes.Add(objectTypeName); } diff --git a/src/Pure.DI.Core/Core/Code/ResolverFieldsBuilder.cs b/src/Pure.DI.Core/Core/Code/ResolverFieldsBuilder.cs index 3f100b6b..aa73fd24 100644 --- a/src/Pure.DI.Core/Core/Code/ResolverFieldsBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/ResolverFieldsBuilder.cs @@ -20,7 +20,7 @@ public CompositionCode Build(CompositionCode composition) var code = composition.Code; code.AppendLine($"private readonly static int {Names.BucketSizeFieldName};"); - var pairs = $"{Names.SystemNamespace}Type, {Names.ResolverInterfaceName}<{composition.Source.Source.Name.ClassName}, object>"; + var pairs = $"{Names.SystemNamespace}Type, {Names.IResolverTypeName}<{composition.Source.Source.Name.ClassName}, object>"; var pairTypeName = $"{Names.ApiNamespace}Pair<{pairs}>"; code.AppendLine($"private readonly static {pairTypeName}[] {Names.BucketsFieldName};"); diff --git a/src/Pure.DI.Core/Core/Code/StaticConstructorBuilder.cs b/src/Pure.DI.Core/Core/Code/StaticConstructorBuilder.cs index a679ffb5..ba692572 100644 --- a/src/Pure.DI.Core/Core/Code/StaticConstructorBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/StaticConstructorBuilder.cs @@ -20,7 +20,7 @@ public CompositionCode Build(CompositionCode composition) // ReSharper disable once InvertIf if (hasOnNewRoot && composition.Source.Source.Hints.IsOnNewRootPartial) { - code.AppendLine($"private static partial void {Names.OnNewRootMethodName}({Names.ResolverInterfaceName}<{composition.Source.Source.Name.ClassName}, TContract> resolver, string name, object? tag, {Names.ApiNamespace}{nameof(Lifetime)} lifetime);"); + code.AppendLine($"private static partial void {Names.OnNewRootMethodName}({Names.IResolverTypeName}<{composition.Source.Source.Name.ClassName}, TContract> resolver, string name, object? tag, {Names.ApiNamespace}{nameof(Lifetime)} lifetime);"); code.AppendLine(); membersCounter++; } @@ -51,7 +51,7 @@ public CompositionCode Build(CompositionCode composition) } var divisor = Buckets.GetDivisor((uint)resolvers.Length); - var pairs = $"{Names.SystemNamespace}Type, {Names.ResolverInterfaceName}<{composition.Source.Source.Name.ClassName}, object>"; + var pairs = $"{Names.SystemNamespace}Type, {Names.IResolverTypeName}<{composition.Source.Source.Name.ClassName}, object>"; var bucketsTypeName = $"{Names.ApiNamespace}Buckets<{pairs}>"; var pairTypeName = $"{Names.ApiNamespace}Pair<{pairs}>"; code.AppendLine($"{Names.BucketsFieldName} = {bucketsTypeName}.{nameof(Buckets.Create)}("); diff --git a/src/Pure.DI.Core/Core/Code/TypeResolver.cs b/src/Pure.DI.Core/Core/Code/TypeResolver.cs index fbb42c4b..668ed4b1 100644 --- a/src/Pure.DI.Core/Core/Code/TypeResolver.cs +++ b/src/Pure.DI.Core/Core/Code/TypeResolver.cs @@ -33,7 +33,7 @@ private TypeDescription Resolve(MdSetup setup, ITypeSymbol type, ITypeParameterS } else { - description = new TypeDescription(symbolNames.GetDisplayName(type), ImmutableArray.Empty, typeParam); + description = new TypeDescription(symbolNames.GetGlobalName(type), ImmutableArray.Empty, typeParam); } break; @@ -73,7 +73,7 @@ private TypeDescription Resolve(MdSetup setup, ITypeSymbol type, ITypeParameterS break; default: - description = new TypeDescription(symbolNames.GetDisplayName(type), ImmutableArray.Empty, typeParam); + description = new TypeDescription(symbolNames.GetGlobalName(type), ImmutableArray.Empty, typeParam); break; } diff --git a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs index 205b5e39..3f2aafe8 100644 --- a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs +++ b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs @@ -17,6 +17,7 @@ internal sealed class DependencyGraphBuilder( IFilter filter, ICache constructKinds, IRegistryManager registryManager, + ISymbolNames symbolNames, CancellationToken cancellationToken) : IDependencyGraphBuilder { @@ -269,7 +270,7 @@ public IEnumerable TryBuild( continue; } - if (injection.Type.ToString() == setup.Name.FullName) + if (symbolNames.GetName(injection.Type) == setup.Name.FullName) { // Composition var compositionBinding = CreateConstructBinding( @@ -380,12 +381,12 @@ void UpdateMap(Injection injection, DependencyNode node) private MdConstructKind GetConstructKind(INamedTypeSymbol geneticType) { var unboundGenericType = geneticType.ConstructUnboundGenericType(); - return constructKinds.Get(unboundGenericType, type => type.ToString() switch + return constructKinds.Get(unboundGenericType, type => symbolNames.GetGlobalName(type) switch { - "System.Span<>" => MdConstructKind.Span, - "System.ReadOnlySpan<>" => MdConstructKind.Span, - "System.Collections.Generic.IEnumerable<>" => MdConstructKind.Enumerable, - "System.Collections.Generic.IAsyncEnumerable<>" => MdConstructKind.AsyncEnumerable, + Names.SpanTypeName => MdConstructKind.Span, + Names.ReadOnlySpanTypeName => MdConstructKind.Span, + Names.IEnumerableTypeName => MdConstructKind.Enumerable, + Names.IAsyncEnumerableTypeName => MdConstructKind.AsyncEnumerable, _ => MdConstructKind.None }); } @@ -399,29 +400,39 @@ private bool TryCreateOnCannotResolve( IDictionary map, ISet processed) { - if (setup.Hints.IsOnCannotResolveEnabled - && filter.IsMeetRegularExpression( - setup, - (Hint.OnCannotResolveContractTypeNameRegularExpression, unresolvedInjection.Type.ToString()), - (Hint.OnCannotResolveTagRegularExpression, unresolvedInjection.Tag.ValueToString()), - (Hint.OnCannotResolveLifetimeRegularExpression, ownerNode.Lifetime.ValueToString()))) + if (setup.Hints.IsOnCannotResolveEnabled) { - var onCannotResolveBinding = CreateConstructBinding( - setup, - ownerNode, - unresolvedInjection, - unresolvedInjection.Type, - Lifetime.Transient, - ++maxId, - MdConstructKind.OnCannotResolve, - unresolvedInjection.Tag); - - var onCannotResolveNodes = CreateNodes(setup, typeConstructor, onCannotResolveBinding); - foreach (var onCannotResolveNode in onCannotResolveNodes) + var contractName = new Lazy(() => symbolNames.GetName(unresolvedInjection.Type)); + var tagName = new Lazy(() => unresolvedInjection.Tag.ValueToString()); + var lifetimeName = new Lazy(() => ownerNode.Lifetime.ValueToString()); + if (filter.IsMeetRegularExpressions( + setup, + (Hint.OnCannotResolveContractTypeNameRegularExpression, contractName), + (Hint.OnCannotResolveTagRegularExpression, tagName), + (Hint.OnCannotResolveLifetimeRegularExpression, lifetimeName)) + && filter.IsMeetWildcards( + setup, + (Hint.OnCannotResolveContractTypeNameWildcard, contractName), + (Hint.OnCannotResolveTagWildcard, tagName), + (Hint.OnCannotResolveLifetimeWildcard, lifetimeName))) { - map[unresolvedInjection] = onCannotResolveNode; - processed.Add(CreateNewProcessingNode(setup, unresolvedInjection.Tag, onCannotResolveNode)); - return true; + var onCannotResolveBinding = CreateConstructBinding( + setup, + ownerNode, + unresolvedInjection, + unresolvedInjection.Type, + Lifetime.Transient, + ++maxId, + MdConstructKind.OnCannotResolve, + unresolvedInjection.Tag); + + var onCannotResolveNodes = CreateNodes(setup, typeConstructor, onCannotResolveBinding); + foreach (var onCannotResolveNode in onCannotResolveNodes) + { + map[unresolvedInjection] = onCannotResolveNode; + processed.Add(CreateNewProcessingNode(setup, unresolvedInjection.Tag, onCannotResolveNode)); + return true; + } } } diff --git a/src/Pure.DI.Core/Core/DependencyGraphValidator.cs b/src/Pure.DI.Core/Core/DependencyGraphValidator.cs index 4be36c17..58c7e32c 100644 --- a/src/Pure.DI.Core/Core/DependencyGraphValidator.cs +++ b/src/Pure.DI.Core/Core/DependencyGraphValidator.cs @@ -23,14 +23,24 @@ public bool Validate(DependencyGraph dependencyGraph) { cancellationToken.ThrowIfCancellationRequested(); var setup = dependencyGraph.Source; - if (setup.Hints.IsOnCannotResolveEnabled - && filter.IsMeetRegularExpression( - setup, - (Hint.OnCannotResolveContractTypeNameRegularExpression, typeResolver.Resolve(setup, unresolvedInjection.Type).Name), - (Hint.OnCannotResolveTagRegularExpression, unresolvedInjection.Tag.ValueToString()), - (Hint.OnCannotResolveLifetimeRegularExpression, dependencyNode.Lifetime.ValueToString()))) + if (setup.Hints.IsOnCannotResolveEnabled) { - continue; + var contractName = new Lazy(() => typeResolver.Resolve(setup, unresolvedInjection.Type).Name); + var tagName = new Lazy(() => unresolvedInjection.Tag.ValueToString()); + var lifetimeName = new Lazy(() => dependencyNode.Lifetime.ValueToString()); + if (filter.IsMeetRegularExpressions( + setup, + (Hint.OnCannotResolveContractTypeNameRegularExpression, contractName), + (Hint.OnCannotResolveTagRegularExpression, tagName), + (Hint.OnCannotResolveLifetimeRegularExpression, lifetimeName)) + && filter.IsMeetWildcards( + setup, + (Hint.OnCannotResolveContractTypeNameWildcard, contractName), + (Hint.OnCannotResolveTagWildcard, tagName), + (Hint.OnCannotResolveLifetimeWildcard, lifetimeName))) + { + continue; + } } isResolved = false; diff --git a/src/Pure.DI.Core/Core/Filter.cs b/src/Pure.DI.Core/Core/Filter.cs index 9a738d82..bad7eb08 100644 --- a/src/Pure.DI.Core/Core/Filter.cs +++ b/src/Pure.DI.Core/Core/Filter.cs @@ -6,19 +6,23 @@ namespace Pure.DI.Core; internal sealed class Filter( ILogger logger, - ICache regexCache) + ICache regexCache, + IWildcardMatcher wildcardMatcher) : IFilter { public static Regex RegexFactory(string filter) => new(filter, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline | RegexOptions.IgnoreCase); - public bool IsMeetRegularExpression(MdSetup setup, params (Hint setting, string value)[] settings) => - settings.All(i => IsMeetRegularExpression(setup, i.setting, i.value)); + public bool IsMeetRegularExpressions(MdSetup setup, params (Hint setting, Lazy textProvider)[] settings) => + settings.All(i => IsMeetRegularExpression(setup, i.setting, i.textProvider)); + + public bool IsMeetWildcards(MdSetup setup, params (Hint setting, Lazy textProvider)[] settings) => + settings.All(i => IsMeetWildcard(setup, i.setting, i.textProvider)); private bool IsMeetRegularExpression( MdSetup setup, Hint hint, - string value) + Lazy textProvider) { if (!setup.Hints.TryGetValue(hint, out var regularExpression) || string.IsNullOrWhiteSpace(regularExpression)) @@ -31,7 +35,7 @@ private bool IsMeetRegularExpression( try { var regex = regexCache.Get(regularExpression.Trim(), RegexFactory); - if (!regex.IsMatch(value)) + if (!regex.IsMatch(textProvider.Value)) { return false; } @@ -43,4 +47,32 @@ private bool IsMeetRegularExpression( return true; } + + private bool IsMeetWildcard( + MdSetup setup, + Hint hint, + Lazy textProvider) + { + if (!setup.Hints.TryGetValue(hint, out var wildcard) + || string.IsNullOrWhiteSpace(wildcard)) + { + { + return true; + } + } + + try + { + if (!wildcardMatcher.Match(wildcard.Trim().AsSpan(), textProvider.Value.AsSpan())) + { + return false; + } + } + catch (Exception ex) + { + logger.CompileError($"Invalid wildcard {wildcard}. {ex.Message}", setup.Source.GetLocation(), LogId.ErrorInvalidMetadata); + } + + return true; + } } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/GenericTypeArguments.cs b/src/Pure.DI.Core/Core/GenericTypeArguments.cs index 2af537de..d0327cc0 100644 --- a/src/Pure.DI.Core/Core/GenericTypeArguments.cs +++ b/src/Pure.DI.Core/Core/GenericTypeArguments.cs @@ -9,10 +9,10 @@ internal class GenericTypeArguments(ISymbolNames symbolNames) : IGenericTypeArgu private readonly ConcurrentDictionary> _genericTypeArgumentAttributesTypes = new(); public bool IsGenericTypeArgument(MdSetup setup, ITypeSymbol typeSymbol) => - _genericTypeArgumentTypes.GetOrAdd(setup, k => [..k.GenericTypeArguments.Select(i => symbolNames.GetDisplayName(i.Type))]) - .Contains(symbolNames.GetDisplayName(typeSymbol)); + _genericTypeArgumentTypes.GetOrAdd(setup, k => [..k.GenericTypeArguments.Select(i => symbolNames.GetGlobalName(i.Type))]) + .Contains(symbolNames.GetGlobalName(typeSymbol)); public bool IsGenericTypeArgumentAttribute(MdSetup setup, ITypeSymbol typeSymbol) => - _genericTypeArgumentAttributesTypes.GetOrAdd(setup, k => [..k.GenericTypeArgumentAttributes.Select(i => symbolNames.GetDisplayName(i.AttributeType))]) - .Contains(symbolNames.GetDisplayName(typeSymbol)); + _genericTypeArgumentAttributesTypes.GetOrAdd(setup, k => [..k.GenericTypeArgumentAttributes.Select(i => symbolNames.GetGlobalName(i.AttributeType))]) + .Contains(symbolNames.GetGlobalName(typeSymbol)); } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/IFilter.cs b/src/Pure.DI.Core/Core/IFilter.cs index f4a73e60..a338ba19 100644 --- a/src/Pure.DI.Core/Core/IFilter.cs +++ b/src/Pure.DI.Core/Core/IFilter.cs @@ -2,5 +2,7 @@ namespace Pure.DI.Core; internal interface IFilter { - bool IsMeetRegularExpression(MdSetup setup, params (Hint setting, string value)[] settings); + bool IsMeetRegularExpressions(MdSetup setup, params (Hint setting, Lazy textProvider)[] settings); + + bool IsMeetWildcards(MdSetup setup, params (Hint setting, Lazy textProvider)[] settings); } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/ISymbolNames.cs b/src/Pure.DI.Core/Core/ISymbolNames.cs index d62b8520..8c809e86 100644 --- a/src/Pure.DI.Core/Core/ISymbolNames.cs +++ b/src/Pure.DI.Core/Core/ISymbolNames.cs @@ -2,5 +2,7 @@ namespace Pure.DI.Core; internal interface ISymbolNames { - string GetDisplayName(ITypeSymbol typeSymbol); + string GetName(ITypeSymbol typeSymbol); + + string GetGlobalName(ITypeSymbol typeSymbol); } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/IWildcardMatcher.cs b/src/Pure.DI.Core/Core/IWildcardMatcher.cs index 23c3ca11..60aa38e9 100644 --- a/src/Pure.DI.Core/Core/IWildcardMatcher.cs +++ b/src/Pure.DI.Core/Core/IWildcardMatcher.cs @@ -6,13 +6,13 @@ internal interface IWildcardMatcher /// Return true if the given expression matches the given name. Supports the following wildcards: /// '*', '?', '<', '>', '"'. The backslash character '\' escapes. /// - /// The expression to match with, such as "*.foo". - /// The name to check against the expression. + /// The wildcard expression to match with, such as "*.foo". + /// The text to check against the expression. /// True to ignore case (default). /// True to use additional expressions symbols. bool Match( - ReadOnlySpan expression, - ReadOnlySpan name, + ReadOnlySpan wildcard, + ReadOnlySpan text, bool ignoreCase = false, bool useExtendedWildcards = false); } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/MetadataSyntaxWalker.cs b/src/Pure.DI.Core/Core/MetadataSyntaxWalker.cs index 6286d3c8..57552bb7 100644 --- a/src/Pure.DI.Core/Core/MetadataSyntaxWalker.cs +++ b/src/Pure.DI.Core/Core/MetadataSyntaxWalker.cs @@ -100,7 +100,7 @@ when memberAccess.Kind() == SyntaxKind.SimpleMemberAccessExpression if (_semanticModel?.GetTypeInfo(curInvocation) is { } typeInfo && (typeInfo.Type ?? typeInfo.ConvertedType) is { } type - && symbolNames.GetDisplayName(type) == Names.ConfigurationInterfaceName) + && symbolNames.GetGlobalName(type) == Names.IConfigurationTypeName) { return true; } diff --git a/src/Pure.DI.Core/Core/Names.cs b/src/Pure.DI.Core/Core/Names.cs index d11eb65e..7b09c2ea 100644 --- a/src/Pure.DI.Core/Core/Names.cs +++ b/src/Pure.DI.Core/Core/Names.cs @@ -31,23 +31,25 @@ internal static class Names public static readonly string ResolverClassName = $"Resolver{Salt}"; public const string DefaultApiMethodModifiers = "public"; public const string ParentScopeArgName = "parentScope"; - - // Interfaces public const string ResolverPropertyName = "Value"; - public const string IDisposableInterfaceName = $"{SystemNamespace}{nameof(IDisposable)}"; - public const string IAsyncDisposableInterfaceName = $"{SystemNamespace}IAsyncDisposable"; - public const string ResolverInterfaceName = $"{ApiNamespace}{nameof(IResolver)}"; - public const string ContextInterfaceName = $"{ApiNamespace}{nameof(IContext)}"; - public const string ConfigurationInterfaceName = $"{ApiNamespace}{nameof(IConfiguration)}"; // Attributes public const string OrdinalAttributeName = $"{ApiNamespace}{nameof(OrdinalAttribute)}"; public const string BindAttributeName = $"{ApiNamespace}{nameof(BindAttribute)}"; // Types + public const string IDisposableTypeName = $"{SystemNamespace}{nameof(IDisposable)}"; + public const string IAsyncDisposableTypeName = $"{SystemNamespace}IAsyncDisposable"; + public const string IResolverTypeName = $"{ApiNamespace}{nameof(IResolver)}"; + public const string IContextTypeName = $"{ApiNamespace}{nameof(IContext)}"; + public const string IConfigurationTypeName = $"{ApiNamespace}{nameof(IConfiguration)}"; public const string ObjectTypeName = $"{SystemNamespace}Object"; public const string ValueTaskTypeName = $"{SystemNamespace}Threading.Tasks.ValueTask"; public const string LockTypeName = $"{SystemNamespace}Threading.Lock"; + public const string SpanTypeName = $"{SystemNamespace}Span<>"; + public const string ReadOnlySpanTypeName = $"{SystemNamespace}ReadOnlySpan<>"; + public const string IEnumerableTypeName = $"{SystemNamespace}Collections.Generic.IEnumerable<>"; + public const string IAsyncEnumerableTypeName = $"{SystemNamespace}Collections.Generic.IAsyncEnumerable<>"; // Members public const string ResolveMethodName = nameof(IResolver.Resolve); diff --git a/src/Pure.DI.Core/Core/SetupsBuilder.cs b/src/Pure.DI.Core/Core/SetupsBuilder.cs index 7c17bc7b..d339157d 100644 --- a/src/Pure.DI.Core/Core/SetupsBuilder.cs +++ b/src/Pure.DI.Core/Core/SetupsBuilder.cs @@ -204,7 +204,7 @@ private void FinalizeBinding(MdSetup setup, MdBinding binding) from member in type.GetMembers() where member.DeclaredAccessibility >= Accessibility.Internal && member.CanBeReferencedByName && member is IFieldSymbol or IPropertySymbol or IMethodSymbol from attribute in member.GetAttributes() - where attribute.AttributeClass is not null && symbolNames.GetDisplayName(attribute.AttributeClass) == Names.BindAttributeName + where attribute.AttributeClass is not null && symbolNames.GetGlobalName(attribute.AttributeClass) == Names.BindAttributeName select (attribute, member); var typeConstructor = typeConstructorFactory(); @@ -346,7 +346,7 @@ from attribute in member.GetAttributes() MdResolver CreateResolver(ITypeConstructor constructor, string name, ITypeSymbol injectedType, object? tag, ref int curPosition) { - var typeSyntax = SyntaxFactory.ParseTypeName(symbolNames.GetDisplayName(injectedType)); + var typeSyntax = SyntaxFactory.ParseTypeName(symbolNames.GetGlobalName(injectedType)); if (semantic.IsValidNamespace(injectedType.ContainingNamespace)) { namespaces.Add(injectedType.ContainingNamespace.ToString()); diff --git a/src/Pure.DI.Core/Core/SymbolNames.cs b/src/Pure.DI.Core/Core/SymbolNames.cs index ee19c8d9..d0df3fd6 100644 --- a/src/Pure.DI.Core/Core/SymbolNames.cs +++ b/src/Pure.DI.Core/Core/SymbolNames.cs @@ -5,8 +5,12 @@ namespace Pure.DI.Core; internal class SymbolNames : ISymbolNames { - private readonly ConcurrentDictionary _displayNames = new(SymbolEqualityComparer.Default); - - public string GetDisplayName(ITypeSymbol typeSymbol) => - _displayNames.GetOrAdd(typeSymbol, symbol => symbol.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat)); + private readonly ConcurrentDictionary _names = new(SymbolEqualityComparer.Default); + private readonly ConcurrentDictionary _globalNames = new(SymbolEqualityComparer.Default); + + public string GetName(ITypeSymbol typeSymbol) => + _names.GetOrAdd(typeSymbol, symbol => symbol.ToString()); + + public string GetGlobalName(ITypeSymbol typeSymbol) => + _globalNames.GetOrAdd(typeSymbol, symbol => symbol.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat)); } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/WildcardMatcher.cs b/src/Pure.DI.Core/Core/WildcardMatcher.cs index 764ab49d..2b161d7d 100644 --- a/src/Pure.DI.Core/Core/WildcardMatcher.cs +++ b/src/Pure.DI.Core/Core/WildcardMatcher.cs @@ -70,8 +70,8 @@ internal class WildcardMatcher : IWildcardMatcher // DOS_DOT matches either a . or zero characters beyond name string. public bool Match( - ReadOnlySpan expression, - ReadOnlySpan name, + ReadOnlySpan wildcard, + ReadOnlySpan text, bool ignoreCase = false, bool useExtendedWildcards = false) { @@ -79,33 +79,33 @@ public bool Match( // in the regular expression that are matching the name. When the name has been exhausted, // if one of the locations in the expression is also just exhausted, the name is in the // language defined by the regular expression. - if (expression.Length == 0 || name.Length == 0) + if (wildcard.Length == 0 || text.Length == 0) { return false; } - if (expression[0] == '*') + if (wildcard[0] == '*') { // Just * matches everything - if (expression.Length == 1) + if (wildcard.Length == 1) { return true; } - var expressionEnd = expression[1..]; + var expressionEnd = wildcard[1..]; if (expressionEnd.IndexOfAny(useExtendedWildcards ? WildcardChars : SimpleWildcardChars) == -1) { // Handle the special case of a single starting *, which essentially means "ends with" // If the name doesn't have enough characters to match the remaining expression, it can't be a match. // ReSharper disable once ConvertIfStatementToReturnStatement - if (name.Length < expressionEnd.Length) + if (text.Length < expressionEnd.Length) { return false; } // See if we end with the expression - return name.EndsWith(expressionEnd, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); + return text.EndsWith(expressionEnd, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); } } @@ -120,7 +120,7 @@ public bool Match( Span priorMatches = stackalloc int[16]; priorMatches[0] = 0; - var maxState = expression.Length * 2; + var maxState = wildcard.Length * 2; int currentState; var nameFinished = false; @@ -142,14 +142,14 @@ public bool Match( // offset. Each character in the expression can represent one or two // states. * and DOS_STAR generate two states: expressionOffset * 2 and // expressionOffset * 2 + 1. All other expression characters can produce - // only a single state. Thus expressionOffset = currentState / 2. + // only a single state. Thus, expressionOffset = currentState / 2. while (!nameFinished) { - if (nameOffset < name.Length) + if (nameOffset < text.Length) { // Not at the end of the name. Grab the current character and move the offset forward. - nameChar = name[nameOffset++]; + nameChar = text[nameOffset++]; } else { @@ -172,10 +172,10 @@ public bool Match( // character of name, so we loop here until the expression stops matching. var expressionOffset = (priorMatches[priorMatch++] + 1) / 2; - while (expressionOffset < expression.Length) + while (expressionOffset < wildcard.Length) { currentState = expressionOffset * 2; - var expressionChar = expression[expressionOffset]; + var expressionChar = wildcard[expressionOffset]; // We may be about to exhaust the local space for matches, // so we have to reallocate if this is the case. @@ -207,10 +207,10 @@ public bool Match( var notLastPeriod = false; if (!nameFinished && nameChar == '.') { - for (var offset = nameOffset; offset < name.Length; offset++) + for (var offset = nameOffset; offset < text.Length; offset++) { // ReSharper disable once InvertIf - if (name[offset] == '.') + if (text[offset] == '.') { notLastPeriod = true; break; @@ -269,14 +269,14 @@ public bool Match( if (expressionChar == '\\') { // Escape character, try to move the expression forward again and match literally. - if (++expressionOffset == expression.Length) + if (++expressionOffset == wildcard.Length) { currentMatches[currentMatch++] = maxState; goto ExpressionFinished; } currentState = expressionOffset * 2 + 2; - expressionChar = expression[expressionOffset]; + expressionChar = wildcard[expressionOffset]; } // From this point on a name character is required to even @@ -311,7 +311,7 @@ public bool Match( MatchZero: currentMatches[currentMatch++] = currentState + 1; NextExpressionCharacter: - if (++expressionOffset == expression.Length) + if (++expressionOffset == wildcard.Length) { currentMatches[currentMatch++] = maxState; } diff --git a/tests/Pure.DI.UsageTests/Hints/OnCannotResolveHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/OnCannotResolveRegularExpressionHintScenario.cs similarity index 94% rename from tests/Pure.DI.UsageTests/Hints/OnCannotResolveHintScenario.cs rename to tests/Pure.DI.UsageTests/Hints/OnCannotResolveRegularExpressionHintScenario.cs index 956cd985..a475e975 100644 --- a/tests/Pure.DI.UsageTests/Hints/OnCannotResolveHintScenario.cs +++ b/tests/Pure.DI.UsageTests/Hints/OnCannotResolveRegularExpressionHintScenario.cs @@ -1,7 +1,7 @@ /* $v=true $p=3 -$d=OnCannotResolve hint +$d=OnCannotResolve regular expression hint $h=Hints are used to fine-tune code generation. The _OnCannotResolve_ hint determines whether to generate a partial `OnCannotResolve(...)` method to handle a scenario where an instance which cannot be resolved. $h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnCannotResolveContractTypeNameRegularExpression = string`. $f=The `OnCannotResolveContractTypeNameRegularExpression` hint helps define the set of types that require manual dependency resolution. You can use it to specify a regular expression to filter the full type name. @@ -15,7 +15,7 @@ // ReSharper disable UnusedVariable // ReSharper disable ArrangeTypeModifiers -namespace Pure.DI.UsageTests.Hints.OnCannotResolveHintScenario; +namespace Pure.DI.UsageTests.Hints.OnCannotResolveRegularExpressionHintScenario; #pragma warning disable CA1822 using Shouldly; diff --git a/tests/Pure.DI.UsageTests/Hints/OnCannotResolveWildcardHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/OnCannotResolveWildcardHintScenario.cs new file mode 100644 index 00000000..daee65e0 --- /dev/null +++ b/tests/Pure.DI.UsageTests/Hints/OnCannotResolveWildcardHintScenario.cs @@ -0,0 +1,85 @@ +/* +$v=true +$p=3 +$d=OnCannotResolve wildcard hint +$h=Hints are used to fine-tune code generation. The _OnCannotResolve_ hint determines whether to generate a partial `OnCannotResolve(...)` method to handle a scenario where an instance which cannot be resolved. +$h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnCannotResolveContractTypeNameWildcard = string`. +$f=The `OnCannotResolveContractTypeNameWildcard` hint helps define the set of types that require manual dependency resolution. You can use it to specify a wildcard to filter the full type name. +$f=For more hints, see [this](README.md#setup-hints) page. +$r=Shouldly +*/ + +// ReSharper disable ClassNeverInstantiated.Local +// ReSharper disable CheckNamespace +// ReSharper disable UnusedParameterInPartialMethod +// ReSharper disable UnusedVariable +// ReSharper disable ArrangeTypeModifiers + +namespace Pure.DI.UsageTests.Hints.OnCannotResolveWildcardHintScenario; +#pragma warning disable CA1822 + +using Shouldly; +using Xunit; +using static Hint; + +// { +//# using Pure.DI; +//# using static Pure.DI.Hint; +// } + +public class Scenario +{ + [Fact] + public void Run() + { + // Resolve = Off +// { + // OnCannotResolveContractTypeNameWildcard = string + DI.Setup(nameof(Composition)) + .Hint(OnCannotResolve, "On") + .Bind().To() + .Bind().To() + .Root("Root"); + + var composition = new Composition(); + var service = composition.Root; + service.Dependency.ToString().ShouldBe("My name"); + +// } + composition.SaveClassDiagram(); + } +} + +// { +interface IDependency; + +class Dependency(string name) : IDependency +{ + public override string ToString() => name; +} + +interface IService +{ + IDependency Dependency { get; } +} + +class Service(IDependency dependency) : IService +{ + public IDependency Dependency { get; } = dependency; +} + +partial class Composition +{ + private partial T OnCannotResolve( + object? tag, + Lifetime lifetime) + { + if (typeof(T) == typeof(string)) + { + return (T)(object)"My name"; + } + + throw new InvalidOperationException("Cannot resolve."); + } +} +// } \ No newline at end of file diff --git a/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionRegularExpressionHintScenario.cs similarity index 94% rename from tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionHintScenario.cs rename to tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionRegularExpressionHintScenario.cs index 2d42f91f..b3e24faf 100644 --- a/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionHintScenario.cs +++ b/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionRegularExpressionHintScenario.cs @@ -1,7 +1,7 @@ /* $v=true $p=2 -$d=OnDependencyInjection hint +$d=OnDependencyInjection regular expression hint $h=Hints are used to fine-tune code generation. The _OnDependencyInjection_ hint determines whether to generate partial _OnDependencyInjection_ method to control of dependency injection. $h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnDependencyInjection = On`. $f=The `OnDependencyInjectionContractTypeNameRegularExpression` hint helps identify the set of types that require injection control. You can use it to specify a regular expression to filter the full name of a type. @@ -18,7 +18,7 @@ // ReSharper disable UnusedMember.Global // ReSharper disable NotAccessedPositionalProperty.Global -namespace Pure.DI.UsageTests.Hints.OnDependencyInjectionHintScenario; +namespace Pure.DI.UsageTests.Hints.OnDependencyInjectionRegularExpressionHintScenario; using Shouldly; using Xunit; diff --git a/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionWildcardHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionWildcardHintScenario.cs new file mode 100644 index 00000000..722f395f --- /dev/null +++ b/tests/Pure.DI.UsageTests/Hints/OnDependencyInjectionWildcardHintScenario.cs @@ -0,0 +1,88 @@ +/* +$v=true +$p=2 +$d=OnDependencyInjection wildcard hint +$h=Hints are used to fine-tune code generation. The _OnDependencyInjection_ hint determines whether to generate partial _OnDependencyInjection_ method to control of dependency injection. +$h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnDependencyInjection = On`. +$f=The `OnDependencyInjectionContractTypeNameWildcard` hint helps identify the set of types that require injection control. You can use it to specify a wildcard to filter the full name of a type. +$f=For more hints, see [this](README.md#setup-hints) page. +$r=Shouldly +*/ + +// ReSharper disable ClassNeverInstantiated.Local +// ReSharper disable CheckNamespace +// ReSharper disable UnusedParameterInPartialMethod +// ReSharper disable UnusedVariable +// ReSharper disable UnusedMemberInSuper.Global +// ReSharper disable ArrangeTypeModifiers +// ReSharper disable UnusedMember.Global + +// ReSharper disable NotAccessedPositionalProperty.Global +namespace Pure.DI.UsageTests.Hints.OnDependencyInjectionWildcardHintScenario; + +using Shouldly; +using Xunit; +using static Hint; + +// { +//# using Pure.DI; +//# using static Pure.DI.Hint; +// } + +public class Scenario +{ + [Fact] + public void Run() + { + // Resolve = Off +// { + // OnDependencyInjection = On + DI.Setup(nameof(Composition)) + .Hint(OnDependencyInjectionContractTypeNameWildcard, "*IDependency") + .RootArg("id") + .Bind().To() + .Bind().To() + .Root("GetRoot"); + + var log = new List(); + var composition = new Composition(log); + var service = composition.GetRoot(33); + + log.ShouldBe(["Dependency injected"]); +// } + composition.SaveClassDiagram(); + } +} + +// { +interface IDependency; + +record Dependency(int Id) : IDependency; + +interface IService +{ + IDependency Dependency { get; } +} + +class Service(IDependency dependency) : IService +{ + public IDependency Dependency { get; } = dependency; +} + +partial class Composition +{ + private readonly List _log = []; + + public Composition(List log) : this() => + _log = log; + + private partial T OnDependencyInjection( + in T value, + object? tag, + Lifetime lifetime) + { + _log.Add($"{value?.GetType().Name} injected"); + return value; + } +} +// } \ No newline at end of file diff --git a/tests/Pure.DI.UsageTests/Hints/OnNewInstanceHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/OnNewInstanceRegularExpressionHintScenario.cs similarity index 89% rename from tests/Pure.DI.UsageTests/Hints/OnNewInstanceHintScenario.cs rename to tests/Pure.DI.UsageTests/Hints/OnNewInstanceRegularExpressionHintScenario.cs index 09afb8e6..193ea295 100644 --- a/tests/Pure.DI.UsageTests/Hints/OnNewInstanceHintScenario.cs +++ b/tests/Pure.DI.UsageTests/Hints/OnNewInstanceRegularExpressionHintScenario.cs @@ -1,7 +1,7 @@ /* $v=true $p=4 -$d=OnNewInstance hint +$d=OnNewInstance regular expression hint $h=Hints are used to fine-tune code generation. The _OnNewInstance_ hint determines whether to generate partial _OnNewInstance_ method. $h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnNewInstance = On`. $f=The `OnNewInstanceLifetimeRegularExpression` hint helps you define a set of lifetimes that require instance creation control. You can use it to specify a regular expression to filter bindings by lifetime name. @@ -17,7 +17,7 @@ // ReSharper disable ArrangeTypeModifiers // ReSharper disable UnusedMember.Global -namespace Pure.DI.UsageTests.Hints.OnNewInstanceHintScenario; +namespace Pure.DI.UsageTests.Hints.OnNewInstanceRegularExpressionHintScenario; using Shouldly; using Xunit; @@ -37,8 +37,9 @@ public void Run() // { DI.Setup(nameof(Composition)) .Hint(OnNewInstance, "On") + .Hint(OnNewInstanceLifetimeRegularExpression, "(Singleton|PerBlock)") .Bind().As(Lifetime.Singleton).To() - .Bind().To() + .Bind().As(Lifetime.PerBlock).To() .Root("Root"); var log = new List(); diff --git a/tests/Pure.DI.UsageTests/Hints/OnNewInstanceWildcardHintScenario.cs b/tests/Pure.DI.UsageTests/Hints/OnNewInstanceWildcardHintScenario.cs new file mode 100644 index 00000000..21097e6c --- /dev/null +++ b/tests/Pure.DI.UsageTests/Hints/OnNewInstanceWildcardHintScenario.cs @@ -0,0 +1,91 @@ +/* +$v=true +$p=4 +$d=OnNewInstance wildcard hint +$h=Hints are used to fine-tune code generation. The _OnNewInstance_ hint determines whether to generate partial _OnNewInstance_ method. +$h=In addition, setup hints can be comments before the _Setup_ method in the form ```hint = value```, for example: `// OnNewInstance = On`. +$f=The `OnNewInstanceImplementationTypeNameWildcard` hint helps you define a set of implementation types that require instance creation control. You can use it to specify a wildcard to filter bindings by implementation name. +$f=For more hints, see [this](README.md#setup-hints) page. +$r=Shouldly +*/ + +// ReSharper disable ClassNeverInstantiated.Local +// ReSharper disable CheckNamespace +// ReSharper disable UnusedParameterInPartialMethod +// ReSharper disable UnusedVariable +// ReSharper disable UnusedMemberInSuper.Global +// ReSharper disable ArrangeTypeModifiers +// ReSharper disable UnusedMember.Global + +namespace Pure.DI.UsageTests.Hints.OnNewInstanceWildcardHintScenario; + +using Shouldly; +using Xunit; +using static Hint; + +// { +//# using Pure.DI; +//# using static Pure.DI.Hint; +// } + +public class Scenario +{ + [Fact] + public void Run() + { + // Resolve = Off +// { + DI.Setup(nameof(Composition)) + .Hint(OnNewInstance, "On") + .Hint(OnNewInstanceImplementationTypeNameWildcard, "*Service") + .Bind().As(Lifetime.Singleton).To() + .Bind().To() + .Root("Root"); + + var log = new List(); + var composition = new Composition(log); + var service1 = composition.Root; + var service2 = composition.Root; + + log.ShouldBe([ + "Service created", + "Service created"]); +// } + composition.SaveClassDiagram(); + } +} + +// { +interface IDependency; + +class Dependency : IDependency +{ + public override string ToString() => nameof(Dependency); +} + +interface IService +{ + IDependency Dependency { get; } +} + +class Service(IDependency dependency) : IService +{ + public IDependency Dependency { get; } = dependency; + + public override string ToString() => nameof(Service); +} + +internal partial class Composition +{ + private readonly List _log = []; + + public Composition(List log) : this() => + _log = log; + + partial void OnNewInstance( + ref T value, + object? tag, + Lifetime lifetime) => + _log.Add($"{typeof(T).Name} created"); +} +// } \ No newline at end of file