Skip to content

Commit cab544c

Browse files
Regenerate the documentation pages with the new scenario guidance
Generated by the readme target from the updated scenarios. Only the description and footer sections change; the generated code, class diagrams and link blocks are left as they were.
1 parent c2c4dc8 commit cab544c

191 files changed

Lines changed: 1028 additions & 245 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎readme/a-few-partial-classes.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#### A few partial classes
22

33
The setting code for one Composition can be located in several methods and/or in several partial classes.
4+
Each `DI.Setup()` call that does not name a composition adds to the composition of the enclosing partial class, so the parts merge regardless of which file or method they live in.
45

56

67
```c#
@@ -76,9 +77,14 @@ dotnet run
7677

7778
</details>
7879

80+
This is how a large composition stays readable: one part per layer - infrastructure, domain, public API - each in its own file, often owned by a different team.
7981
>[!NOTE]
8082
>Splitting composition setup across multiple partial classes can improve organization for large compositions but may reduce readability if overused.
8183
84+
All parts share one set of roots and one set of bindings, so a later part can override a binding of an earlier one and `DIW000` reports the duplicate.
85+
A part may leave `Resolve` enabled or disabled by hint, but a later part overrides the earlier hint, so keep hints in one place.
86+
Prefer `DependsOn` with named `Internal` setups ([dependent compositions](dependent-compositions.md)) when the layers must be reusable by more than one composition.
87+
8288
<details>
8389
<summary>The following partial class will be generated</summary>
8490

‎readme/accumulators.md‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#### Accumulators
22

3-
Accumulators allow you to accumulate instances of certain types and lifetimes.
4-
Use this when you need an aggregated view of created dependencies (for diagnostics, telemetry, or registries).
3+
Accumulators allow you to accumulate instances of certain types and lifetimes: `Accumulate<T, TAccumulator>(...)` adds every created `T` whose lifetime is in the list to a `TAccumulator` that needs only a parameterless constructor and an `Add(T)` method.
4+
Use this when you need an aggregated view of created dependencies (for diagnostics, telemetry, or registries) instead of a single injected instance, and when the registry should not be threaded through constructors by hand.
5+
The accumulator is an ordinary dependency of the composition, so a root returns it like any other service.
56

67

78
```c#
@@ -87,8 +88,11 @@ dotnet run
8788

8889
</details>
8990

90-
Limitations: accumulation order depends on object creation order in the graph, so do not treat it as a stable business ordering.
91+
In a real project this is the shape of a start-up snapshot: collect the telemetry sources, plug-ins, handlers or validators the graph actually built, then hand the collected list to a diagnostics page or a health check.
92+
Limitations: accumulation order depends on object creation order in the graph, so do not treat it as a stable business ordering; sort or key the result before you expose it.
9193
The dependencies of a `Singleton` or `Scoped` instance belong to that shared instance, so `Transient`, `PerBlock` and `PerResolve` accumulators do not collect them.
94+
The accumulator itself is a `PerBlock` dependency: it is built for the root access that asks for it and describes the objects created in that access, so request it from the same root as the objects you want to observe.
95+
Prefer [Enumerable](enumerable.md) when you only need every implementation injected as a collection, and a `Singleton` collector service when the view must outlive a single root access.
9296
See also: [Enumerable](enumerable.md), [Lifetimes](transient.md).
9397

9498
<details>

‎readme/advanced-interception.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,15 @@ dotnet run
151151

152152
>[!NOTE]
153153
>Advanced interception provides high-performance proxy generation for scenarios where runtime interception overhead must be minimized.
154+
154155
Use this only when decorators are not expressive enough or when an existing interception ecosystem is required. For simple cross-cutting behavior, decorators are easier to read and cheaper to reason about.
155156

157+
The realistic case is an application that must log every call on a latency-sensitive path, or that already depends on an interceptor library and cannot express the behavior as a decorator. Compiling the proxy constructor once per service type removes the repeated reflection that dominates the naive hook.
158+
>[!WARNING]
159+
>The proxy still sits between caller and target on every call, so a method that runs in nanoseconds stays dominated by the interception. Measure before adopting this, and exclude value-type dependencies - the hook already returns them untouched because they cannot be proxied.
160+
161+
The `OnDependencyInjection` hook runs for every injected dependency, so proxy the contracts you care about with `OnDependencyInjectionContractTypeNameWildcard` instead of wrapping the whole graph. Prefer [Decorator](decorator.md) when you can wrap at compile time, and [Interception](interception.md) when the plain proxy generator is fast enough.
162+
156163
<details>
157164
<summary>The following partial class will be generated</summary>
158165

