Snowberry.DependencyInjection
5.2.0
dotnet add package Snowberry.DependencyInjection --version 5.2.0
NuGet\Install-Package Snowberry.DependencyInjection -Version 5.2.0
<PackageReference Include="Snowberry.DependencyInjection" Version="5.2.0" />
<PackageVersion Include="Snowberry.DependencyInjection" Version="5.2.0" />
<PackageReference Include="Snowberry.DependencyInjection" />
paket add Snowberry.DependencyInjection --version 5.2.0
#r "nuget: Snowberry.DependencyInjection, 5.2.0"
#:package Snowberry.DependencyInjection@5.2.0
#addin nuget:?package=Snowberry.DependencyInjection&version=5.2.0
#tool nuget:?package=Snowberry.DependencyInjection&version=5.2.0
Snowberry.DependencyInjection
A lightweight, easy-to-use IoC container for .NET. Warm resolution runs through a compiled per-service resolver graph (allocation parity with Microsoft.Extensions.DependencyInjection), and unlike build-once providers the container stays mutable, so you can add, remove, and overwrite registrations at any time. When you are done configuring, an opt-in Freeze() locks it in for maximum speed.
Install
dotnet add package Snowberry.DependencyInjection
Quick start
using Snowberry.DependencyInjection;
using Snowberry.DependencyInjection.Abstractions.Extensions;
using var container = new ServiceContainer();
container.RegisterSingleton<IFoo, Foo>();
container.RegisterTransient<IBar, Bar>();
var foo = container.GetRequiredService<IFoo>();
Dispose the container (Dispose() or await container.DisposeAsync()) when you are done. It disposes the instances it created.
Service lifetimes
| Lifetime | Description |
|---|---|
| Singleton | One instance for the container's lifetime. |
| Transient | A new instance on every resolve. |
| Scoped | One instance per scope. |
container.RegisterSingleton<IFoo, Foo>();
container.RegisterTransient<IBar, Bar>();
container.RegisterScoped<IBaz, Baz>();
Register a pre-built instance. Instances you supply are not disposed by the container:
container.RegisterSingleton<IFoo>(instance: new Foo());
Scopes
container.RegisterScoped<IScopedType, ScopedType>();
using (var scope = container.CreateScope())
{
// Created for this scope and disposed when the scope is disposed.
var svc = scope.ServiceProvider.GetRequiredService<IScopedType>();
}
Resolving a scoped service directly from the container uses the container's own root scope (disposed with the container).
Keyed services
container.RegisterTransient<ITestService, TestServiceA>("_KEY0_");
var svc = container.GetRequiredKeyedService<ITestService>("_KEY0_");
Open generic types
container.Register(typeof(IRepository<>), typeof(Repository<>), serviceKey: null, lifetime: ServiceLifetime.Transient, singletonInstance: null);
var repo = container.GetRequiredService<IRepository<User>>();
Overwriting registrations
By default a registration can be replaced. Pass ServiceContainerOptions.ReadOnly to forbid overwrites.
var container = new ServiceContainer(ServiceContainerOptions.Default & ~ServiceContainerOptions.ReadOnly);
container.RegisterTransient<IService, Impl>();
// Replaces the registration. Previously-created instances are still disposed as usual.
container.RegisterSingleton<IService, OtherImpl>();
Injection attributes
InjectAttribute ([Inject])
Injects a service into a property during construction.
- Valid on properties only. Cannot be applied more than once. Inherited.
- Required by default. If the service is not registered, resolution throws. Pass
isRequired: falseto make it optional, in which case an unregistered service leaves the property as its default (null) instead of throwing. - Can be combined with
[FromKeyedServices].
[Inject]
public ITestService Service { get; set; }
[Inject(isRequired: false)]
public ILogger? OptionalLogger { get; set; }
FromKeyedServicesAttribute ([FromKeyedServices])
Selects which keyed service to use for a property or constructor parameter.
- Valid on properties and parameters. Cannot be applied more than once. Inherited.
- On a property, combine with
[Inject]; on a constructor parameter it works on its own.
// Property:
[Inject]
[FromKeyedServices("_KEY1_")]
public ITestService? KeyedService { get; set; }
// Constructor parameter:
public MyService([FromKeyedServices("_KEY1_")] ITestService service) { ... }
PreferredConstructorAttribute ([PreferredConstructor])
Specifies which constructor the container should use to instantiate a type.
- Valid on constructors only. Cannot be applied more than once. Not inherited.
- Not needed when the type has a single constructor.
public class MyService
{
public MyService() { }
[PreferredConstructor]
public MyService(IDependency dependency) { ... }
}
Validation
Check the whole registered graph up front ("fail fast at startup") without constructing any instances. The container stays mutable; re-run after further changes.
// Throws ServiceValidationException listing every problem.
container.Validate();
// Or collect problems without throwing.
if (!container.TryValidate(out var errors))
{
foreach (var error in errors)
Console.WriteLine(error); // missing dependency / circular dependency / no usable constructor
}
Missing required dependencies and dependency cycles surface as exceptions at resolve time too (ServiceTypeNotRegistered, CircularDependencyException).
Freezing (opt-in lock-in for maximum speed)
When configuration is complete, Freeze() locks the container for the fastest resolves:
container.Freeze(); // validates first; pass Freeze(validate: false) to skip
bool locked = container.IsFrozen;
container.RegisterTransient<IBaz, Baz>(); // throws ServiceRegistryReadOnlyException
Freeze()validates by default. It runsValidate()first; on a problem it throwsServiceValidationExceptionand the container stays unfrozen and mutable. UseFreeze(validate: false)to skip.- One-way and idempotent (re-freezing is a no-op and does not re-validate).
- Stronger than
ServiceContainerOptions.ReadOnly, which blocks overwrite only. Freezing blocks all registration changes. - Freeze before creating long-lived scopes (scopes created after freezing use the optimized scope cache).
| Mode | Add / remove / overwrite | Warm-resolve speed |
|---|---|---|
| Mutable (default) | Yes | Fast: beats MS.DI on singleton/scoped, parity on simple transients |
Frozen (Freeze()) |
No (locked) | Fastest: full-graph inlining, baked singletons, optimized scopes |
How it works
Resolution is backed by a compiled per-service resolver graph: the first resolve of a type compiles and caches a delegate, and every warm resolve afterward is a dictionary lookup plus a delegate call (0 allocations for warm singleton/scoped). See docs/ARCHITECTURE.md for diagrams of the resolver graph and the mutable → frozen lifecycle.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Snowberry.DependencyInjection.Abstractions (>= 5.2.0)
-
net10.0
- Snowberry.DependencyInjection.Abstractions (>= 5.2.0)
-
net8.0
- Snowberry.DependencyInjection.Abstractions (>= 5.2.0)
-
net9.0
- Snowberry.DependencyInjection.Abstractions (>= 5.2.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Snowberry.DependencyInjection:
| Package | Downloads |
|---|---|
|
Snowberry.Mediator.DependencyInjection
Extension for the main package to work with `Snowberry.DependencyInjection`. |
|
|
Snowberry.Mediator.OpenTelemetry
Snowberry.DependencyInjection integration for Snowberry.Mediator OpenTelemetry. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 5.2.0 | 107 | 5/31/2026 | |
| 5.1.1 | 572 | 1/28/2026 | |
| 5.1.0 | 594 | 11/15/2025 | |
| 5.0.0.2 | 312 | 11/4/2025 | |
| 5.0.0.1 | 357 | 11/4/2025 | |
| 5.0.0 | 369 | 11/4/2025 | |
| 4.0.0 | 282 | 10/7/2025 | |
| 3.8.0 | 429 | 9/22/2025 | |
| 3.7.0 | 287 | 8/1/2025 | |
| 3.6.0 | 751 | 11/14/2024 | |
| 3.5.0 | 495 | 10/9/2024 | |
| 3.4.0 | 754 | 8/28/2024 | |
| 3.3.0 | 2,047 | 3/28/2024 | |
| 3.2.0 | 1,151 | 1/31/2024 | |
| 3.1.0 | 348 | 1/31/2024 | |
| 3.0.0 | 355 | 1/31/2024 | |
| 2.0.0 | 872 | 12/20/2023 | |
| 1.0.3 | 2,247 | 3/8/2023 |