Skip to content

Commit

Permalink
The Bind method without parameters should only add bindings to first-…
Browse files Browse the repository at this point in the history
…level abstractions for the type
  • Loading branch information
NikolayPianikov committed May 16, 2024
1 parent d7cafd5 commit e0239a4
Show file tree
Hide file tree
Showing 15 changed files with 55 additions and 30 deletions.
2 changes: 2 additions & 0 deletions readme/generic-composition-roots-with-constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ var service = composition.GetMyRoot<Stream, double>();
var someOtherService = composition.GetOtherService<BinaryReader>();
```

:warning: `Resolve' methods cannot be used to resolve generic composition roots.

The following partial class will be generated:

```c#
Expand Down
4 changes: 2 additions & 2 deletions readme/generic-composition-roots.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

A generic composition root is represented by a method.
Sometimes you want to be able to create composition roots with type parameters. In this case, the composition root can only be represented by a method.


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

When a generic composition root is used, `Resolve` methods cannot be used to resolve them.
:warning: `Resolve' methods cannot be used to resolve generic composition roots.

The following partial class will be generated:

Expand Down
8 changes: 4 additions & 4 deletions readme/resolve-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ var service1 = composition.Resolve<IService>();
var service2 = composition.Resolve(typeof(IService));
var service3 = composition.Resolve(typeof(IService), null);