‎readme/allows-ref-struct-factory.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ This manual factory pattern keeps `ReadOnlySpan<T>` and other stack-only values
7777
When the standard delegate shape is enough, prefer the generated default `Func<ReadOnlySpan<char>, T>` binding. It uses local values in the generated delegate invocation and does not require a manual `lock`.
7878
The fluent contract API accepts ref-like type arguments in `Bind<T>()`, `RootBind<T>()`, `Root<T>()`, and `DefaultLifetime<T>()`. The `Transient<T>()`, `PerResolve<T>()`, and `PerBlock<T>()` shortcuts support ref-like implementations and factory results because generated instances remain local to root execution.
7979
`Singleton<T>()` and `Scoped<T>()` keep their implementation and factory-result type heap-safe. Their parameterized factory overloads accept ref-like dependency types so Pure.DI can apply its stored-lifetime validation and report `DIE046` instead of failing earlier with the C# generic-argument diagnostic `CS9244`.
80+
This is the escape hatch for a hot path that must parse without allocating a `string` per call: the span stays inside one delegate invocation, and the factory validation happens at compile time. When the only stack-only value is a `ReadOnlySpan<char>` argument, the generated default binding is shorter and needs no lock.
8081

8182
<details>
8283
<summary>The following partial class will be generated</summary>

‎readme/array.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#### Array
22

3-
Specifying `T[]` as the injection type allows instances from all bindings that implement the `T` type to be injected.
3+
Specifying `T[]` as the injection type allows instances from all bindings that implement the `T` type to be injected. It is the way to get "every handler of this contract" without registering a composite by hand.
44

55

