Releases: DevTeam/Pure.DI
2.4.0
❗ Breaking Changes
- Nullable Reference Types — This release introduces full support for nullable reference types, which changes how Pure.DI handles nullable types throughout the pipeline. Code that previously relied on automatic null checks for nullable arguments may need adjustment.
🚀 New Features
-
Nullable Reference Types — Breaking change! Pure.DI now fully preserves nullable reference type annotations throughout the dependency injection pipeline.
- Nullable composition arguments (
.Arg<T?>()) and root arguments (.RootArg<T?>()) no longer generate null checks, allowingnullvalues to flow through as intended. - Non-null bindings can satisfy nullable dependencies — useful for optional constructor parameters, nullable factory results, and nullable collection elements.
- Type system integration: nullable annotations are preserved when reading contracts, building graphs, and generating composition members.
- Generic constraint awareness: prefer
where T : class?overwhere T : classfor contracts with nullable generic arguments to avoid C# compiler warnings. - New warnings for ambiguous nullable root types in
Resolvemethods help catch potential issues early. - See
readme/nullable-reference-types.mdfor detailed examples and best practices.
- Nullable composition arguments (
-
TryBuildUp for Builders
- New
TryBuildUpmethod generated alongsideBuildUpfor safe type-based composition. - Returns
falseinstead of throwing when the runtime subtype is unknown, enabling graceful fallback handling. - Example:
composition.TryBuildUp(externalRobot)returnsfalsefor unsupported types whilecomposition.BuildUp(externalRobot)would throwArgumentException. - See
readme/builders.mdfor usage examples.
- New
👥 Contributors
Thanks to everyone who contributed to this release:
- Adam Hathcock (@adamhathcock)
- Nikolay Pianikov (@NikolayPianikov)
Full Changelog: 2.3.7...2.4.0
2.3.7
🚀 New Features
-
Generate Interfaces from Classes — special thanks to Adam Hathcock for designing and implementing this feature! 🎉
- New
[GenerateInterface]attribute lets a concrete service produce a matching interface automatically, so consumers can depend on the abstraction without hand-writing it. - Customize the generated interface: set the interface name, namespace, and access level via named arguments.
- Generate several interfaces from one class by applying
[GenerateInterface]multiple times, with per-interface member selection. - Ignore specific members with
[IgnoreInterface], which takes precedence over[GenerateInterface]. - Generic interfaces are supported.
- XML documentation comments on source members are preserved on the generated interface.
- New
-
Scope Setup
- New
SetupScopemethod for composing scoped sub-graphs from a parent composition — open a scope per request/operation, share scoped instances inside it, and dispose them at the end. - Scopes can be produced by factory methods, enabling custom scope creation logic.
- Parent and child scopes are validated to be distinct, preventing accidental self-nesting.
- New
Hint.ScopeMethodNamehint lets you rename the generated scope factory method to fit your domain (e.g.SetupScope,CreateScope,BeginRequest):DI.Setup() .Hint(Hint.ScopeMethodName, "SetupScope") .Bind().As(Scoped).To<RequestContext>();
- New
-
Unity Scene Scopes
- New Unity scene scopes scenario: each loaded Unity scene gets its own scope, so scoped services are shared within a scene and isolated between scenes — Unity creates the
MonoBehaviourinstances and Pure.DI builds them up.
- New Unity scene scopes scenario: each loaded Unity scene gets its own scope, so scoped services are shared within a scene and isolated between scenes — Unity creates the
-
Microsoft DI Integration: Scoped Lifetimes
IServiceProviderintegration now supports scoped lifetimes end-to-end viacomposition.CreateScope(), matchingMicrosoft.Extensions.DependencyInjectionsemantics.- Tag-based resolution and value-type roots are supported through the integration.
-
Generated Code is Marked
- All generated code is now decorated with
[GeneratedCode], so analyzers, coverage, and code-style tools can recognize and skip it correctly. - The generated
Compositionclass embeds the actual Pure.DI package version it was produced with, instead of a hard-coded string.
- All generated code is now decorated with
🐛 Bug Fixes
- Fixed scoped/singleton instance propagation through nested scopes.
- Fixed read-only handling of setup-context arguments inside generated scope methods.
👥 Contributors
Thanks to everyone who contributed to this release:
- Adam Hathcock (@adamhathcock)
- Nikolay Pianikov (@NikolayPianikov)
Full Changelog: 2.3.6...2.3.7
2.3.6
🚀 New Features
-
BCL Dictionary Support
- Added support for injecting .NET Base Class Library
Dictionary<TKey, TValue>types - Extended injection capabilities to cover standard dictionary types from BCL
- Added support for injecting .NET Base Class Library
-
IContext Extensions
- Added RootName and RootType properties to
IContextinterface - Added IsLockRequired property to
IContext
- Added RootName and RootType properties to
-
Extended Factory Method Bindings
- Added support for up to 16-parameter factory method bindings
- Increased from previous limit, supporting more complex factory scenarios
⚡ Performance Improvements
-
Metadata Analysis
- Optimized metadata analysis pipeline
- Reduced overhead in setup metadata detection
-
Code Generation
- Optimized factory code generation with reduced allocations
- Optimized type resolution process
-
Graph Building
- Reduced semantic analysis overhead by favoring syntactic analysis
- Improved graph construction performance
📝 Documentation
- Updated and improved README documentation
- Added "Article Basics" documentation
Full Changelog: 2.3.5...2.3.6
2.3.5
🐛 Bug Fixes
- #140 Incorrect generated code for the PerBlock lifetime: a local or parameter named ... cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
- #141 Objects created without explicit binding began to use the specified default lifetime instead of Transient
Contributors
@YoshihiroIto
@NikolayPianikov
Full Changelog: 2.3.4...2.3.5
2.3.4
🚀 New Features
-
Auto-Bindings Default Lifetime Support
- Added support for default lifetime configuration in auto-bindings
- Improves consistency and reduces manual configuration
-
- Made anonymous roots lightweight
- Reduced memory footprint and improved performance for anonymous root scenarios
🐛 Bug Fixes
-
#132: Fixed optional enum default value emission
- Fixed issue where optional enum default values were emitted as int literals, causing CS1503 compilation error
- Enum default values from other assemblies are now correctly generated
- Enhanced handling of explicit default values for enum types in dependency injection
-
#133: Fixed null parameter issue in self-injecting registrations
- Fixed issue where null was passed as parameter when a registration injects itself
- Improved Lazy status determination by properly taking into account injected dependencies
-
DIE043: Fixed casting exception
- Fixed "Unable to cast object of type 'System.Int32' to type 'System.String'" error
- Improved type handling in dependency graph construction
-
Improved error message for unresolved dependencies
⚡ Performance Improvements (#136)
-
Cache Refactoring
- Significant performance improvements through cache refactoring
- Reduced redundant operations in dependency graph building
-
Graph Build Optimizations
- Optimization of graph build process to reduce computation time
- Reduced number of attempts to build a dependency graph
- Improved metadata analysis with preference for syntactic analysis instead of semantic
-
Variable Management
- Optimization of Var and VarsMap handling
- Improved graph build context preparation
- Enhanced scope persistence rules with helper methods
-
Metadata Analysis
- Optimized metadata analysis pipeline
- Reduced overhead in setup metadata detection
-
GraphOverrider
- Refactored GraphOverrider internals into focused helpers
- Improved override depth handling
-
Code Generation
- Spot optimization to eliminate unnecessary local values in generation
- Optimized dependency graph construction by handling duplicate injections
-
Multithreading
- Removed multithreading to improve stability
Full Changelog: 2.3.3...2.3.4
2.3.3
🐞 Bug Fixes
- #132 Optional enum default value is emitted as int literal, causing CS1503
Full Changelog: 2.3.2...2.3.3
2.3.2
🚀 New Features
-
Setup Context Support, see these examples:
-
Added support for deep and one-level override behavior, see this example:
-
Error Handling and Diagnostics
- Added specific IDs for errors and warnings to improve clarity
- Added errors and warning descriptions
- Linked diagnostics to documentation with help links in diagnostic descriptors
- Added localization support for new error and warning messages
-
Code Generation Enhancements
- Optimize dependency graph construction by handling duplicate injections and improving object creation workflow
Contributors
2.3.1
This release focuses on simplifying the API, increasing the stability of code generation, and extending support for specific platforms like Unity.
🚀 New Features
- Simplified Bindings API: Simplified lifetime-specific bindings methods are now available directly, making configurations shorter and more readable (see below).
- New
IConfiguration.SpecialType<T>()API: Allows marking special types (such as Unity base types) to exclude them from automatic simplified bindings and avoid conflicts.
🐞 Bug Fixes
- Cycle-safe IEnumerable resolution: Improved dependency resolution logic for collections; it now prioritizes non-cyclic dependencies to prevent errors during cross-referencing.
- Action-based injection scenarios: Added support for scenarios where dependencies are injected via actions, with correct handling of the syntax context.
- Various fixes in integration tests and benchmarks.
Full Changelog: 2.3.0...2.3.1
Bindings
Bindings are the core mechanism of Pure.DI, used to define how types are created and which contracts they fulfill.
Overview
For Implementations
To bind a contract to a specific implementation:
.Bind<Contract1>(tags).Bind<ContractN>(tags)
.Tags(tags)
.As(Lifetime)
.To<Implementation>()Alternatively, you can bind multiple contracts at once:
.Bind<Contract1, Contract2>(tags)
.To<Implementation>()Example:
.Bind<IService>().To<Service>()For Factories
To use a custom factory logic via IContext:
.Bind<Contract1>(tags).Bind<ContractN>(tags)
.Tags(tags)
.As(Lifetime)
.To(ctx => new Implementation(ctx.Resolve<Dependency>()))Example:
.Bind<IService>().To(ctx => new Service(ctx.Resolve<IDependency>()))For Simplified Factories
When you only need to inject specific dependencies without accessing the full context:
.Bind<Contract1>(tags).Bind<ContractN>(tags)
.Tags(tags)
.As(Lifetime)
.To<Implementation>((Dependency1 dep1, Dependency2 dep2) => new Implementation(dep1, dep2))Example:
.Bind<IService>().To((IDependency dep) => new Service(dep))Lifetimes
Lifetimes control how long an object lives and how it is reused:
- Transient: A new instance is created for every injection (default).
- Singleton: A single instance is created for the entire composition.
- PerResolve: A single instance is reused within a single
Resolve(composition root). - PerBlock: Reuses instances within a code block to reduce allocations.
- Scoped: A single instance is reused within a specific scope.
Default Lifetimes
You can set a default lifetime for all subsequent bindings in a setup:
.DefaultLifetime(Lifetime.Singleton)
// This will be a Singleton
.Bind<IInterface>().To<Implementation>()Alternatively, you can set a default lifetime for a specific contract type:
.DefaultLifetime<IDisposable>(Lifetime.Singleton)Tags
Tags allow you to distinguish between multiple implementations of the same contract.
- Use
.Bind<T>(tags)or.Tags(tags)to apply tags to a binding. - Use the
[Tag(tag)]attribute orctx.Resolve<T>(tag)to consume a tagged dependency.
Example:
.Bind<IService>("MyTag").To<Service>()Implementation Bindings
Implementation bindings allow for a more concise syntax where the implementation type itself serves as the contract or where you want the binder to automatically infer suitable base types and interfaces.
For Implementations
// Infers all suitable base types and interfaces automatically
.Bind(tags).Tags(tags).As(Lifetime).To<Implementation>()Alternatively, you can use the implementation type as the contract:
.Bind().To<Implementation>()Example:
.Bind().To<Service>()For Factories
.Bind(tags).Tags(tags).To(ctx => new Implementation())Example:
.Bind().To(ctx => new Service())For Simplified Factories
.Bind(tags).Tags(tags).To((Dependency dep) => new Implementation(dep))Example:
.Bind().To((IDependency dep) => new Service(dep))Special types will not be added to bindings
By default, Pure.DI avoids binding tospecial types during auto-inference to prevent polluting the container with unintended bindings for types like IDisposable, IEnumerable, or object. Special types will not be added to bindings by default:
System.ObjectSystem.EnumSystem.MulticastDelegateSystem.DelegateSystem.Collections.IEnumerableSystem.Collections.Generic.IEnumerable<T>System.Collections.Generic.IList<T>System.Collections.Generic.ICollection<T>System.Collections.IEnumeratorSystem.Collections.Generic.IEnumerator<T>System.Collections.Generic.IReadOnlyList<T>System.Collections.Generic.IReadOnlyCollection<T>System.IDisposableSystem.IAsyncResultSystem.AsyncCallback
If you want to add your own special type, use the SpecialType<T>() call, for example:
.SpecialType<MonoBehaviour>()
.Bind().To<MyMonoBehaviourImplementation>()
// Now MonoBehaviour will not be added to the contractsSimplified Lifetime-Specific Bindings
Pure.DI provides semantic sugar for common lifetimes. These methods combine Bind(), .Tags(tags), As(Lifetime), and To() into a single call.
For Implementations
// Equivalent to Bind<T, T1, ...>(tags).As(Lifetime.Transient).To<Implementation>()
.Transient<T>(tags)
// or multiple types at once
.PerResolve<T, T1, ...>(tags)Example:
.Transient<Service>()
.Singleton<Service2, Service3, Service4>()For Factories
// Equivalent to Bind(tags).As(Lifetime.Singleton).To(ctx => ...)
.Singleton<Implementation>(ctx => new Implementation(), tags)Example:
.Singleton<IService>(ctx => new Service())For Simplified Factories
// Equivalent to Bind(tags).As(Lifetime.PerResolve).To((Dependency dep) => ...)
.PerResolve((Dependency dep) => new Implementation(dep), tags)Example:
.PerResolve((IDependency dep) => new Service(dep))Equivalent shortcuts exist for all lifetimes:
Transient<T>(...)Singleton<T>(...)Scoped<T>(...)PerResolve<T>(...)PerBlock<T>(...)
2.2.15
🚀 What's New in This Release
Key Enhancements:
- Improved examples
- Improved project templates
- Including Rider Junie’s guidelines
- .NET 10 support
- Reorganized AI-related docs and guidance
Full Changelog: 2.2.14...2.2.15
2.2.14
🚀 What's New in This Release
🐛 Critical Fixes:
- #124 Mulitple binding (as attributes) sometimes fail to be properly resolved in a single dependency graph
Full Changelog: 2.2.13...2.2.14