Skip to content

Commit 2bbb619

Browse files
author
Nikolay Pianikov
committed
Update README
1 parent 54885ad commit 2bbb619

107 files changed

Lines changed: 850 additions & 637 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.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,78 @@ The _compositionTypeName_ parameter can be omitted:
513513

514514
</details>
515515

516+
<details>
517+
<summary>How to read generated code</summary>
518+
519+
Generated code is regular C# code. Read it in two passes:
520+
521+
1. First inspect the generated public API of the composition.
522+
2. Then inspect implementation details only when debugging lifetimes, scopes, or performance.
523+
524+
The public API answers the main questions:
525+
526+
- Which composition class was generated from `DI.Setup(...)`.
527+
- Which roots are available as properties or methods.
528+
- Which constructor arguments are required by the composition.
529+
- Whether `Resolve`/`ResolveByTag` methods, scopes, `Dispose`, or `DisposeAsync` were generated.
530+
531+
For example, this setup:
532+
533+
```c#
534+
DI.Setup("Composition")
535+
.Arg<string>("connectionString")
536+
.RootArg<Guid>("userId")
537+
.Bind<IRepository>().To<Repository>()
538+
.Bind<IService>().To<Service>()
539+
.Root<IService>("CreateService");
540+
```
541+
542+
produces a composition API shaped like this:
543+
544+
```c#
545+
partial class Composition
546+
{
547+
public Composition(string connectionString) { ... }
548+
549+
public IService CreateService(Guid userId) { ... }
550+
}
551+
```
552+
553+
The implementation body shows how Pure.DI creates the graph:
554+
555+
- `new Implementation(...)` calls show constructor injection.
556+
- Private fields usually represent cached singleton or scoped instances.
557+
- Lock statements appear when thread-safe access is required.
558+
- Local variables named for per-resolve or per-block lifetimes show reuse inside one generated root body.
559+
- `Dispose` and `DisposeAsync` release tracked singleton and scoped disposable instances.
560+
561+
Use `Hint.FormatCode` only when reading or presenting generated code. It can increase compilation time and memory usage.
562+
563+
</details>
564+
565+
<details>
566+
<summary>Generated API reference</summary>
567+
568+
| Setup element | Generated API |
569+
|----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
570+
| `DI.Setup("Composition")` | A partial class named `Composition`, unless the setup kind prevents class generation. |
571+
| `.Root<T>("Name")` | A public property named `Name`, or a method named `Name` when the root uses root arguments or generic type arguments. |
572+
| `.Root<T>()` | An anonymous private root. It is available only through generated `Resolve`/`ResolveByTag` methods when those methods can resolve it. |
573+
| `.Arg<T>("name")` | A composition constructor parameter when the argument is used by at least one root graph. |
574+
| `.RootArg<T>("name")` | A parameter on root methods that use this value. Roots with root arguments cannot be resolved by `Resolve`/`ResolveByTag`. |
575+
| `Lifetime.Transient` | A new instance is created at each injection site. |
576+
| `Lifetime.Singleton` | A private cached field is generated and reused by the composition. |
577+
| `Lifetime.Scoped` | Scope-related constructors/members are generated and the instance is reused inside a scope. |
578+
| `Lifetime.PerResolve` | A local value is reused during one root or `Resolve` call. |
579+
| `Lifetime.PerBlock` | A local value is reused inside a generated code block. |
580+
| Disposable singleton/scoped dependency | `Dispose` and/or `DisposeAsync` are generated on the composition. |
581+
| `Hint.Resolve = Off` | `Resolve`/`ResolveByTag` methods and anonymous roots are not generated. Use named roots directly. |
582+
| `Hint.ToString = On` | `ToString()` returns a Mermaid class diagram of the composition. |
583+
| `Hint.Comments = Off` | XML documentation comments are not generated for the composition API. |
584+
| `Hint.FormatCode = On` | Generated code is formatted for easier reading. |
585+
586+
</details>
587+
516588
<details>
517589
<summary>Setup arguments</summary>
518590