66
```c#
@@ -93,6 +93,10 @@ In addition to arrays, other collection types are also supported, such as:
9393
- System.Collections.Immutable.ImmutableQueue<T>
9494
- System.Collections.Immutable.IImmutableStack<T>
9595
And of course this list can easily be supplemented on its own.
96+
The array contains every binding of `ISensor`, tagged or not, in the order the bindings are declared; here the untagged `TemperatureSensor` comes before the tagged `WindSensor`. An array is created eagerly, so all elements exist as soon as the consumer is created.
97+
Adding a binding for the same contract changes the length of every injected array of that contract, which is why a test that asserts the count or the order is worth keeping. A consumer that must not see a particular implementation needs a separate contract or a tag on the collection parameter.
98+
>[!TIP]
99+
>Prefer `IEnumerable<T>` when the consumer may not need every element, and an array or `ImmutableArray<T>` when it reads them repeatedly.
96100
97101
<details>
98102
<summary>The following partial class will be generated</summary>

‎readme/arraypool-buffer.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ dotnet run
109109

110110
This pattern is useful for serialization, compression, protocol framing, and batch export pipelines. Keep the owner lifetime short, clear sensitive buffers before returning them, and do not store the rented `Memory<T>` after the owner is disposed.
111111
The important DI detail is ownership: bind or auto-bind the owner as the dependency that is tracked and disposed, not the raw array. The pool itself is a built-in BCL dependency; the owner is the application-specific object that defines rent size, cleanup, and return rules. `Owned<T>` keeps that cleanup tied to one root call instead of the whole composition.
112+
>[!WARNING]
113+
>A rented array is never freed by the GC in a useful way: forgetting the return keeps the pool growing until every new rent allocates a fresh array. The owner must be disposed on every exit path, including exceptions, which is why `Owned<T>` or a `using` is the right boundary.
114+
115+
Prefer pooling only where a profiler showed buffer churn. For small fixed sizes a `stackalloc`-backed span is cheaper, and for a single short-lived copy a plain `new byte[]` is simpler. See [Span and ReadOnlySpan](span-and-readonlyspan.md) for stack-only values and [Object pool](object-pool.md) for pooled objects that are not arrays.
112116

113117
<details>
114118
<summary>The following partial class will be generated</summary>

‎readme/async-disposable-scope.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ dotnet run
117117
>[!NOTE]
118118
>Async disposable scope is essential for scenarios requiring proper async cleanup of scoped resources.
119119
120+
A per-request session that holds a network stream, a database session or an open file is exactly this case: the scope ends when the request ends, and the release must await the network.
121+
Each session is a child composition, so scoped instances are unique per session and singletons stay shared with the parent. Disposing a session releases only what that session created.
122+
>[!WARNING]
123+
>A session that is dropped without `DisposeAsync` keeps its scoped resources alive until the GC runs finalizers. In a request pipeline, tie the release to the same place that ends the request, not to a background cleanup.
124+
125+
Prefer `await using` inside an `async` method so the dispose happens on every exit path, including exceptions. When the scoped resources are synchronous, the plain [Scope](scope.md) variant avoids the extra state machine.
126+
120127
<details>
121128
<summary>The following partial class will be generated</summary>
122129

‎readme/async-disposable-singleton.md‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#### Async disposable singleton
22

3-
If at least one of these objects implements the `IAsyncDisposable` interface, then the composition implements `IAsyncDisposable` as well. To dispose of all created singleton instances in an asynchronous manner, simply dispose of the composition instance in an asynchronous manner:
3+
If at least one of these objects implements the `IAsyncDisposable` interface, then the composition implements `IAsyncDisposable` as well. To dispose of all created singleton instances in an asynchronous manner, simply dispose of the composition instance in an asynchronous manner. The composition keeps both contracts: a synchronous `Dispose` still works, but it blocks on each asynchronous release.
44

55

66
```c#
@@ -105,6 +105,13 @@ dotnet run
105105
>[!NOTE]
106106
>Async disposable singleton ensures proper async cleanup of all singleton resources when the composition is disposed.
107107
108+
Audit writers, message producers, flush-on-exit telemetry and connection pools are the usual owners of this contract: their release performs real I/O and must not block a shutdown thread.
109+
>[!IMPORTANT]
110+
>Use `await using` or `await DisposeAsync()`. Calling `Dispose()` only blocks: for an async-only resource the generated code waits on `DisposeAsync().GetAwaiter().GetResult()`, which inside a synchronization context can deadlock and on a shutdown thread turns every async release into a blocking call.
111+
112+
Disposal order is reverse to creation and each failure is reported separately through the `OnDisposeException` or `OnDisposeAsyncException` partial method, so one broken resource does not prevent the others from being released. Override them to log, otherwise the failure is silent.
113+
Prefer this whenever a singleton holds I/O; when the resource closes synchronously, [Disposable singleton](disposable-singleton.md) is enough and simpler.
114+
108115
<details>
109116
<summary>The following partial class will be generated</summary>
110117

‎readme/async-enumerable.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ dotnet run
7878
>[!NOTE]
7979
>IAsyncEnumerable<T> provides efficient lazy enumeration for scenarios where you need to process many instances without loading them all into memory at once.
8080
81+
Each element is created when the consumer reaches it, so a health check that fails early can stop the loop and the later checks are never constructed. The order follows the binding declaration order, exactly as with `IEnumerable<T>`, and here the consumer collects the results into a list of its own.
82+
The sequence itself carries no cancellation token: pass the token to the consumer's method and stop the `await foreach`, as `GetChecksAsync` does by returning a materialized list. Do not keep the enumerable in a field and enumerate it from several threads.
83+
>[!TIP]
84+
>Prefer `IAsyncEnumerable<T>` only when element creation or consumption really awaits work; for plain handlers `IEnumerable<T>` is cheaper and easier to test.
85+
8186
<details>
8287
<summary>The following partial class will be generated</summary>
8388

‎readme/async-root.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ dotnet run
6464
>[!NOTE]
6565
>Async roots are useful when you need to perform asynchronous initialization or when your services require async creation.
6666
67+
The root is a method returning `Task<T>`, so it is called like any other async API: `await composition.GetBackupServiceAsync(token)`.
68+
The token is a normal root argument here, which is why such a composition keeps `Resolve = Off`: a dynamic resolve call cannot supply the token.
69+
Prefer a synchronous root when the constructors are synchronous - an async root adds a state machine to an otherwise allocation-free path.
70+
6771
<details>
6872
<summary>The following partial class will be generated</summary>
6973

0 commit comments

Comments
 (0)