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 c4e07dc
Show file tree
Hide file tree
Showing 42 changed files with 237 additions and 37 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,8 @@ 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)

## Generated Code

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
3 changes: 2 additions & 1 deletion readme/FooterTemplate.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
- [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)

## Generated Code

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
12 changes: 7 additions & 5 deletions readme/class-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
[![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 +45,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 +59,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 +93,7 @@ partial class Composition
_argDependencyName = _root._argDependencyName;
}

public IService MyRoot
public IService MyService
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
Expand All @@ -108,7 +110,7 @@ Class diagram:
classDiagram
class Composition {
<<partial>>
+IService MyRoot
+IService MyService
}
Dependency --|> IDependency
class Dependency {
Expand All @@ -126,7 +128,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
5 changes: 4 additions & 1 deletion readme/composition-roots.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

[![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.
> [!NOTE]
> 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
37 changes: 36 additions & 1 deletion readme/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

[![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 generalized 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 +55,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 generalized 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
1 change: 1 addition & 0 deletions readme/injections-of-abstractions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Basics/InjectionsOfAbstractionsScenario.cs)

This example demonstrates the recommended approach of using abstractions instead of implementations when injecting dependencies.
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. 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.


```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
10 changes: 5 additions & 5 deletions readme/property-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ DI.Setup(nameof(Composition))
.Bind<IService>().To<Service>()

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

var composition = new Composition();
var service = composition.Root;
var service = composition.MyService;
service.Dependency.ShouldBeOfType<Dependency>();
```

Expand All @@ -53,7 +53,7 @@ partial class Composition
_root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root;
}

public IService Root
public IService MyService
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
Expand All @@ -72,7 +72,7 @@ Class diagram:
classDiagram
class Composition {
<<partial>>
+IService Root
+IService MyService
}
Dependency --|> IDependency
class Dependency {
Expand All @@ -89,7 +89,7 @@ classDiagram
class IService {
<<interface>>
}
Composition ..> Service : IService Root
Composition ..> Service : IService MyService
Service *-- Dependency : IDependency
```

2 changes: 1 addition & 1 deletion readme/resolve-hint.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var service = composition.Root;
var dependencyRoot = composition.DependencyRoot;
```

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
8 changes: 8 additions & 0 deletions readme/resolve-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ var otherService2 = composition.Resolve(typeof(IService),"My Tag");
var otherService3 = composition.OtherService; // Gets the composition through the public root
```

_Resolve_ methods are similar to calls to the roots of a composition. Composition roots are common properties. Their use is efficient and does not cause exceptions. And that is why it is recommended to use them. In contrast, _Resolve_ methods have a number of disadvantages:

- They provide access to an unlimited set of dependencies.

- Their use can potentially lead to runtime exceptions, for example, when the corresponding root has not been defined.

- Lead to performance degradation because they search for the root of a composition based on its type.

The following partial class will be generated:

```c#
Expand Down
4 changes: 3 additions & 1 deletion readme/root-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

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

Sometimes it is necessary to pass some state to the composition root to use it when resolving dependencies. To do this, just use the `RootArg<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 root of a composition that uses at least one root argument is prepended as a method, not a property. It is important to remember that the method will only display arguments that are used in the object graph of that composition root. Arguments that are not involved will not be added to the method arguments. It is best to use unique argument names so that there are no collisions.
Sometimes it is necessary to pass some state to the composition to use it when resolving dependencies. To do this, just use the `RootArg<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 root of a composition that uses at least one root argument is prepended as a method, not a property. It is important to remember that the method will only display arguments that are used in the object graph of that composition root. Arguments that are not involved will not be added to the method arguments. It is best to use unique argument names so that there are no collisions.
> [!NOTE]
> Actually, root 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
14 changes: 14 additions & 0 deletions readme/singleton.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ service1.Dependency1.ShouldBe(service1.Dependency2);
service2.Dependency1.ShouldBe(service1.Dependency1);
```

Some sources advise using objects with a _Singleton_ lifecycle as often as possible, but the following details must be considered:

- For .NET the default behavior is to create a new instance of the type each time it is needed, other behavior requires, additional logic that is not free and requires additional resources.

- The use of _Singleton_, adds a requirement for thread-safety controls on their use, since singletons are more likely to share their state between different threads without even realizing it.

- The thread-safety control should be automatically extended to all dependencies that _Singleton_ uses, since their state is also now shared.

- Logic for thread-safety control can be resource-costly, error-prone, interlocking, and difficult to test.

- _Singleton_ can retain dependency references longer than their expected lifetime, this is especially significant for objects that hold "non-renewable" resources, such as the operating system Handler.

- Sometimes additional logic is required to dispose of _Singleton_.

The following partial class will be generated:

```c#
Expand Down
2 changes: 1 addition & 1 deletion readme/threadsafe-hint.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var composition = new Composition();
var service = composition.Root;
```

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
3 changes: 2 additions & 1 deletion readme/tostring-hint.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ var composition = new Composition();
string classDiagram = composition.ToString();
```

For more hints, see [this](https://github.com/DevTeam/Pure.DI/blob/master/README.md#setup-hints) page.
Developers who start using DI technology often complain that they stop seeing the structure of the application because it is difficult to understand how it is built. To make life easier, you can add the _ToString_ hint by telling the generator to create a `ToString()` method.
For more hints, see [this](README.md#setup-hints) page.

The following partial class will be generated:

Expand Down
Loading

0 comments on commit c4e07dc

Please sign in to comment.