@@ -1978,6 +2050,17 @@ See also:
19782050
<summary>Comments</summary>
19792051

19802052
Pure.DI can copy comments from setup calls into generated documentation comments for the composition class, composition arguments, and composition roots.
2053+
When no user comment is provided, Pure.DI generates documentation for the generated API so the composition can be inspected from IntelliSense.
2054+
2055+
Generated comments describe:
2056+
2057+
- The composition roots exposed by the generated composition class.
2058+
- The root contract, implementation type, tag, and lifetime.
2059+
- Whether a root is a property or a method.
2060+
- Composition constructor parameters created from used `Arg<T>(...)` values.
2061+
- Root method parameters created from used `RootArg<T>(...)` values.
2062+
- `Resolve`/`ResolveByTag` limitations for roots that require root arguments.
2063+
- `Dispose`/`DisposeAsync` behavior for tracked singleton and scoped disposable instances.
19812064

19822065
Use regular `//` comments before API calls when you want Pure.DI to include the text in the generated documentation:
19832066

@@ -2016,6 +2099,15 @@ public IService Service
20162099

20172100
For other setup calls, such as `Arg<T>(...)`, comments are used as documentation text in the generated constructor documentation. Use regular `//` comments there unless you want XML markup to be shown as text.
20182101

2102+
To suppress generated documentation comments, turn comments off for the setup:
2103+
2104+
```c#
2105+
DI.Setup("Composition")
2106+
.Hint(Hint.Comments, "Off")
2107+
.Bind<IService>().To<Service>()
2108+
.Root<IService>("Service");
2109+
```
2110+
20192111
</details>
20202112

20212113
<details>
@@ -2054,6 +2146,65 @@ flowchart TD
20542146

20552147
</details>
20562148

2149+
<details>
2150+
<summary>Debugging generated code</summary>
2151+
2152+
Use this workflow when you need to inspect or debug the generated composition:
2153+
2154+
1. Save generated files in the project.
2155+
2156+
```xml
2157+
<PropertyGroup>
2158+
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
2159+
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
2160+
</PropertyGroup>
2161+
```
2162+
2163+
2. Enable formatting only while debugging.
2164+
2165+
```c#
2166+
DI.Setup("Composition")
2167+
.Hint(Hint.FormatCode, "On")
2168+
.Bind<IService>().To<Service>()
2169+
.Root<IService>("Service");
2170+
```
2171+
2172+
3. Rebuild the project and open the generated `Composition.g.cs` file under the configured generated files folder.
2173+
2174+
4. Start with the generated public API:
2175+
2176+
- composition constructors;
2177+
- root properties and root methods;
2178+
- `Resolve`/`ResolveByTag` methods;
2179+
- scope factory methods;
2180+
- `Dispose`/`DisposeAsync`.
2181+
2182+
5. Then inspect the root body that creates the graph. Constructor calls show the exact dependency path, private fields show cached singleton/scoped values, and local variables show per-resolve/per-block reuse.
2183+
2184+
6. For a structural view, enable `Hint.ToString` and render the Mermaid diagram:
2185+
2186+
```c#
2187+
DI.Setup("Composition")
2188+
.Hint(Hint.ToString, "On")
2189+
.Bind<IService>().To<Service>()
2190+
.Root<IService>("Service");
2191+
2192+
var diagram = new Composition().ToString();
2193+
```
2194+
2195+
7. If an anonymous root is hard to step through, disable lightweight anonymous roots for debugging:
2196+
2197+
```c#
2198+
DI.Setup("Composition")
2199+
.Hint(Hint.LightweightAnonymousRoot, "Off")
2200+
.Bind<IService>().To<Service>()
2201+
.Root<IService>();
2202+
```
2203+
2204+
Turn `FormatCode`, `ToString`, and extra debugging hints off again when they are no longer needed.
2205+
2206+
</details>
2207+
20572208
## Project template
20582209

