Sundew.Injection 0.0.0-u20230425-232810-ci

This is a prerelease version of Sundew.Injection.
This package has a SemVer 2.0.0 package version: 0.0.0-u20230425-232810-ci+89147c5c41.
There is a newer prerelease version of this package available.
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
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sundew.Injection" Version="0.0.0-u20230425-232810-ci" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Sundew.Injection --version 0.0.0-u20230425-232810-ci
#r "nuget: Sundew.Injection, 0.0.0-u20230425-232810-ci"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// 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
  • 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

  1. It was instantiated by the factory
  2. 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?
  • 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

0.0 - Initial version.