Skip to content

Commit

Permalink
Update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolayPianikov committed May 22, 2024
1 parent f77b2dd commit eed8906
Show file tree
Hide file tree
Showing 44 changed files with 305 additions and 60 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,9 @@ dotnet run
- [https://devteam.github.io/Pure.DI/](https://devteam.github.io/Pure.DI/)
- Git repo with examples
- [Schrödinger's cat](https://github.com/DevTeam/Pure.DI.Example)
- [How to use Pure.DI to create libraries](https://github.com/DevTeam/Pure.DI.Solution)

- [How to use Pure.DI to create and test libraries](https://github.com/DevTeam/Pure.DI.Solution)
- [Another example with cats](https://github.com/DevTeam/Pure.DI.Example)
nm
## Generated Code

Each generated class, hereafter called a _composition_, must be customized. Setup starts with a call to the `Setup(string compositionTypeName)` method:
Expand Down
3 changes: 3 additions & 0 deletions readme/Avalonia.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

This example demonstrates the creation of a [Avalonia](https://avaloniaui.net/) application in the pure DI paradigm using the _Pure.DI_ code generator.

> [!NOTE]
> [Another example](samples/SingleRootAvaloniaApp) with Avalonia shows how to create an application with a single composition root.
The definition of the composition is in [Composition.cs](/samples/AvaloniaApp/Composition.cs). You must not forget to define any necessary composition roots, for example, these can be view models such as _ClockViewModel_:

```csharp
Expand Down
3 changes: 3 additions & 0 deletions readme/AvaloniaPageTemplate.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

This example demonstrates the creation of a [Avalonia](https://avaloniaui.net/) application in the pure DI paradigm using the _Pure.DI_ code generator.

> [!NOTE]
> [Another example](samples/SingleRootAvaloniaApp) with Avalonia shows how to create an application with a single composition root.
The definition of the composition is in [Composition.cs](/samples/AvaloniaApp/Composition.cs). You must not forget to define any necessary composition roots, for example, these can be view models such as _ClockViewModel_:

```csharp
Expand Down
5 changes: 3 additions & 2 deletions readme/FooterTemplate.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
- [https://devteam.github.io/Pure.DI/](https://devteam.github.io/Pure.DI/)
- Git repo with examples
- [Schrödinger's cat](https://github.com/DevTeam/Pure.DI.Example)
- [How to use Pure.DI to create libraries](https://github.com/DevTeam/Pure.DI.Solution)

- [How to use Pure.DI to create and test libraries](https://github.com/DevTeam/Pure.DI.Solution)
- [Another example with cats](https://github.com/DevTeam/Pure.DI.Example)
nm
## Generated Code

Each generated class, hereafter called a _composition_, must be customized. Setup starts with a call to the `Setup(string compositionTypeName)` method:
Expand Down
2 changes: 2 additions & 0 deletions readme/check-for-a-root.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ Composition.HasRoot(typeof(IComparable)).ShouldBeFalse();

```

For more hints, see [this](README.md#setup-hints) page.

The following partial class will be generated:

```c#
Expand Down
13 changes: 8 additions & 5 deletions readme/class-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Basics/ClassArgumentsScenario.cs)

Sometimes you need to pass some state to a composition class to use it when resolving dependencies. To do this, just use the `Arg<T>(string argName)` method, specify the type of argument and its name. You can also specify a tag for each argument. You can then use them as dependencies when building the object graph. If you have multiple arguments of the same type, just use tags to distinguish them. The values of the arguments are manipulated when you create a composition class by calling its constructor. It is important to remember that only those arguments that are used in the object graph will appear in the constructor. Arguments that are not involved will not be added to the constructor arguments.
> [!NOTE]
> Actually, class arguments work like normal bindings. The difference is that they bind to the values of the arguments. These values will be implemented as dependencies wherever they are required.


```c#
Expand Down Expand Up @@ -43,7 +46,7 @@ DI.Setup(nameof(Composition))
.Bind<IService>().To<Service>()

// Composition root "MyRoot"
.Root<IService>("MyRoot")
.Root<IService>("MyService")

// Some kind of identifier
.Arg<int>("id")
Expand All @@ -57,7 +60,7 @@ DI.Setup(nameof(Composition))
var composition = new Composition(id: 123, serviceName: "Abc", dependencyName: "Xyz");

// service = new Service("Abc", new Dependency(123, "Xyz"));
var service = composition.MyRoot;
var service = composition.MyService;

service.Name.ShouldBe("Abc");
service.Dependency.Id.ShouldBe(123);
Expand Down Expand Up @@ -91,7 +94,7 @@ partial class Composition
_argDependencyName = _root._argDependencyName;
}

public IService MyRoot
public IService MyService
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
Expand All @@ -108,7 +111,7 @@ Class diagram:
classDiagram
class Composition {
<<partial>>
+IService MyRoot
+IService MyService
}
Dependency --|> IDependency
class Dependency {
Expand All @@ -126,7 +129,7 @@ classDiagram
class IService {
<<interface>>
}
Composition ..> Service : IService MyRoot
Composition ..> Service : IService MyService
Dependency o-- Int32 : Argument "id"
Dependency o-- String : Argument "dependencyName"
Service o-- String : "my service name" Argument "serviceName"
Expand Down
6 changes: 5 additions & 1 deletion readme/composition-roots.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

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

This example demonstrates several ways to create a composition root. There is no limit to the number of roots, but you should consider limiting the number of roots. Ideally, an application should have a single composition root.
This example demonstrates several ways to create a composition root.
> [!TIP]
> There is no limit to the number of roots, but you should consider limiting the number of roots. Ideally, an application should have a single composition root.
If you use classic DI containers, the composition is created dynamically every time you call a method similar to `T Resolve<T>()` or `object GetService(Type type)`. The root of the composition there is simply the root type of the composition of objects in memory T or Type type. There can be as many of these as you like. In the case of Pure.DI, the number of composition roots is limited because for each composition root a separate property or method is created at compile time. Therefore, each root is defined explicitly by calling the `Root(string rootName)` method.


```c#
Expand Down
1 change: 1 addition & 0 deletions readme/decorator.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

Interception is the ability to intercept calls between objects in order to enrich or change their behavior, but without having to change their code. A prerequisite for interception is weak binding. That is, if programming is abstraction-based, the underlying implementation can be transformed or improved by "packaging" it into other implementations of the same abstraction. At its core, intercept is an application of the Decorator design pattern. This pattern provides a flexible alternative to inheritance by dynamically "attaching" additional responsibility to an object. Decorator "packs" one implementation of an abstraction into another implementation of the same abstraction like a "matryoshka doll".
_Decorator_ is a well-known and useful design pattern. It is convenient to use tagged dependencies to build a chain of nested decorators, as in the example below:


Expand Down
5 changes: 4 additions & 1 deletion readme/factory.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

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

This example demonstrates how to create and initialize an instance manually. This approach is more expensive to maintain, but allows you to create objects more flexibly by passing them some state and introducing dependencies. As in the case of automatic dependency embedding, objects give up control on embedding, and the whole process takes place when the object graph is created.
This example demonstrates how to create and initialize an instance manually.
At the compilation stage, the set of dependencies that an object needs in order to be created is determined. In most cases, this happens automatically according to the set of constructors and their arguments and does not require any additional customization efforts. But sometimes it is necessary to manually create an object, as in lines of code:


```c#
Expand Down Expand Up @@ -60,6 +61,8 @@ var service = composition.MyService;
service.Dependency.IsInitialized.ShouldBeTrue();
```

This approach is more expensive to maintain, but allows you to create objects more flexibly by passing them some state and introducing dependencies. As in the case of automatic dependency embedding, objects give up control on embedding, and the whole process takes place when the object graph is created.

The following partial class will be generated:

```c#
Expand Down
45 changes: 31 additions & 14 deletions readme/func-with-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ class Clock : IClock
interface IDependency
{
int Id { get; }

int SubId { get; }
}

class Dependency(IClock clock, int id) : IDependency
class Dependency(
IClock clock,
int id,
[Tag("sub")] int subId)
: IDependency
{
public int Id => id;

public int SubId => subId;
}

interface IService
Expand All @@ -31,11 +39,11 @@ interface IService

class Service : IService
{
public Service(Func<int, IDependency> dependencyFactory) =>
public Service(Func<int, int, IDependency> dependencyFactory) =>
Dependencies = [
..Enumerable
.Range(0, 10)
.Select((_, index) => dependencyFactory(index))
.Select((_, index) => dependencyFactory(index, 99))
];

public ImmutableArray<IDependency> Dependencies { get; }
Expand All @@ -46,12 +54,16 @@ DI.Setup(nameof(Composition))
// Binds a dependency of type int
// to the source code statement "dependencyId"
.Bind<int>().To<int>("dependencyId")
.Bind<Func<int, IDependency>>()
.To<Func<int, IDependency>>(ctx =>
dependencyId =>
// Binds a dependency of type int with tag "sub"
// to the source code statement "subId"
.Bind<int>("sub").To<int>("subId")
.Bind<Func<int, int, IDependency>>()
.To<Func<int, int, IDependency>>(ctx =>
(dependencyId, subId) =>
{
// Builds up an instance of type Dependency
// referring the source code statement "dependencyId"
// referring source code statements "dependencyId"
// and source code statements "subId"
ctx.Inject<Dependency>(out var dependency);
return dependency;
})
Expand All @@ -64,6 +76,7 @@ var composition = new Composition();
var service = composition.Root;
service.Dependencies.Length.ShouldBe(10);
service.Dependencies[3].Id.ShouldBe(3);
service.Dependencies[3].SubId.ShouldBe(99);
```

The following partial class will be generated:
Expand Down Expand Up @@ -93,8 +106,11 @@ partial class Composition
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Func<int, IDependency> transientFunc1 = dependencyId =>
Func<int, int, IDependency> transientFunc1 =
[MethodImpl(MethodImplOptions.AggressiveInlining)]
(dependencyId, subId) =>
{
int transientInt324 = subId;
int transientInt323 = dependencyId;
if (_root._singletonClock39 == null)
{
Expand All @@ -107,7 +123,7 @@ partial class Composition
}
}

Dependency localDependency19 = new Dependency(_root._singletonClock39!, transientInt323);
Dependency localDependency19 = new Dependency(_root._singletonClock39!, transientInt323, transientInt324);
return localDependency19;
};
return new Service(transientFunc1);
Expand Down Expand Up @@ -243,17 +259,17 @@ classDiagram
+ object Resolve(Type type, object? tag)
}
class Dependency {
+Dependency(IClock clock, Int32 id)
+Dependency(IClock clock, Int32 id, Int32 subId)
}
Clock --|> IClock
class Clock {
+Clock()
}
class Int32
class FuncᐸInt32ˏIDependencyᐳ
class FuncᐸInt32ˏInt32ˏIDependencyᐳ
Service --|> IService
class Service {
+Service(FuncᐸInt32ˏIDependencyᐳ dependencyFactory)
+Service(FuncᐸInt32ˏInt32ˏIDependencyᐳ dependencyFactory)
}
class IClock {
<<interface>>
Expand All @@ -263,8 +279,9 @@ classDiagram
}
Dependency o-- "Singleton" Clock : IClock
Dependency *-- Int32 : Int32
Dependency *-- Int32 : "sub" Int32
Composition ..> Service : IService Root
FuncᐸInt32ˏIDependencyᐳ *-- Dependency : Dependency
Service *-- FuncᐸInt32ˏIDependencyᐳ : FuncᐸInt32ˏIDependencyᐳ
FuncᐸInt32ˏInt32ˏIDependencyᐳ *-- Dependency : Dependency
Service *-- FuncᐸInt32ˏInt32ˏIDependencyᐳ : FuncᐸInt32ˏInt32ˏIDependencyᐳ
```

38 changes: 37 additions & 1 deletion readme/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

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

Generic types are also supported, this is easy to do by binding generic types and specifying generic markers like `TT`, `TT1` etc. as generic type parameters:
Generic types are also supported.
> [!IMPORTANT]
> Instead of open generic types, as in classical DI container libraries, "marker" types are used as parameters of generalized types. Such "marker" types allow to define dependency graph more precisely.
This is easy to do by binding generic types and specifying generic markers like `TT`, `TT1` etc. as generic type parameters:


```c#
Expand Down Expand Up @@ -52,6 +56,38 @@ public IService Root
}
}
```
Even in this simple scenario, it is not possible to precisely define the binding of an abstraction to its implementation using open generic types:
```c#
.Bind(typeof(IMap<,>)).To(typeof(Map<,>))
```
You can try to match them by order or by name derived from the .NET type reflection. But this is not reliable, since order and name matching is not guaranteed. For example, there is some interface with two arguments of type _key and _value_. But in its implementation the sequence of type arguments is mixed up: first comes the _value_ and then the _key_ and the names do not match:
```c#
class Map<TV, TK>: IMap<TKey, TValue> { }
```
At the same time, the marker types `TT1` and `TT2` handle this easily. They determine the exact correspondence between the type arguments in the interface and its implementation:
```c#
.Bind<IMap<TT1, TT2>>().To<Map<TT2, TT1>>()
```
The first argument of the type in the interface, corresponds to the second argument of the type in the implementation and is a _key_. The second argument of the type in the interface, corresponds to the first argument of the type in the implementation and is a _value_. This is a simple example. Obviously, there are plenty of more complex scenarios where tokenized types will be useful.
Marker types are regular .NET types marked with a special attribute, such as:
```c#
[GenericTypeArgument]
internal abstract class TT1 { }

[GenericTypeArgument]
internal abstract class TT2 { }
```
This way you can easily create your own, including making them fit the constraints on the type parameter, for example:
```c#
[GenericTypeArgument]
internal struct TTS { }

[GenericTypeArgument]
internal interface TTDisposable: IDisposable { }

[GenericTypeArgument]
internal interface TTEnumerator<out T>: IEnumerator<T> { }
```

The following partial class will be generated:

Expand Down
5 changes: 5 additions & 0 deletions readme/injections-of-abstractions.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ var root = composition.Root;
root.Run();
```

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

The following partial class will be generated:

```c#
Expand Down
16 changes: 16 additions & 0 deletions readme/interception.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ var service = composition.Root;
service.GetMessage().ShouldBe("Hello World !!!");
```

Using an intercept gives you the ability to add end-to-end functionality such as:

- Logging

- Action logging

- Performance monitoring

- Security

- Caching

- Error handling

- Providing resistance to failures, etc.

The following partial class will be generated:

```c#
Expand Down
2 changes: 1 addition & 1 deletion readme/oncannotresolve-hint.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ service.Dependency.ToString().ShouldBe("Dependency with name");
```

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.
For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page.
For more hints, see [this](README.md#setup-hints) page.

The following partial class will be generated:

Expand Down
2 changes: 1 addition & 1 deletion readme/ondependencyinjection-hint.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ log.ShouldBe(ImmutableArray.Create("Dependency injected"));
```

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.
For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page.
For more hints, see [this](README.md#setup-hints) page.

The following partial class will be generated:

Expand Down
2 changes: 1 addition & 1 deletion readme/onnewinstance-hint.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ log.ShouldBe(ImmutableArray.Create("Dependency", "Service", "Service"));
```

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.
For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page.
For more hints, see [this](README.md#setup-hints) page.

The following partial class will be generated:

Expand Down
Loading

0 comments on commit eed8906

Please sign in to comment.