SourceCrafter.DependencyInjection
2.26.249.144
dotnet add package SourceCrafter.DependencyInjection --version 2.26.249.144
NuGet\Install-Package SourceCrafter.DependencyInjection -Version 2.26.249.144
<PackageReference Include="SourceCrafter.DependencyInjection" Version="2.26.249.144" />
<PackageVersion Include="SourceCrafter.DependencyInjection" Version="2.26.249.144" />
<PackageReference Include="SourceCrafter.DependencyInjection" />
paket add SourceCrafter.DependencyInjection --version 2.26.249.144
#r "nuget: SourceCrafter.DependencyInjection, 2.26.249.144"
#:package SourceCrafter.DependencyInjection@2.26.249.144
#addin nuget:?package=SourceCrafter.DependencyInjection&version=2.26.249.144
#tool nuget:?package=SourceCrafter.DependencyInjection&version=2.26.249.144
SourceCrafter.DependencyInjection
Truly compile-time dependency injection for .NET. Services are declared with attributes and the Roslyn generator emits plain, readable C# resolvers — no reflection, no expression trees, no runtime container.
[ServiceContainer]
[Singleton<IClock, SystemClock>]
[Scoped<DbSession>]
[Transient<Handler>]
public partial class AppContainer { }
using var container = new AppContainer();
var handler = container.CreateScope().Handler;
Full documentation lives in SourceCrafter.DependencyInjection/README.md —
attribute reference, generated code walkthrough, cancellation semantics and the SCDI
diagnostic table.
Packages
| Package | What it is |
|---|---|
SourceCrafter.DependencyInjection |
The generator. This is the one you usually want. |
SourceCrafter.DependencyInjection.Metadata |
Attributes only, no generator. For libraries that want to declare contracts without pulling in the generator. |
SourceCrafter.DependencyInjection.MsConfiguration |
The generator plus Microsoft.Extensions.Configuration support ([JsonSetting<T>]). Use instead of the core generator. |
SourceCrafter.DependencyInjection.MsConfiguration.Metadata |
Attributes only, configuration flavour. |
Repository layout
SourceCrafter.DependencyInjection/— the generator (netstandard2.0 is deliberately not targeted)SourceCrafter.DependencyInjection.Metadata/— attribute definitionsSourceCrafter.DependencyInjection.MsConfiguration*/— configuration-aware variantsSourceCrafter.DependencyInjection.Tests/— the test suiteBenchmarks/— BenchmarkDotNet harness
Building and testing
dotnet build SourceCrafter.DependencyInjection.slnx
The test project targets .NET 10 and runs through the xUnit v3 in-process runner. dotnet test
is currently unreliable with the .NET 10 SDK here, so run the produced executable directly:
dotnet build SourceCrafter.DependencyInjection.Tests/SourceCrafter.DependencyInjection.Tests.csproj
SourceCrafter.DependencyInjection.Tests/bin/Debug/net10.0/SourceCrafter.DependencyInjection.Tests.exe
Filter with -method "*SomePattern*".
Test layers
Changing the generator means keeping five different kinds of test honest:
Tests.cs/Server.cs— behaviour. A realistic container is compiled by the real generator during the build, then exercised at runtime.GeneratedCodeTests.cs— emission. Runs the generator in memory viaGeneratorHarnessand asserts on the text it produces: lock scoping, the constructor, disposal ordering, file shape, and determinism across repeated passes.RootPropagationTests.cs— theRoot/CreateScopevirtual-dispatch contract.LockingStrategyTests.cs— the locking scheme itself, modelled by hand with no generator involved. Shows why one lock per lifetime deadlocks on cross-lifetime dependencies, why a single lock is correct but serializing, and why an instance lock cannot guard a static field.EqualityContractTests.cs— guards every generator model type against declaringEqualswithout a matching==, which silently breaks incremental caching.
If you touch the emitter, GeneratedCodeTests is where a regression will surface first.
Inspecting generated output
dotnet build -p:EmitCompilerGeneratedFiles=true -p:CompilerGeneratedFilesOutputPath=obj/generated
Emit to a directory outside the project folder, or the generated files get picked up by the next compilation and every type ends up declared twice.
Benchmarks
Benchmarks/ compares this generator against every actively maintained compile-time DI
container — Jab,
Pure.DI,
StrongInject and
MrMeeseeks.DIE — following the scenarios that
.NET Matrix has made the de facto standard for
this category. Results are in the package README.
dotnet run -c Release --project Benchmarks -- --filter "*"
Two rules the harness must keep:
- Every branch does exactly the same work. An earlier version of this harness only built the container in the SourceCrafter branch while the others created a scope and resolved a service, so the published numbers compared nothing.
Hand Codedis the baseline, not another container. Nestednew(...)is the floor a compile-time container is trying to reach.
And one rule for reading the output:
- Do not publish a timing ratio from a single run. These scenarios resolve in tens of
nanoseconds, where per-process JIT and heap-layout luck is a large fraction of the
measurement. A single-launch run once showed all three containers jumping from ~38 ns to
~80 ns while the baseline held still — three independent libraries do not regress in
lockstep.
HarnessConfigtherefore pins three process launches plus memory randomization, and even then the complex-graph scenario has been seen to vary 0.95x–2.10x across runs of the same binary with byte-identical allocations. Allocation counts are reproducible; sub-100 ns timings are only approximately so.
Where a library genuinely lacks a feature (StrongInject and MrMeeseeks.DIE have no request scope), the row is left empty rather than approximated: a made-up equivalent would measure something no user of that library could write.
Each container declaration lives in its own Containers.*.cs file, because the libraries ship
attributes with colliding names ([Transient<,>], [Singleton<,>], [Scoped<,>],
[Register]).
Contributing
Issues and pull requests are welcome. A few conventions that are not obvious from the code:
- The generator project sets
EnableDefaultCompileItems=false. Any new.csfile must be added to the.csprojby hand, or it will silently not compile. - Parsing and emission are separated on purpose. Anything reachable from a cached delegate
must be free of
ISymbol,SyntaxNode,SemanticModelandCompilation, otherwise the incremental pipeline keeps whole Roslyn compilations alive.ResolverRendereris the frozen, symbol-free projection that the emitter renders from. - New diagnostics go in
ServiceContainerDiagnostics.cswith a fresh, uniqueSCDIid, and aDiagnosticDescriptor.Titlethat is static text (dynamic titles break IDE grouping). - Generated code is normalized to tabs, CRLF and no trailing whitespace before it is emitted.
License
See LICENSE.txt.
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.26.249.144 | 132 | 9/6/2026 |
| 2.26.233.161 | 93 | 8/21/2026 |
| 2.26.227.290 | 99 | 8/15/2026 |
| 2.26.226.430 | 99 | 8/14/2026 |
| 2.26.226.81 | 95 | 8/14/2026 |
| 2.26.212.228 | 107 | 7/31/2026 |
| 2.26.212.105 | 105 | 7/31/2026 |
| 2.26.211.464 | 97 | 7/30/2026 |
| 2.26.206.340 | 104 | 7/25/2026 |
| 2.26.206.240 | 105 | 7/25/2026 |
| 2.26.206.225 | 102 | 7/25/2026 |
| 2.26.206.222 | 104 | 7/25/2026 |
| 2.26.206.28 | 110 | 7/25/2026 |
| 2.26.206.21 | 103 | 7/25/2026 |
| 2.26.206.9 | 105 | 7/25/2026 |
| 2.26.205.341 | 108 | 7/24/2026 |
| 2.26.205.55 | 115 | 7/24/2026 |
| 2.26.204.248 | 111 | 7/23/2026 |
| 2.26.203.271 | 113 | 7/22/2026 |
| 2.26.203.27 | 112 | 7/22/2026 |