SourceCrafter.DependencyInjection
0.24.294.5
dotnet add package SourceCrafter.DependencyInjection --version 0.24.294.5
NuGet\Install-Package SourceCrafter.DependencyInjection -Version 0.24.294.5
<PackageReference Include="SourceCrafter.DependencyInjection" Version="0.24.294.5" />
paket add SourceCrafter.DependencyInjection --version 0.24.294.5
#r "nuget: SourceCrafter.DependencyInjection, 0.24.294.5"
// Install SourceCrafter.DependencyInjection as a Cake Addin #addin nuget:?package=SourceCrafter.DependencyInjection&version=0.24.294.5 // Install SourceCrafter.DependencyInjection as a Cake Tool #tool nuget:?package=SourceCrafter.DependencyInjection&version=0.24.294.5
SourceCrafter.DependencyInjection - Truly compile-time depedency injection
Overview
SourceCrafter.DependencyInjection is a compile-time dependency injection library utilizing attributes to simplify and automate service registration. The package is designed to provide flexibility in configuring service lifetimes, custom factory methods, and other advanced DI features while ensuring compile-time safety.
Key Features
- Attribute-based Service Registration: Register services directly on classes and interfaces using attributes.
- Flexible Lifetimes: Supports
Singleton
,Scoped
, andTransient
lifetimes. - Custom Factories: Use factory static methods or existing instances (static properties or fields) to provide service implementations.
- Disposability Management: Control how services are disposed with customizable
Disposability
settings. It scales at compile time according the disposability. If there are IDisposable services and just having a single one IAsynDiposable, automatically the service is async disposable - Advanced Configuration Options: Define settings like resolver method name formatting, caching, and more through attribute parameters.
Installation
Install the SourceCrafter.DependencyInjection
NuGet package:
dotnet add package SourceCrafter.DependencyInjection
Example Usage
Below is an example of how to apply the available attributes for service registration in a Server
class, using SourceCrafter.DependencyInjection
.
1. Annotating the Server
Class
namespace SourceCrafter.DependencyInjection.Tests
{
[ServiceContainer]
[JsonSetting<AppSettings>("AppSettings")] // Load settings into AppSettings class
[JsonSetting<string>("ConnectionStrings::DefaultConnection", nameFormat: "GetConnectionString")] // Connection string
[Transient<int>("count", nameof(ResolveAsync))] // Register a transient int value using the ResolveAsync method
[Singleton<IDatabase, Database>] // Register Database as a singleton service
[Scoped<IAuthService, AuthService>] // Register AuthService as a scoped service
public partial class Server
{
internal static ValueTask<int> ResolveAsync(CancellationToken _)
{
return ValueTask.FromResult(1);
}
}
}
2. Service Definitions
AuthService
This service is scoped, meaning it is created once per request.
public class AuthService(IDatabase application, int count) : IAuthService, IDisposable
{
public int Count => count;
public IDatabase Database { get; } = application;
public void Dispose()
{
// Cleanup resources, e.g., database connections
}
}
Database
This is a singleton service that depends on AppSettings
and a connection string. It implements IDatabase
and uses IAsyncDisposable
for asynchronous cleanup.
public class Database(AppSettings settings, string connection) : IDatabase, IAsyncDisposable
{
public void TrySave(out string setting1)
{
setting1 = settings?.Setting1 ?? "Value3";
}
public ValueTask DisposeAsync()
{
return default;
}
}
3. Configuration and Settings
AppSettings
A simple class for application settings, loaded via [JsonSetting<AppSettings>("AppSettings")]
.
public class AppSettings
{
public string Setting1 { get; set; }
public string Setting2 { get; set; }
}
4. Attribute Definitions and Explanation
[ServiceContainer]
: Marks theServer
class as a container for services.[JsonSetting<T>]
: Specifies that the configuration sectionT
should be loaded from a JSON configuration file. In the example,AppSettings
andConnectionStrings::DefaultConnection
are loaded.[Singleton<T, TImplementation>]
: Registers a singleton service of typeT
with an implementation ofTImplementation
. Singleton services are created once and shared across the application.[Scoped<T, TImplementation>]
: Registers a scoped service of typeT
with an implementation ofTImplementation
. Scoped services are created once per request.[Transient<T>]
: Registers a transient service, meaning a new instance ofT
is created each time it is requested. In this example, theint
value is generated using theResolveAsync
method.
Advanced Configuration Options
1. Disposability
You can control the lifecycle of services using the Disposability
parameter, which supports the following options:
None
: No specific disposal behavior is applied.Dispose
: Standard disposal pattern.AsyncDispose
: Asynchronous disposal pattern usingIAsyncDisposable
.
2. Factory Methods
For advanced scenarios, you can specify factory methods or instances directly using the source
parameter in the attributes. This allows fine-grained control over how services are created and managed.
3. Caching
- Singleton services are cached at static level with appropiate thread-safe handling
- Scoped services are at instance level with appropiate thread-safe handling
Both of previous ones registered will consider even caching factory obtained values
Conclusion
SourceCrafter.DependencyInjection provides a flexible and powerful approach to dependency injection using attributes. It removes much of the boilerplate code required for service registration while allowing you to leverage advanced DI techniques such as factory methods, caching, and disposability control.
For more advanced scenarios and detailed API references, see the official documentation on GitHub.
Generated code
As result of the previous example, we can notice some aspects:
- Transient and non-cached services depedencies are called as they are defined: ()
#nullable enable
namespace SourceCrafter.DependencyInjection.Tests;
[global::System.CodeDom.Compiler.GeneratedCode("SourceCrafter.DependencyInjection", "0.24.280.49")]
public partial class Server : global::System.IAsyncDisposable
{
public static string Environment => global::System.Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Development";
static readonly object __lock = new object();
private static readonly global::System.Threading.SemaphoreSlim __globalSemaphore = new global::System.Threading.SemaphoreSlim(1, 1);
private static global::System.Threading.CancellationTokenSource __globalCancellationTokenSrc = new global::System.Threading.CancellationTokenSource();
private bool isScoped = false;
[global::System.CodeDom.Compiler.GeneratedCode("SourceCrafter.DependencyInjection", "0.24.280.49")]
public Server CreateScope() =>
new global::SourceCrafter.DependencyInjection.Tests.Server { isScoped = true };
[global::System.CodeDom.Compiler.GeneratedCode("SourceCrafter.DependencyInjection", "0.24.280.49")]
private static global::SourceCrafter.DependencyInjection.Tests.Database? _getDatabase;
[global::System.CodeDom.Compiler.GeneratedCode("SourceCrafter.DependencyInjection", "0.24.280.49")]
public global::SourceCrafter.DependencyInjection.Tests.Database GetDatabase()
{
if (_getDatabase is not null) return _getDatabase;
lock(__lock) return _getDatabase ??= new global::SourceCrafter.DependencyInjection.Tests.Database(GetSettings(), GetConnectionString());
}
[global::System.CodeDom.Compiler.GeneratedCode("SourceCrafter.DependencyInjection", "0.24.280.49")]
private global::SourceCrafter.DependencyInjection.Tests.AuthService? _getAuthService;
[global::System.CodeDom.Compiler.GeneratedCode("SourceCrafter.DependencyInjection", "0.24.280.49")]
public async global::System.Threading.Tasks.ValueTask<global::SourceCrafter.DependencyInjection.Tests.AuthService> GetAuthServiceAsync(global::System.Threading.CancellationToken? cancellationToken = default)
{
if (_getAuthService is not null) return _getAuthService;
await __globalSemaphore.WaitAsync(cancellationToken ??= __globalCancellationTokenSrc.Token);
try
{
return _getAuthService ??= new global::SourceCrafter.DependencyInjection.Tests.AuthService(GetDatabase(), await ResolveAsync(cancellationToken.Value));
}
finally
{
__globalSemaphore.Release();
}
}
[global::System.CodeDom.Compiler.GeneratedCode("SourceCrafter.DependencyInjection", "0.24.280.49")]
public async global::System.Threading.Tasks.ValueTask DisposeAsync()
{
if(isScoped)
{
_getAuthService?.Dispose();
}
else
{
(_getConfiguration as global::System.IDisposable)?.Dispose();
if (_getDatabase is not null) await _getDatabase.DisposeAsync();
}
}
}
TODO
- Support generic factory definitions like
static IService Get<TServiceType>(...) where IService : TServiceType, class /*or struct*/;
- Modules like Jab
Benchmark
Definitions
MrMeeseeks.DIE
[ImplementationAggregation(
typeof(AppSettings),
typeof(Database),
typeof(AuthService))]
[CreateFunction(typeof(AuthService), "Create")]
Jab
[ServiceProvider]
[Transient<AppSettings>]
[Singleton<IDatabase, Database>]
[Scoped<IAuthService, AuthService>]
public sealed partial class ServerJab;
SourceCrafter.Dependendcy
[ServiceContainer]
[Transient<AppSettings>]
[Singleton<IDatabase, Database>]
[Scoped<IAuthService, AuthService>]
public sealed partial class ServerSCDI;
Benchmark methods
[Benchmark]
public void MrMeeseeksDIE()
{
var container = ServerMrMeeseeks.DIE_CreateContainer();
var authService = container.Create();
}
[Benchmark]
public void Jab()
{
var container = new Jab.Tests.ServerJab();
var scope = container.CreateScope();
var authService = scope.GetService<Jab.Tests.IAuthService>();
}
[Benchmark]
public void SourceCrafter_DependencyInjection()
{
var container = new SourceCrafter.DependencyInjection.Tests.ServerSCDI();
var scope = container.CreateScope();
var authService = scope.GetAuthService();
}
Results:
BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4169/23H2/2023Update/SunValley3)
Intel Core i9-14900HX, 1 CPU, 32 logical and 24 physical cores
.NET SDK 9.0.100-rc.1.24452.12
[Host] : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2
Method | Job | Runtime | Mean | Error | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|---|---|---|---|---|
MrMeeseeksDIE | .NET 8.0 | .NET 8.0 | 721.34 ns | 14.413 ns | 1.00 | 0.03 | 0.0372 | 0.0362 | 0.0048 | 616 B | 1.00 |
MrMeeseeksDIE | .NET 9.0 | .NET 9.0 | 694.30 ns | 11.764 ns | 0.96 | 0.02 | 0.0343 | 0.0334 | 0.0067 | 616 B | 1.00 |
Jab | .NET 8.0 | .NET 8.0 | 32.79 ns | 0.586 ns | 1.00 | 0.02 | 0.0085 | - | - | 160 B | 1.00 |
Jab | .NET 9.0 | .NET 9.0 | 31.32 ns | 0.515 ns | 0.96 | 0.02 | 0.0085 | - | - | 160 B | 1.00 |
SourceCrafter_DependencyInjection | .NET 8.0 | .NET 8.0 | 13.58 ns | 0.062 ns | 1.00 | 0.01 | 0.0030 | - | - | 56 B | 1.00 |
SourceCrafter_DependencyInjection | .NET 9.0 | .NET 9.0 | 14.07 ns | 0.166 ns | 1.04 | 0.01 | 0.0030 | - | - | 56 B | 1.00 |
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 | 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
- Microsoft.CodeAnalysis.CSharp (>= 4.10.0)
- SourceCrafter.DependencyInjection.Interop (>= 0.24.294.5)
- System.Diagnostics.Tools (>= 4.3.0)
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.24.294.5 | 113 | 10/20/2024 |
0.24.290.68 | 102 | 10/16/2024 |
0.24.284.53 | 100 | 10/10/2024 |
0.24.284.9 | 96 | 10/10/2024 |
0.24.284.7 | 92 | 10/10/2024 |
0.24.284.6 | 104 | 10/10/2024 |
0.24.284.1 | 99 | 10/10/2024 |
0.24.283.90 | 106 | 10/9/2024 |
0.24.283.75 | 99 | 10/9/2024 |
0.24.283.66 | 100 | 10/9/2024 |
0.24.283.60 | 98 | 10/9/2024 |
0.24.283.58 | 95 | 10/9/2024 |
0.24.283.54 | 93 | 10/9/2024 |
0.24.283.8 | 110 | 10/9/2024 |
0.24.282.92 | 90 | 10/8/2024 |
0.24.282.78 | 101 | 10/8/2024 |
0.24.280.94 | 103 | 10/6/2024 |
0.24.280.92 | 103 | 10/6/2024 |
0.24.280.90 | 106 | 10/6/2024 |
0.24.280.87 | 109 | 10/6/2024 |
0.24.280.80 | 104 | 10/6/2024 |
0.24.280.70 | 112 | 10/6/2024 |
0.24.278.60 | 100 | 10/4/2024 |
0.24.278.56 | 99 | 10/4/2024 |
0.24.278.54 | 107 | 10/4/2024 |
0.24.278.53 | 104 | 10/4/2024 |
0.24.278.9 | 104 | 10/4/2024 |
0.24.277.7 | 107 | 10/3/2024 |
0.24.276.78 | 143 | 10/2/2024 |
0.24.276.19 | 108 | 10/2/2024 |
0.24.274.52 | 119 | 9/30/2024 |
0.24.274.51 | 109 | 9/30/2024 |