20592210
Install the DI template [Pure.DI.Templates](https://www.nuget.org/packages/Pure.DI.Templates)
@@ -2131,6 +2282,62 @@ You can set project properties to save generated files and control their storage
21312282

21322283
</details>
21332284

2285+
<details>
2286+
<summary>Generated code troubleshooting</summary>
2287+
2288+
### Generated files are not visible
2289+
2290+
Set `EmitCompilerGeneratedFiles` to `true`, rebuild the project, and check the generated files folder configured by `CompilerGeneratedFilesOutputPath`. If the folder is empty, run `dotnet build-server shutdown`, rebuild, and check that the project references the `Pure.DI` package.
2291+
2292+
### The root was generated as a method, not a property
2293+
2294+
A root becomes a method when it needs runtime data, such as `RootArg<T>(...)`, or when the root itself is generic. Call it directly and pass the required arguments:
2295+
2296+
```c#
2297+
var service = composition.CreateService(userId);
2298+
```
2299+
2300+
### Resolve methods were not generated
2301+
2302+
Check `Hint.Resolve`. When it is `Off`, `Resolve`/`ResolveByTag` methods are intentionally omitted and anonymous roots are not generated. Use named roots instead:
2303+
2304+
```c#
2305+
var service = composition.Service;
2306+
```
2307+
2308+
### Resolve cannot create a root with root arguments
2309+
2310+
`Resolve`/`ResolveByTag` methods do not have a place to pass root arguments. Use the generated root method directly, or disable `Resolve` with `Hint.Resolve = Off` to avoid warnings and keep the generated API explicit.
2311+
2312+
### A lock appears in generated code
2313+
2314+
Pure.DI generates synchronization for thread-safe access to cached instances when needed. To remove it only when composition access is known to be single-threaded, use:
2315+
2316+
```c#
2317+
DI.Setup("Composition")
2318+
.Hint(Hint.ThreadSafe, "Off");
2319+
```
2320+
2321+
### Dispose or DisposeAsync was generated
2322+
2323+
The composition tracks singleton and scoped instances that implement `IDisposable` or `IAsyncDisposable`. Dispose the composition when it owns such instances:
2324+
2325+
```c#
2326+
using var composition = new Composition();
2327+
```
2328+
2329+
or:
2330+
2331+
```c#
2332+
await using var composition = new Composition();
2333+
```
2334+
2335+
### Generated code is hard to read
2336+
2337+
Temporarily enable `Hint.FormatCode` and save generated files with `EmitCompilerGeneratedFiles`. For graph-level inspection, enable `Hint.ToString` and use the Mermaid diagram.
2338+
2339+
</details>
2340+
21342341
<details>
21352342
<summary>Performance profiling</summary>
21362343

readme/EnumDetails.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,15 @@ partial class Enum
106106
public partial CompositionRoot TestPureDIByCR()
107107
{
108108
[MethodImpl(MethodImplOptions.AggressiveInlining)]
109-
IEnumerable<IService3> EnumerationOf_perBlockIEnumerable72()
109+
IEnumerable<IService3> EnumerationOf_perBlockIEnumerableIService3()
110110
{
111111
yield return new Service3(new Service4(), new Service4());
112112
yield return new Service3v2(new Service4(), new Service4());
113113
yield return new Service3v3(new Service4(), new Service4());
114114
yield return new Service3v4(new Service4(), new Service4());
115115
}
116116

117-
return new CompositionRoot(new Service1(new Service2Enum(EnumerationOf_perBlockIEnumerable72())), new Service2Enum(EnumerationOf_perBlockIEnumerable72()), new Service2Enum(EnumerationOf_perBlockIEnumerable72()), new Service2Enum(EnumerationOf_perBlockIEnumerable72()), new Service3(new Service4(), new Service4()), new Service4(), new Service4());
117+
return new CompositionRoot(new Service1(new Service2Enum(EnumerationOf_perBlockIEnumerableIService3())), new Service2Enum(EnumerationOf_perBlockIEnumerableIService3()), new Service2Enum(EnumerationOf_perBlockIEnumerableIService3()), new Service2Enum(EnumerationOf_perBlockIEnumerableIService3()), new Service3(new Service4(), new Service4()), new Service4(), new Service4());
118118
}
119119

120120
[MethodImpl(MethodImplOptions.AggressiveInlining)]

readme/FuncDetails.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,13 @@ partial class Func
8484
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8585
public partial CompositionRoot TestPureDIByCR()
8686
{
87-
Func<IService3> perBlockFunc96 = new Func<IService3>(
87+
Func<IService3> perBlockFuncIService3 = new Func<IService3>(
8888
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8989
() =>
9090
{
9191
return new Service3(new Service4(), new Service4());
9292
});
93-
return new CompositionRoot(new Service1(new Service2Func(perBlockFunc96)), new Service2Func(perBlockFunc96), new Service2Func(perBlockFunc96), new Service2Func(perBlockFunc96), new Service3(new Service4(), new Service4()), new Service4(), new Service4());
93+
return new CompositionRoot(new Service1(new Service2Func(perBlockFuncIService3)), new Service2Func(perBlockFuncIService3), new Service2Func(perBlockFuncIService3), new Service2Func(perBlockFuncIService3), new Service3(new Service4(), new Service4()), new Service4(), new Service4());
9494
}
9595

9696
[MethodImpl(MethodImplOptions.AggressiveInlining)]

readme/accumulators.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,32 +108,32 @@ partial class Composition
108108
[MethodImpl(MethodImplOptions.AggressiveInlining)]
109109
get
110110
{
111-
var perBlockTelemetryRegistry3 = new TelemetryRegistry();
112-
var perBlockSqlDataSource7 = new SqlDataSource();
111+
var perBlockTelemetryRegistry = new TelemetryRegistry();
112+
var perBlockSqlDataSource = new SqlDataSource();
113113
if (_singletonNetworkDataSource64 is null)
114114
lock (_lock)
115115
if (_singletonNetworkDataSource64 is null)
116116
{
117117
NetworkDataSource _singletonNetworkDataSource64Temp;
118118
_singletonNetworkDataSource64Temp = new NetworkDataSource();
119-
perBlockTelemetryRegistry3.Add(_singletonNetworkDataSource64Temp);
119+
perBlockTelemetryRegistry.Add(_singletonNetworkDataSource64Temp);
120120
Thread.MemoryBarrier();
121121
_singletonNetworkDataSource64 = _singletonNetworkDataSource64Temp;
122122
}
123123

124-
var transientSqlDataSource5 = new SqlDataSource();
124+
var transientSqlDataSource = new SqlDataSource();
125125
lock (_lock)
126126
{
127-
perBlockTelemetryRegistry3.Add(transientSqlDataSource5);
127+
perBlockTelemetryRegistry.Add(transientSqlDataSource);
128128
}
129129

130-
var transientDashboard4 = new Dashboard(transientSqlDataSource5, _singletonNetworkDataSource64, perBlockSqlDataSource7);
130+
var transientDashboard = new Dashboard(transientSqlDataSource, _singletonNetworkDataSource64, perBlockSqlDataSource);
131131
lock (_lock)
132132
{
133-
perBlockTelemetryRegistry3.Add(transientDashboard4);
133+
perBlockTelemetryRegistry.Add(transientDashboard);
134134
}
135135

136-
return (transientDashboard4, perBlockTelemetryRegistry3);
136+
return (transientDashboard, perBlockTelemetryRegistry);
137137
}
138138
}
139139
}

