Skip to content

Commit edab8d2

Browse files
committed
docs: correct the README's requirements, diagnostics, generated code and performance
1 parent 1bf3533 commit edab8d2

1 file changed

Lines changed: 30 additions & 28 deletions

File tree

‎README.md‎

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ Key features:
3838

3939
### Requirements
4040

41-
- .NET SDK: Any version supporting C# 7.3 or later
41+
- Compiler: .NET SDK 9.0.300 or later, or Visual Studio 2022 17.14 or later (the generator is built against Roslyn 4.14)
42+
- Language: C# 8.0 or later (the generated code uses `#nullable` directives)
4243
- Target Frameworks: .NET Framework 4.6.2+, .NET 8+, .NET 9+, .NET 10+
4344
- Splat: Version 19.1.1 or later (supports modern generic-first resolvers)
4445

@@ -301,12 +302,15 @@ The package includes intelligent analyzers that provide real-time feedback:
301302

302303
| Diagnostic ID | Severity | Description | Code Fix |
303304
|--------------|----------|-------------|----------|
304-
| SPLATDI001 | Warning | Multiple constructors without `[DependencyInjectionConstructor]` attribute | Adds attribute to selected constructor |
305+
| SPLATDI001 | Error | Multiple constructors without `[DependencyInjectionConstructor]` attribute | Adds attribute to selected constructor |
305306
| SPLATDI002 | Error | Property with `[DependencyInjectionProperty]` lacks accessible setter | Changes setter to `public` or `internal` |
306307
| SPLATDI003 | Error | Multiple constructors marked with `[DependencyInjectionConstructor]` | Manual fix required |
307308
| SPLATDI004 | Error | Constructor marked with `[DependencyInjectionConstructor]` is not accessible | Changes to `public` or `internal` |
309+
| SPLATDI005 | Error | Registered types depend on each other through their constructors in a cycle | Manual fix required |
310+
| SPLATDI006 | Warning | A service is registered more than once with the same contract | Manual fix required |
311+
| SPLATDI007 | Error | A constructor takes `Lazy<T>` of a service that is not registered with `RegisterLazySingleton` | Manual fix required |
308312

309-
The analyzer detects issues in real-time and offers automatic fixes via Quick Actions (Ctrl+. or Cmd+.).
313+
The analyzer detects issues in real-time and offers automatic fixes via Quick Actions (Ctrl+. or Cmd+.). SPLATDI005 to SPLATDI007 need every registration in the project, so the source generator reports them when it runs.
310314

311315
## How It Works
312316

@@ -317,37 +321,35 @@ The source generator follows a four-step process:
317321
3. Code Generation - Generates optimized registration code with no reflection
318322
4. Incremental Builds - Only regenerates when relevant code changes
319323

320-
Generated code example:
324+
The code generated for the [Complete Example](#complete-example):
321325

322326
```csharp
323-
// Generated by Splat.DependencyInjection.SourceGenerator
324-
static partial void SetupIOCInternal(IDependencyResolver resolver)
327+
static partial void SetupIOCInternal(Splat.IDependencyResolver resolver)
325328
{
326-
// Transient registration
327-
resolver.Register<IUserService>(() => new UserService(
328-
(IDatabase)resolver.GetService(typeof(IDatabase)),
329-
(ILogger)resolver.GetService(typeof(ILogger))
330-
) {
331-
Navigation = (INavigationService)resolver.GetService(typeof(INavigationService))
332-
});
333-
334-
// Lazy singleton registration
335-
{
336-
var lazy = new Lazy<IDatabase>(() => new SqliteDatabase(),
337-
LazyThreadSafetyMode.ExecutionAndPublication);
338-
resolver.Register<Lazy<IDatabase>>(() => lazy);
339-
resolver.Register<IDatabase>(() => lazy.Value);
340-
}
329+
resolver.Register<global::ILogger>(() => new global::FileLogger());
330+
331+
resolver.Register<global::IUserService>(
332+
() => new global::UserService(
333+
resolver.GetService<global::IDatabase>() ?? ThrowNotRegistered<global::IDatabase>("global::IDatabase"))
334+
{
335+
Logger = resolver.GetService<global::ILogger>() ?? ThrowNotRegistered<global::ILogger>("global::ILogger"),
336+
});
337+
338+
var lazy0 = new global::System.Lazy<global::IDatabase>(() => new global::SqliteDatabase());
339+
resolver.Register<global::System.Lazy<global::IDatabase>>(() => lazy0);
340+
resolver.Register<global::IDatabase>(() => lazy0.Value);
341341
}
342342
```
343343

344-
## Performance Benefits
344+
Transient registrations are made before lazy singletons. A missing dependency throws an `InvalidOperationException` naming its type, from a `ThrowNotRegistered` helper written into the same file.
345+
346+
## Performance
347+
348+
- No reflection at run time: resolving a service allocates only the objects it constructs, and a registration with no dependencies creates its factory delegate once
349+
- Full Native AOT and trimming support
350+
- Builds: a generation pass over 128 registrations takes 9.6 ms and allocates 1.8 MB, and an IDE edit elsewhere in the project reruns it in 8.2 ms. An edit that only moves registrations does not regenerate the file
345351

346-
Compared to reflection-based DI:
347-
- Approximately 100x faster registration execution (no runtime reflection)
348-
- Approximately 10-100x faster incremental builds (only processes changed files)
349-
- Full AOT support (works with Native AOT and trimming)
350-
- Zero runtime overhead (all work done at compile-time)
352+
The benchmarks, and how to reproduce these figures, are in [src/benchmarks/README.md](src/benchmarks/README.md).
351353

352354
## Troubleshooting
353355

@@ -402,7 +404,7 @@ We welcome contributions! Here's how you can help:
402404
3. Documentation: Help improve our examples and docs
403405
4. Testing: Add test cases for edge scenarios
404406

405-
See our [contribution guidelines](CONTRIBUTING.md) for details.
407+
See the [ReactiveUI contribution guide](https://www.reactiveui.net/contribute/index.html) for details.
406408

407409
## Sponsors
408410

0 commit comments

Comments
 (0)