Sundew.Injection
0.0.0-u20230425-232810-ci
See the version list below for details.
dotnet add package Sundew.Injection --version 0.0.0-u20230425-232810-ci
NuGet\Install-Package Sundew.Injection -Version 0.0.0-u20230425-232810-ci
<PackageReference Include="Sundew.Injection" Version="0.0.0-u20230425-232810-ci" />
paket add Sundew.Injection --version 0.0.0-u20230425-232810-ci
#r "nuget: Sundew.Injection, 0.0.0-u20230425-232810-ci"
// Install Sundew.Injection as a Cake Addin #addin nuget:?package=Sundew.Injection&version=0.0.0-u20230425-232810-ci&prerelease // Install Sundew.Injection as a Cake Tool #tool nuget:?package=Sundew.Injection&version=0.0.0-u20230425-232810-ci&prerelease
Sundew.Injection
The overarching goal of Sundew.Injection is to increase performance related to object creation, through the means of Pure DI, while keeping most of the benefits of using a Dependency Injection Container (DIC).
Software applications types based on time running.
Application types | Time running |
---|---|
Classical server application | Long run time, where most of the application is created on startup. Will often create an object graph per request |
Desktop application | These vary from having a medium to long run time, but typically create object graph per request |
Mobile application | Short run time as the OS will suspend apps not in focus, requiring it to be initialized again on activation |
WebJob (Cloud enabled) | Short run time as the cloud system will suspend the app. |
Tests run | Very short run time with frequent start-ups when running tests |
Generally, short-lived applications can benefit the most from Pure DI during start-up, but server applications can fx improve the number of request the can process by using Pure DI.
Motivation
- Recognize that DI containers are a too generic solution to application object graph creation
- 90+% of all types created in an application have a static relationship to the application (e.g. Only one implementation per interface)
- A DI container will allow an application to resolve nearly any type, but effectively any application explicitly resolves a finite number of types
- The resolved types might change over time, which is why DI containers are convenient.
- As long as changing which types are resolved and building an object graph is easy, a DI container is not needed
- The resolved types might change over time, which is why DI containers are convenient.
- Performance
- Whether DI container performance is a issue can be debated, reflected/dynamically compiled code is slower than statically compiled code.
- DI container adds high costs to high-level tests, alternatives such as reusing containers introduce complexity and risk of not running tests properly isolated
- Support platforms that do not support dynamic code compilation: iOS
Features
Features | Usage | Comparison to DIC |
---|---|---|
Declaration | Declare how types, map and what factories to create | Configuration of the container |
- Declaring factories to be created | Used to define which root types can be created | Dependending on DIC this is similar to explicit calls to Resolve<T>() |
- Generate factory interface | Used to optionally declare whether an interface will be generated for the fatory | Not relevant |
- Multiple Create functions per factory | Sharing objects between created types or create an 'operations factory' | Some DICs supports implementing (runtime) a factory interface |
- Instantiation of unregistered types | When a dependency is an 'instantiable' type there is no need to register it. | Supported by most DICs |
- Constructor selection | Used to manually select the constructor to use | Supported by most DICs |
- Mapping interface to implementation | Used to specify what concrete type should be instantiated for a specific interface | Supported by most DICs |
- Multiple implementations | Used to resolve all implementations of an interface | Supported by most DICs |
- Generic types | Used to register generic types, so that any bound generic of that type can be resolved | Supported by most DICs |
Parameters (Modular development, required/public interface) | Used to pass in required parameters into the factory | Similar to resolve overrides in other DICs |
- Required parameters (required interface) | Required parameters are explicitly part of the factory interface | Same as above, although interface is not explicit |
- Required parameter from custom class | Specify a custom class that contains parameters, to control public interface | Same as above |
- Optional parameters | Explicitly specifies a parameter, but in case of null the implementation is resovled | Depends on DICs, typically emulated though an empty multiple implementations |
Lifetime scopes | Used to declare the dependency graph e.g. how object communicate | Supported by all DICs |
- Single instance per factory | The same instance will be used throughout the factory lifetime | Equivalent to singleton |
- Single instance per request | A new instance will be created per call to the 'Create' method and thus be shared | Equivalent to single instance per request/resolve |
- New instance | A new instance is created every time it is requested | Equivalent to transient |
Override 'new' in derived factory class | Useful when wanting to replace an implementation with a different one.<br/>e.g. a mock without making explicitly part of the interface | Typically registrations can be overwritten |
Disposal of any (owned) IDisposables | Disposal by disposing factory or explicit Dispose(TCreated) method | Depends on DIC, some support only disposing singletons |
Factories can depend on other generated factories | Currently, factories can be injection, but does not support calling the 'Create' method | Supported by some DICs through child containers |
Zero reflection | Improved performance<br/>Enable .NET Native/NativeAOT etc. | Not supported by DICs |
Not implemented yet:
- Interception
- Custom lifetime scope, to support implementing something like single instance per thread or per session
- Calling child factory 'Create' methods
- Generating documentation
- Test error cases
- Examples
- Initialization
- Thread safety
- Test correctness of generated code
Not supported DIC features
- No dynamic assembly loading such as a plug-in system → Use an existing DI container/AssemblyLoadContext for the high-level plug-in loading and Sundew.Injection for the plug-ins themselves.
- No support for describing conditional object creation → Considered business logic, implement a factory manually
Factory or Dependency Injection Container?
An issue with the factory pattern is that it is only conceptually reusable. Hence DICs provide a productivity improvement as a reusable pattern for instantiating object graphs. Although the output of Sundew.Injection resembles the Factory pattern more closely, the declaration of these are very aligned with usage of a DICs. Therefore the name Sundew.Injection was chosen.
With this, an instantiated Factory is conceptually similar to a configured DIC instance.
Definition of IDisposable ownership
An IDisposable object is considered owned by a factory in the following scenarios
- It was instantiated by the factory
- It was created by a Func<> passed into the factory and creation was triggered by the factory.
Open questions
- How to integrate with application frameworks?
- Some frameworks like ASP.NET dictates the use of an DICs.
Challenges/Risk
- Build performance
- If solution cannot be implemented in a performant (enough) way with IIncrementalGenerator
- Support fallback non-code-generated (common DI container) solution for non release builds?
- If solution cannot be implemented in a performant (enough) way with IIncrementalGenerator
- Versioning across multiple projects, since the library contains some required types internal and public types.
- Internal types are added to the project, by public types like IInterceptor can only be included as a PackageReferences, so that other projects have a chance to implement an interceptor.
- Only expose generated factories? How can this work with a non-code-generated fallback? (It probably can't, as the generator has to run to determine the public interface (Only a problem if build performance is a problem))
- Hard to integrate with application framework the dictate a the use of a dynamic DIC.
- Generate code to integrate with DICs
- Explore how these could be changed to rely less on reflection
Integration with application frameworks
Framework | Comments |
---|---|
ASP.NET/Blazor/Razor | Currently, there is no good answer for ASP.NET, but it does allow replacing it middleware at various interception points.<br/>Here Sundew.Injection could be used, although some reflection would still be involved to determine which factory to call. |
ReactiveUI | TODO. |
LightMVVM | TODO. |
Maui | TODO. |
Uno | TODO. |
Console | New it and use it. |
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 was computed. 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. |
.NET Core | netcoreapp1.0 was computed. netcoreapp1.1 was computed. netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard1.3 is compatible. netstandard1.4 was computed. netstandard1.5 was computed. netstandard1.6 was computed. netstandard2.0 was computed. netstandard2.1 was computed. |
.NET Framework | net46 was computed. 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 | tizen30 was computed. tizen40 was computed. tizen60 was computed. |
Universal Windows Platform | uap was computed. uap10.0 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
This package has 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 |
---|---|---|
0.1.0-u20240903-221012-ci | 53 | 9/3/2024 |
0.1.0-u20240831-223002-ci | 37 | 8/31/2024 |
0.1.0-u20240515-220752-ci | 56 | 5/15/2024 |
0.1.0-u20240504-211532-ci | 62 | 5/4/2024 |
0.1.0-u20240504-074952-ci | 48 | 5/4/2024 |
0.1.0-u20240503-222931-ci | 49 | 5/3/2024 |
0.1.0-u20240430-183052-ci | 47 | 4/30/2024 |
0.1.0-u20240422-004228-ci | 54 | 4/22/2024 |
0.1.0-u20240411-223024-ci | 60 | 4/11/2024 |
0.1.0-u20240410-220206-ci | 53 | 4/10/2024 |
0.1.0-u20240402-204833-ci | 47 | 4/2/2024 |
0.1.0-u20240330-234319-ci | 57 | 3/30/2024 |
0.1.0-u20240330-232212-ci | 56 | 3/30/2024 |
0.1.0-u20240329-185825-ci | 62 | 3/29/2024 |
0.1.0-u20240329-184655-ci | 59 | 3/29/2024 |
0.1.0-u20240329-154845-ci | 44 | 3/29/2024 |
0.1.0-u20240325-213010-ci | 58 | 3/25/2024 |
0.1.0-u20240325-203646-ci | 54 | 3/25/2024 |
0.1.0-u20240324-234553-ci | 56 | 3/24/2024 |
0.1.0-u20230913-231710-ci | 77 | 9/13/2023 |
0.0.0-u20230516-231121-ci | 74 | 5/16/2023 |
0.0.0-u20230425-232810-ci | 79 | 4/25/2023 |
0.0 - Initial version.