readme/async-disposable-scope.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,13 @@ partial class Composition: IDisposable, IAsyncDisposable
174174
[MethodImpl(MethodImplOptions.AggressiveInlining)]
175175
get
176176
{
177-
Func<Session> perBlockFunc602 = new Func<Session>(
177+
Func<Session> perBlockFuncSession = new Func<Session>(
178178
[MethodImpl(MethodImplOptions.AggressiveInlining)]
179179
() =>
180180
{
181181
return new Session(this);
182182
});
183-
return new Program(perBlockFunc602);
183+
return new Program(perBlockFuncSession);
184184
}
185185
}
186186

readme/async-enumerable.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,14 @@ partial class Composition
8989
get
9090
{
9191
[MethodImpl(MethodImplOptions.AggressiveInlining)]
92-
async IAsyncEnumerable<IHealthCheck> EnumerationOf_transientIAsyncEnumerable382()
92+
async IAsyncEnumerable<IHealthCheck> EnumerationOf_transientIAsyncEnumerableIHealthCheck()
9393
{
9494
yield return new MemoryCheck();
9595
yield return new ExternalServiceCheck();
9696
await Task.CompletedTask;
9797
}
9898

99-
return new HealthService(EnumerationOf_transientIAsyncEnumerable382());
99+
return new HealthService(EnumerationOf_transientIAsyncEnumerableIHealthCheck());
100100
}
101101
}
102102
}

