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                
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="SourceCrafter.DependencyInjection" Version="0.24.294.5" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add SourceCrafter.DependencyInjection --version 0.24.294.5                
#r "nuget: SourceCrafter.DependencyInjection, 0.24.294.5"                
#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 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, and Transient 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 the Server class as a container for services.
  • [JsonSetting<T>]: Specifies that the configuration section T should be loaded from a JSON configuration file. In the example, AppSettings and ConnectionStrings::DefaultConnection are loaded.
  • [Singleton<T, TImplementation>]: Registers a singleton service of type T with an implementation of TImplementation. Singleton services are created once and shared across the application.
  • [Scoped<T, TImplementation>]: Registers a scoped service of type T with an implementation of TImplementation. Scoped services are created once per request.
  • [Transient<T>]: Registers a transient service, meaning a new instance of T is created each time it is requested. In this example, the int value is generated using the ResolveAsync 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 using IAsyncDisposable.

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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