// Resolve by tag
// Resolve by "My Tag" tag
// The next 3 lines of code do the same thing too:
var otherService1 = composition.Resolve<IService>("Other");
var otherService2 = composition.Resolve(typeof(IService),"Other");
var otherService1 = composition.Resolve<IService>("My Tag");
var otherService2 = composition.Resolve(typeof(IService),"My Tag");
var otherService3 = composition.OtherService; // Gets the composition through the public root
```

Expand Down Expand Up @@ -218,7 +218,7 @@ classDiagram
class Service {
+Service(IDependency dependency)
}
OtherService --|> IService : "Other"
OtherService --|> IService : "My Tag"
class OtherService {
+OtherService()
}
Expand Down
2 changes: 1 addition & 1 deletion readme/root-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

You might want to register some services as roots. You can use `RootBind<T>()` method in order to reduce repetitions. The registration `composition.RootBind<IDependency>().To<Dependency>()` is an equivalent to `composition.Bind<IDependency>().To<Dependency>().Root<IDependency>()`.
In general, it is recommended to define one composition root for the entire application. But sometimes it is necessary to have multiple roots. To simplify the definition of composition roots, a "hybrid" API method `RootBind<T>(string rootName)` was added. It allows you to define a binding and at the same time the root of the composition. You can it in order to reduce repetitions. The registration `composition.RootBind<IDependency>().To<Dependency>()` is an equivalent to `composition.Bind<IDependency>().To<Dependency>().Root<IDependency>()`.


```c#
Expand Down
6 changes: 1 addition & 5 deletions readme/simplified-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Service(
: IService;

// Specifies to create a partial class "Composition"
DI.Setup("Composition")
DI.Setup(nameof(Composition))
// Begins the binding definition for the implementation type itself,
// and if the implementation is not an abstract class or structure,
// for all abstract but NOT special types that are directly implemented.
Expand Down Expand Up @@ -132,7 +132,6 @@ classDiagram
Dependency --|> IDependency
Dependency --|> IOtherDependency
Dependency --|> IEnumerableᐸStringᐳ
Dependency --|> IDependencyBase
class Dependency {
+Dependency()
}
Expand All @@ -149,9 +148,6 @@ classDiagram
class IEnumerableᐸStringᐳ {
<<interface>>
}
class IDependencyBase {
<<interface>>
}
class IService {
<<interface>>
}
Expand Down
24 changes: 19 additions & 5 deletions src/Pure.DI.Core/Core/BaseSymbolsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,32 @@ namespace Pure.DI.Core;

internal class BaseSymbolsProvider : IBaseSymbolsProvider
{
public IEnumerable<ITypeSymbol> GetBaseSymbols(ITypeSymbol symbol, int deep = int.MaxValue)
public IEnumerable<ITypeSymbol> GetBaseSymbols(
ITypeSymbol symbol,
Func<ITypeSymbol, int, bool> predicate,
int maxDeepness = int.MaxValue) =>
GetBaseSymbols(symbol, predicate, maxDeepness, 0);

private static IEnumerable<ITypeSymbol> GetBaseSymbols(
ITypeSymbol symbol,
Func<ITypeSymbol, int, bool> predicate,
int maxDeepness,
int deepness)
{
if (deep < 0)
if (deepness > maxDeepness)
{
yield break;
}

deep--;
while (true)
{
yield return symbol;
foreach (var type in symbol.Interfaces.SelectMany(i => GetBaseSymbols(i, deep)))
if (predicate(symbol, deepness))
{
yield return symbol;
}

deepness++;
foreach (var type in symbol.Interfaces.SelectMany(i => GetBaseSymbols(i, predicate, maxDeepness, deepness)))
{
yield return type;
}
Expand Down
13 changes: 11 additions & 2 deletions src/Pure.DI.Core/Core/BindingBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,17 @@ public MdBinding Build(MdSetup setup)
if (type is { SpecialType: SpecialType.None, TypeKind: TypeKind.Class, IsAbstract: false })
{
baseSymbols = baseSymbolsProvider
.GetBaseSymbols(type, 1)
.Where(i => i.IsAbstract && i.SpecialType == SpecialType.None);
.GetBaseSymbols(type, (i, deepness) => deepness switch
{
0 => true,
1 when
type.TypeKind != TypeKind.Interface
&& !type.IsAbstract
&& (i.TypeKind == TypeKind.Interface || i.IsAbstract)
&& i.SpecialType == SpecialType.None
=> true,
_ => false
}, 1);
}

var contracts = new HashSet<ITypeSymbol>(baseSymbols, SymbolEqualityComparer.Default)
Expand Down
3 changes: 1 addition & 2 deletions src/Pure.DI.Core/Core/Code/BuildTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ public IEnumerable<Line> OnCreated(BuildContext ctx, Variable variable)
}

var baseTypes =
baseSymbolsProvider.GetBaseSymbols(variable.InstanceType)
.Concat(Enumerable.Repeat(variable.InstanceType, 1))
baseSymbolsProvider.GetBaseSymbols(variable.InstanceType, (_, _) => true)
.ToImmutableHashSet(SymbolEqualityComparer.Default);

var code = new LinesBuilder();
Expand Down
5 changes: 4 additions & 1 deletion src/Pure.DI.Core/Core/IBaseSymbolsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@

internal interface IBaseSymbolsProvider
{
IEnumerable<ITypeSymbol> GetBaseSymbols(ITypeSymbol symbol, int deep = int.MaxValue);
IEnumerable<ITypeSymbol> GetBaseSymbols(
ITypeSymbol symbol,
Func<ITypeSymbol, int, bool> predicate,
int maxDeepness = int.MaxValue);
}
2 changes: 1 addition & 1 deletion src/Pure.DI.Core/Core/MetadataValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ private bool Validate(MdSetup setup, in MdBinding binding)
var severityOfNotImplementedContract = setup.Hints.SeverityOfNotImplementedContract;
if (severityOfNotImplementedContract > DiagnosticSeverity.Hidden)
{
var supportedContracts = new HashSet<ITypeSymbol>(baseSymbolsProvider.GetBaseSymbols(implementationType), SymbolEqualityComparer.Default)
var supportedContracts = new HashSet<ITypeSymbol>(baseSymbolsProvider.GetBaseSymbols(implementationType, (_, _) => true), SymbolEqualityComparer.Default)
{
implementationType
};
Expand Down
6 changes: 3 additions & 3 deletions tests/Pure.DI.UsageTests/Basics/ResolveMethodsScenario.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ public void Run()
var service2 = composition.Resolve(typeof(IService));
var service3 = composition.Resolve(typeof(IService), null);

// Resolve by tag
// Resolve by "My Tag" tag
// The next 3 lines of code do the same thing too:
var otherService1 = composition.Resolve<IService>("Other");
var otherService2 = composition.Resolve(typeof(IService),"Other");
var otherService1 = composition.Resolve<IService>("My Tag");
var otherService2 = composition.Resolve(typeof(IService),"My Tag");
var otherService3 = composition.OtherService; // Gets the composition through the public root
// }
service1.ShouldBeOfType<Service>();
Expand Down
2 changes: 1 addition & 1 deletion tests/Pure.DI.UsageTests/Basics/RootBindScenario.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
$v=true
$p=17
$d=Root binding
$h=You might want to register some services as roots. You can use `RootBind<T>()` method in order to reduce repetitions. The registration `composition.RootBind<IDependency>().To<Dependency>()` is an equivalent to `composition.Bind<IDependency>().To<Dependency>().Root<IDependency>()`.
$h=In general, it is recommended to define one composition root for the entire application. But sometimes it is necessary to have multiple roots. To simplify the definition of composition roots, a "hybrid" API method `RootBind<T>(string rootName)` was added. It allows you to define a binding and at the same time the root of the composition. You can it in order to reduce repetitions. The registration `composition.RootBind<IDependency>().To<Dependency>()` is an equivalent to `composition.Bind<IDependency>().To<Dependency>().Root<IDependency>()`.
*/

// ReSharper disable ClassNeverInstantiated.Local
Expand Down
3 changes: 2 additions & 1 deletion tests/Pure.DI.UsageTests/Basics/SimplifiedBindingScenario.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
// ReSharper disable UnusedVariable
// ReSharper disable UnusedMember.Local
// ReSharper disable ArrangeTypeMemberModifiers
// ReSharper disable ClassNeverInstantiated.Global
#pragma warning disable CS9113 // Parameter is unread.
namespace Pure.DI.UsageTests.Basics.SimplifiedBindingScenario;

Expand Down Expand Up @@ -92,7 +93,7 @@ public void Run()
// Resolve = Off
// {
// Specifies to create a partial class "Composition"
DI.Setup("Composition")
DI.Setup(nameof(Composition))
// Begins the binding definition for the implementation type itself,
// and if the implementation is not an abstract class or structure,
// for all abstract but NOT special types that are directly implemented.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
$v=true
$p=4
$d=Generic composition roots with constraints
$f=:warning: `Resolve' methods cannot be used to resolve generic composition roots.
*/

// ReSharper disable ClassNeverInstantiated.Local
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
$v=true
$p=2
$d=Generic composition roots
$h=A generic composition root is represented by a method.
$f=When a generic composition root is used, `Resolve` methods cannot be used to resolve them.
$h=Sometimes you want to be able to create composition roots with type parameters. In this case, the composition root can only be represented by a method.
$f=:warning: `Resolve' methods cannot be used to resolve generic composition roots.
*/

// ReSharper disable ClassNeverInstantiated.Local
Expand Down

0 comments on commit e0239a4

Please sign in to comment.