readme/async-root.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,29 +70,29 @@ partial class Composition
7070
[MethodImpl(MethodImplOptions.AggressiveInlining)]
7171
public Task<IBackupService> GetBackupServiceAsync(CancellationToken cancellationToken)
7272
{
73-
Task<IBackupService> transientTask221;
73+
Task<IBackupService> transientTaskIBackupService;
7474
// Injects an instance factory
75-
Func<IBackupService> perBlockFunc222 = new Func<IBackupService>(
75+
Func<IBackupService> perBlockFuncIBackupService = new Func<IBackupService>(
7676
[MethodImpl(MethodImplOptions.AggressiveInlining)]
7777
() =>
7878
{
7979
return new BackupService(new FileStore());
8080
});
81-
Func<IBackupService> localFactory = perBlockFunc222;
81+
Func<IBackupService> localFactory = perBlockFuncIBackupService;
8282
// Injects a task factory creating and scheduling task objects
83-
TaskFactory<IBackupService> perBlockTaskFactory223;
83+
TaskFactory<IBackupService> perBlockTaskFactoryIBackupService;
8484
CancellationToken localCancellationToken = cancellationToken;
85-
TaskCreationOptions transientTaskCreationOptions227 = TaskCreationOptions.None;
86-
TaskCreationOptions localTaskCreationOptions = transientTaskCreationOptions227;
87-
TaskContinuationOptions transientTaskContinuationOptions228 = TaskContinuationOptions.None;
88-
TaskContinuationOptions localTaskContinuationOptions = transientTaskContinuationOptions228;
89-
TaskScheduler transientTaskScheduler229 = TaskScheduler.Default;
90-
TaskScheduler localTaskScheduler = transientTaskScheduler229;
91-
perBlockTaskFactory223 = new TaskFactory<IBackupService>(localCancellationToken, localTaskCreationOptions, localTaskContinuationOptions, localTaskScheduler);
92-
TaskFactory<IBackupService> localTaskFactory = perBlockTaskFactory223;
85+
TaskCreationOptions transientTaskCreationOptions = TaskCreationOptions.None;
86+
TaskCreationOptions localTaskCreationOptions = transientTaskCreationOptions;
87+
TaskContinuationOptions transientTaskContinuationOptions = TaskContinuationOptions.None;
88+
TaskContinuationOptions localTaskContinuationOptions = transientTaskContinuationOptions;
89+
TaskScheduler transientTaskScheduler = TaskScheduler.Default;
90+
TaskScheduler localTaskScheduler = transientTaskScheduler;
91+
perBlockTaskFactoryIBackupService = new TaskFactory<IBackupService>(localCancellationToken, localTaskCreationOptions, localTaskContinuationOptions, localTaskScheduler);
92+
TaskFactory<IBackupService> localTaskFactory = perBlockTaskFactoryIBackupService;
9393
// Creates and starts a task using the instance factory
94-
transientTask221 = localTaskFactory.StartNew(localFactory);
95-
return transientTask221;
94+
transientTaskIBackupService = localTaskFactory.StartNew(localFactory);
95+
return transientTaskIBackupService;
9696
}
9797
}
9898
```

0 commit comments

Comments
 (0)