Dependency Injection Container Feature Comparison

General Information

Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Repository Url Link Link
License Url Link Link
Nuget Package Url Link Link
Documentation Url Link Link
Resolution Stage 🏃 Run-Time 🔨 Compile-Time

Implementations

Group of features concerned with resolving implementation types (non-abstract classes & structs).

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Concrete Class 🔍
A concrete (non-abstract) class is resolvable.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ImplementationsConcreteClass)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Implementations.ConcreteClass;

// Simple class that we want to create objects from using a container
internal class ConcreteClass
{
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // The type of the class needs to be registered with the container
        builder.Services.AddTransient<ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var concreteClass = host.Services.GetRequiredService<ConcreteClass>();
        Console.WriteLine(concreteClass.GetType().Name); // ConcreteClass
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ImplementationsConcreteClass)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Implementations.ConcreteClass;

// Simple class that we want to create objects from using a container
internal class ConcreteClass
{
}

// The type of the class needs to be registered with the container
[ImplementationAggregation(typeof(ConcreteClass))]
// Also, the container needs to have a create-function specified
[CreateFunction(typeof(ConcreteClass), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var concreteClass = container.Create();
        Console.WriteLine(concreteClass.GetType().Name); // ConcreteClass
    }
}
Struct 🔍
A struct is resolvable.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ImplementationsStruct)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Implementations.Struct;

// Simple struct that we want to create values from using a container
internal struct Struct
{
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();
        
        // Register Struct and create a new value each time
        builder.Services.Add(new ServiceDescriptor(typeof(Struct), _ => new Struct(), ServiceLifetime.Transient));
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var value = host.Services.GetRequiredService<Struct>();
        Console.WriteLine(value.GetType().Name); // Struct
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ImplementationsStruct)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Implementations.Struct;

// Simple struct that we want to create values from using a container
internal struct Struct
{
}

// The type of the struct needs to be registered with the container
[ImplementationAggregation(typeof(Struct))]
// Also, the container needs to have a create-function specified
[CreateFunction(typeof(Struct), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var value = container.Create();
        Console.WriteLine(value.GetType().Name); // Struct
    }
}
Record 🔍
A record is resolvable.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ImplementationsRecord)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Implementations.Record;

// Simple record that we want to create objects from using a container
internal record Record;

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // The type of the record needs to be registered with the container
        builder.Services.AddTransient<Record>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var record = host.Services.GetRequiredService<Record>();
        Console.WriteLine(record.GetType().Name); // Record
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ImplementationsRecord)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Implementations.Record;

// Simple record that we want to create objects from using a container
internal record Record
{
}

// The type of the record needs to be registered with the container
[ImplementationAggregation(typeof(Record))]
// Also, the container needs to have a create-function specified
[CreateFunction(typeof(Record), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var record = container.Create();
        Console.WriteLine(record.GetType().Name); // Record
    }
}
Struct Record 🔍
A value record is resolvable.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ImplementationsStructRecord)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Implementations.StructRecord;

// Simple struct-record that we want to create objects from using a container
internal record struct StructRecord
{
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Register StructRecord and create a new value each time
        builder.Services.Add(new ServiceDescriptor(typeof(StructRecord), _ => new StructRecord(), ServiceLifetime.Transient));
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var value = host.Services.GetRequiredService<StructRecord>();
        Console.WriteLine(value.GetType().Name); // StructRecord
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ImplementationsStructRecord)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Implementations.StructRecord;

// Simple struct-record that we want to create objects from using a container
internal record struct StructRecord
{
}

// The type of the struct-record needs to be registered with the container
[ImplementationAggregation(typeof(StructRecord))]
// Also, the container needs to have a create-function specified
[CreateFunction(typeof(StructRecord), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var structRecord = container.Create();
        Console.WriteLine(structRecord.GetType().Name); // StructRecord
    }
}
Explicit Constructor Choice 🔍
If an implementation type has multiple constructors, the container is configurable to select a specific one.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ImplementationsExplicitConstructorChoice)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Implementations.ExplicitConstructorChoice;

// This class has three constructors
// That means that it is ambiguous which constructor to choose
internal class ConcreteClass
{
    public ConcreteClass() => Char = 'a';
    // This attribute tells the container to use this constructor
    [ActivatorUtilitiesConstructor]
    public ConcreteClass(double _) => Char = 'b';
    public ConcreteClass(DateTime _, FileInfo __) => Char = 'c';
    internal char Char { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Register the dependency for the chosen constructor
        builder.Services.Add(new ServiceDescriptor(typeof(double), 1.0));
        
        // Register the dependencies for the other constructors. So in principle, we could have chosen any of the constructors
        builder.Services.Add(new ServiceDescriptor(typeof(DateTime), DateTime.Now));
        builder.Services.Add(new ServiceDescriptor(typeof(FileInfo), new FileInfo("file.txt")));
        
        // Register the class itself and use the ActivatorUtilities to create an instance
        builder.Services.AddTransient<ConcreteClass>(sp => ActivatorUtilities.CreateInstance<ConcreteClass>(sp));
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var concreteClass = host.Services.GetRequiredService<ConcreteClass>();
        Console.WriteLine($"Char: {concreteClass.Char}"); // Char: b
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ImplementationsExplicitConstructorChoice)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Implementations.ExplicitConstructorChoice;

// This class has two constructors, one with an int parameter and one without
// That means that it is ambiguous which constructor to choose
internal class ConcreteClass
{
    internal ConcreteClass() => Value = 68;
    internal ConcreteClass(int value) => Value = value;
    
    internal int Value { get; }
}

[ImplementationAggregation(typeof(ConcreteClass))]
// Choose the constructor with the int parameter
[ConstructorChoice(typeof(ConcreteClass), typeof(int))]

[CreateFunction(typeof(ConcreteClass), "Create")]
internal partial class Container
{
    private Container() {}

    // Custom factory for the int parameter
    private int DIE_Factory => 69;
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var concreteClass = container.Create();
        Console.WriteLine($"The value is {concreteClass.Value}"); // The value is 69
    }
}
Struct Parameterless Constructor Ignored 🔍
If a struct type has a non-parameterless constructor, then the parameterless constructor will be ignored.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ImplementationsStructParameterlessConstructorIgnored)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Implementations.StructParameterlessConstructorIgnored;

// This struct has an explicit constructor in addition to the parameterless one this makes two constructors
// That means that it is technically ambiguous which constructor to choose
internal struct Struct
{
    internal Struct(int value) => Value = value; 
    internal int Value { get; }
}

// Configure no constructor choice
// The container should implicitly ignore the parameterless constructor
// Therefore, it should choose the single explicit constructor
[ImplementationAggregation(typeof(Struct))]
[CreateFunction(typeof(Struct), "Create")]
internal partial class Container
{
    private Container() {}

    // Custom factory for the int parameter
    private int DIE_Factory => 69;
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var value = container.Create();
        Console.WriteLine($"The value is {value.Value}"); // The value is 69
    }
}
Nullable Concrete Class (Null Case) 🔍
Injection of an instance of a nullable concrete class type. If the container doesn't know of the concrete class, it resolves to null.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ImplementationsNullableConcreteClassNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Implementations.NullableConcreteClassNullCase;

internal class ConcreteClass
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    public Parent(ConcreteClass? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Register ConcreteClass but map it to null
        builder.Services.AddTransient<ConcreteClass>(_ => null!);
        builder.Services.AddTransient<Parent>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ImplementationsNullableConcreteClassNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Implementations.NullableConcreteClassNullCase;

internal class ConcreteClass
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    internal Parent(ConcreteClass? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

// Don't register the ConcreteClass as an implementation
[ImplementationAggregation(typeof(Parent))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: True
    }
}
Nullable Concrete Class (Not Null Case) 🔍
Injection of an instance of a nullable concrete class type. If the container knows the concrete class, it resolves to an instance of this type.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ImplementationsNullableConcreteClassNotNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Implementations.NullableConcreteClassNotNullCase;

internal class ConcreteClass
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    public Parent(ConcreteClass? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Register ConcreteClass
        builder.Services.AddTransient<ConcreteClass>();
        builder.Services.AddTransient<Parent>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: False
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ImplementationsNullableConcreteClassNotNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Implementations.NullableConcreteClassNotNullCase;

internal class ConcreteClass
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    internal Parent(ConcreteClass? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

// Register the ConcreteClass as an implementation
[ImplementationAggregation(typeof(Parent), typeof(ConcreteClass))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: False
    }
}
Nullable Struct (Null Case) 🔍
Injection of an instance of a nullable struct type. If the container doesn't know of the concrete class, it resolves to null.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ImplementationsNullableStructNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Implementations.NullableStructNullCase;

internal struct Struct
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    public Parent(Struct? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Register Struct? but map it to null
        builder.Services.Add(new ServiceDescriptor(typeof(Struct?), _ => null!, ServiceLifetime.Transient));
        builder.Services.AddTransient<Parent>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ImplementationsNullableStructNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Implementations.NullableStructNullCase;

internal struct Struct
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    internal Parent(Struct? child) => IsNull = !child.HasValue; 
    internal bool IsNull { get; }
}

// Don't register the Struct as an implementation
[ImplementationAggregation(typeof(Parent))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: True
    }
}
Nullable Struct (Not Null Case) 🔍
Injection of an instance of a nullable struct type. If the container knows the concrete class, it resolves to an instance of this type.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ImplementationsNullableStructNotNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Implementations.NullableStructNotNullCase;

internal struct Struct
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    public Parent(Struct? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Register Struct?
        builder.Services.Add(new ServiceDescriptor(typeof(Struct?), _ => new Struct(), ServiceLifetime.Transient));
        builder.Services.AddTransient<Parent>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: False
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ImplementationsNullableStructNotNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Implementations.NullableStructNotNullCase;

internal struct Struct
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    internal Parent(Struct? child) => IsNull = !child.HasValue; 
    internal bool IsNull { get; }
}

// Register the Struct as an implementation
[ImplementationAggregation(typeof(Parent), typeof(Struct))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: False
    }
}

Abstractions

Group of features concerned with resolving abstraction types (interfaces & abstract classes).

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Interface (Single Implementation) 🔍
Resolution of an interface type which has a single implementation type.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.AbstractionsInterfaceSingleImplementation)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Abstractions.InterfaceSingleImplementation;

// Simple interface that will work as an abstraction for an implementation
internal interface IInterface
{
}

// Simple class that will be used as an implementation for the interface
internal class ConcreteClass : IInterface
{
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Explicitly choose ConcreteClass as the implementation for AbstractClass
        builder.Services.AddTransient<IInterface, ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = AbstractClassSingleImplementation.Builder.CreateBuilder().Build();

        var concreteClass = host.Services.GetRequiredService<IInterface>();
        Console.WriteLine(concreteClass.GetType().Name); // ConcreteClass
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AbstractionsInterfaceSingleImplementation)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Abstractions.InterfaceSingleImplementation;

// Simple interface that will work as an abstraction for an implementation
internal interface IInterface
{
}

// Simple class that will be used as an implementation for the interface
internal class ConcreteClass : IInterface
{
}

// Register implementation type only
// No explicit mapping to the interface is needed since it is the only implementation
[ImplementationAggregation(typeof(ConcreteClass))]
[CreateFunction(typeof(IInterface), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var instance = container.Create();
        Console.WriteLine(instance.GetType().Name); // ConcreteClass
    }
}
Abstract Class (Single Implementation) 🔍
Resolution of an abstract class type which has a single implementation type.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.AbstractionsAbstractClassSingleImplementation)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Abstractions.AbstractClassSingleImplementation;

// Simple abstract class that will work as an abstraction for a concrete class
internal abstract class AbstractClass
{
}

// Simple class that will be used as an implementation for the abstract class
internal class ConcreteClass : AbstractClass
{
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Explicitly choose ConcreteClass as the implementation for AbstractClass
        builder.Services.AddTransient<AbstractClass, ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var concreteClass = host.Services.GetRequiredService<AbstractClass>();
        Console.WriteLine(concreteClass.GetType().Name); // ConcreteClass
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AbstractionsAbstractClassSingleImplementation)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Abstractions.AbstractClassSingleImplementation;

// Simple abstract class that will work as an abstraction for a concrete class
internal abstract class AbstractClass
{
}

// Simple class that will be used as an implementation for the abstract class
internal class ConcreteClass : AbstractClass
{
}

// Register implementation type only
// No explicit mapping to the abstract class is needed since it is the only implementation
[ImplementationAggregation(typeof(ConcreteClass))]
[CreateFunction(typeof(AbstractClass), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var abstractClass = container.Create();
        Console.WriteLine(abstractClass.GetType().Name); // ConcreteClass
    }
}
Interface (Multiple Implementation) 🔍
Resolution of an interface type which has multiple implementation types.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.AbstractionsInterfaceMultipleImplementation)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Abstractions.InterfaceMultipleImplementation;

// Simple interface that will work as an abstraction for an implementation
internal interface IInterface
{
}

// Simple class that will be used as an implementation for the interface
internal class ConcreteClass : IInterface
{
}

// Another simple class that implements the interface
internal class AnotherConcreteClass : IInterface
{
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Explicitly choose ConcreteClass as the implementation for IInterface
        builder.Services.AddTransient<IInterface, ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var concreteClass = host.Services.GetRequiredService<IInterface>();
        Console.WriteLine(concreteClass.GetType().Name); // ConcreteClass
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AbstractionsInterfaceMultipleImplementation)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Abstractions.InterfaceMultipleImplementation;

// Simple interface that will work as an abstraction for an implementation
internal interface IInterface
{
}

// Simple class that will be used as an implementation for the interface
internal class ConcreteClass : IInterface
{
}

// Another simple class that implements the interface
internal class AnotherConcreteClass : IInterface
{
}

// Registering both implementation types leads to an ambiguity which implementation to choose for the interface
[ImplementationAggregation(typeof(ConcreteClass), typeof(AnotherConcreteClass))]
// Explicitly choose ConcreteClass as the implementation for IInterface
[ImplementationChoice(typeof(IInterface), typeof(ConcreteClass))]
[CreateFunction(typeof(IInterface), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var instance = container.Create();
        Console.WriteLine(instance.GetType().Name); // ConcreteClass
    }
}
Abstract Class (Multiple Implementation) 🔍
Resolution of an abstract class type which has multiple implementation types.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.AbstractionsAbstractClassMultipleImplementation)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Abstractions.AbstractClassMultipleImplementation;

// Simple abstract class that will work as an abstraction for a concrete class
internal abstract class AbstractClass
{
}

// Simple class that will be used as an implementation for the abstract class
internal class ConcreteClass : AbstractClass
{
}

// Another simple class that implements the abstract class
internal class AnotherConcreteClass : AbstractClass
{
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Explicitly choose ConcreteClass as the implementation for AbstractClass
        builder.Services.AddTransient<AbstractClass, ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var concreteClass = host.Services.GetRequiredService<AbstractClass>();
        Console.WriteLine(concreteClass.GetType().Name); // ConcreteClass
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AbstractionsAbstractClassMultipleImplementation)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Abstractions.AbstractClassMultipleImplementation;

// Simple abstract class that will work as an abstraction for a concrete class
internal abstract class AbstractClass
{
}

// Simple class that will be used as an implementation for the abstract class
internal class ConcreteClass : AbstractClass
{
}

// Another simple class that implements the abstract class
internal class AnotherConcreteClass : AbstractClass
{
}

// Registering both implementation types leads to an ambiguity which implementation to choose for the abstract class
[ImplementationAggregation(typeof(ConcreteClass), typeof(AnotherConcreteClass))]
// Explicitly choose ConcreteClass as the implementation for AbstractClass
[ImplementationChoice(typeof(AbstractClass), typeof(ConcreteClass))]
[CreateFunction(typeof(AbstractClass), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var abstractClass = container.Create();
        Console.WriteLine(abstractClass.GetType().Name); // ConcreteClass
    }
}
Nullable Interface (Null Case) 🔍
Injection of an instance of a nullable interface type. If the container doesn't know of an implementation for this interface, it resolves to null.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.AbstractionsNullableInterfaceNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Abstractions.NullableInterfaceNullCase;

// Simple interface that doesn't have any implementation
internal interface IInterface
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    public Parent(IInterface? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Explicitly choose null for IInterface
        builder.Services.AddTransient<IInterface>(_ => null!);
        builder.Services.AddTransient<Parent>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AbstractionsNullableInterfaceNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Abstractions.NullableInterfaceNullCase;

// Simple interface that doesn't have any implementation
internal interface IInterface
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    internal Parent(IInterface? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

// There is no concrete implementation of IInterface to register
[ImplementationAggregation(typeof(Parent))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: True
    }
}
Nullable Interface (Not Null Case) 🔍
Injection of an instance of a nullable concrete class type. If the container knows of an implementation for this interface, it resolves to an instance of the implementation.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.AbstractionsNullableInterfaceNotNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Abstractions.NullableInterfaceNotNullCase;

// Simple interface that will work as an abstraction for a concrete class
internal interface IInterface
{
}

// Simple class that will be used as an implementation for the interface
internal class ConcreteClass : IInterface
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    public Parent(IInterface? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Explicitly choose ConcreteClass as the implementation for IInterface
        builder.Services.AddTransient<IInterface, ConcreteClass>();
        builder.Services.AddTransient<Parent>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: False
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AbstractionsNullableInterfaceNotNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Abstractions.NullableInterfaceNotNullCase;

// Simple interface that will work as an abstraction for a concrete class
internal interface IInterface
{
}

// Simple class that will be used as an implementation for the interface
internal class ConcreteClass : IInterface
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    internal Parent(IInterface? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClass))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: False
    }
}
Nullable Abstract Class (Null Case) 🔍
Injection of an instance of a nullable abstract class type. If the container doesn't know of an implementation for this abstract class type, it resolves to null.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.AbstractionsNullableAbstractClassNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Abstractions.NullableAbstractClassNullCase;

// Simple abstract class that doesn't have any implementation
internal abstract class AbstractClass
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    public Parent(AbstractClass? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Explicitly choose null for AbstractClass
        builder.Services.AddTransient<AbstractClass>(_ => null!);
        builder.Services.AddTransient<Parent>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AbstractionsNullableAbstractClassNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Abstractions.NullableAbstractClassNullCase;

// Simple abstract class that doesn't have any implementation
internal abstract class AbstractClass
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    internal Parent(AbstractClass? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

// There is no concrete implementation of AbstractClass to register
[ImplementationAggregation(typeof(Parent))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: True
    }
}
Nullable Abstract Class (Not Null Case) 🔍
Injection of an instance of a nullable abstract class type. If the container knows of an implementation for this abstract class type, it resolves to an instance of this type.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.AbstractionsNullableAbstractClassNotNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Abstractions.NullableAbstractClassNotNullCase;

// Simple abstract class that will work as an abstraction for a concrete class
internal abstract class AbstractClass
{
}

// Simple class that will be used as an implementation for the abstract class
internal class ConcreteClass : AbstractClass
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    public Parent(AbstractClass? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Explicitly choose ConcreteClass as the implementation for AbstractClass
        builder.Services.AddTransient<AbstractClass, ConcreteClass>();
        builder.Services.AddTransient<Parent>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: False
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AbstractionsNullableAbstractClassNotNullCase)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Abstractions.NullableAbstractClassNotNullCase;

// Simple abstract class that will work as an abstraction for a concrete class
internal abstract class AbstractClass
{
}

// Simple class that will be used as an implementation for the abstract class
internal class ConcreteClass : AbstractClass
{
}

// Utility class to get a nullable injection and check if it is null
internal class Parent
{
    internal Parent(AbstractClass? child) => IsNull = child is null; 
    internal bool IsNull { get; }
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClass))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Is null: {parent.IsNull}"); // Is null: False
    }
}

Generics

Group of features concerned with resolving generic types.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Generic Implementation 🔍
A generic implementation type is resolvable.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.GenericsImplementation)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Generics.Implementation;

// Different types of generic implementations
internal class ConcreteClass<T>
{
}

internal record Record<T>
{
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Registering the generic types with the container
        builder.Services.Add(new ServiceDescriptor(typeof(ConcreteClass<>), typeof(ConcreteClass<>), ServiceLifetime.Transient));
        builder.Services.Add(new ServiceDescriptor(typeof(Record<>), typeof(Record<>), ServiceLifetime.Transient));
        
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var concreteClassOfInt = host.Services.GetRequiredService<ConcreteClass<int>>();
        Console.WriteLine($"{concreteClassOfInt.GetType().Name} {concreteClassOfInt.GetType().GenericTypeArguments.First().Name}"); // ConcreteClass`1 Int32
        var concreteClassOfString = host.Services.GetRequiredService<ConcreteClass<string>>();
        Console.WriteLine($"{concreteClassOfString.GetType().Name} {concreteClassOfString.GetType().GenericTypeArguments.First().Name}"); // ConcreteClass`1 String
        var recordOfInt = host.Services.GetRequiredService<Record<int>>();
        Console.WriteLine($"{recordOfInt.GetType().Name} {recordOfInt.GetType().GenericTypeArguments.First().Name}"); // Record`1 Int32
        var recordOfString = host.Services.GetRequiredService<Record<string>>();
        Console.WriteLine($"{recordOfString.GetType().Name} {recordOfString.GetType().GenericTypeArguments.First().Name}"); // Record`1 String
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.GenericsImplementation)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Generics.Implementation;

// Different types of generic implementations
internal class ConcreteClass<T>
{
}

internal struct Struct<T>
{
}

internal record Record<T>
{
}

internal record struct RecordStruct<T>
{
}

// Register the generic implementations
[ImplementationAggregation(typeof(ConcreteClass<>), typeof(Struct<>), typeof(Record<>), typeof(RecordStruct<>))]
// Define create functions for some closed versions of the generic implementation types
[CreateFunction(typeof(ConcreteClass<int>), "CreateConcreteClassOfInt")]
[CreateFunction(typeof(ConcreteClass<string>), "CreateConcreteClassOfString")]
[CreateFunction(typeof(Struct<int>), "CreateStructOfInt")]
[CreateFunction(typeof(Struct<string>), "CreateStructOfString")]
[CreateFunction(typeof(Record<int>), "CreateRecordOfInt")]
[CreateFunction(typeof(Record<string>), "CreateRecordOfString")]
[CreateFunction(typeof(RecordStruct<int>), "CreateRecordStructOfInt")]
[CreateFunction(typeof(RecordStruct<string>), "CreateRecordStructOfString")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var concreteClassOfInt = container.CreateConcreteClassOfInt();
        var concreteClassOfString = container.CreateConcreteClassOfString();
        var structOfInt = container.CreateStructOfInt();
        var structOfString = container.CreateStructOfString();
        var recordOfInt = container.CreateRecordOfInt();
        var recordOfString = container.CreateRecordOfString();
        var recordStructOfInt = container.CreateRecordStructOfInt();
        var recordStructOfString = container.CreateRecordStructOfString();
        Console.WriteLine($"{concreteClassOfInt.GetType().Name} {concreteClassOfInt.GetType().GenericTypeArguments.First().Name}"); // ConcreteClass`1 Int32
        Console.WriteLine($"{concreteClassOfString.GetType().Name} {concreteClassOfString.GetType().GenericTypeArguments.First().Name}"); // ConcreteClass`1 String
        Console.WriteLine($"{structOfInt.GetType().Name} {structOfInt.GetType().GenericTypeArguments.First().Name}"); // Struct`1 Int32
        Console.WriteLine($"{structOfString.GetType().Name} {structOfString.GetType().GenericTypeArguments.First().Name}"); // Struct`1 String
        Console.WriteLine($"{recordOfInt.GetType().Name} {recordOfInt.GetType().GenericTypeArguments.First().Name}"); // Record`1 Int32
        Console.WriteLine($"{recordOfString.GetType().Name} {recordOfString.GetType().GenericTypeArguments.First().Name}"); // Record`1 String
        Console.WriteLine($"{recordStructOfInt.GetType().Name} {recordStructOfInt.GetType().GenericTypeArguments.First().Name}"); // RecordStruct`1 Int32
        Console.WriteLine($"{recordStructOfString.GetType().Name} {recordStructOfString.GetType().GenericTypeArguments.First().Name}"); // RecordStruct`1 String
    }
}
Generic Abstraction 🔍
A generic abstraction type is resolvable.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.GenericsAbstraction)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Generics.Abstraction;

// Different types of generic abstractions
internal interface IInterface<T>
{
}

internal abstract class AbstractClass<T>
{
}

// An implementation of the generic abstractions
internal class ConcreteClass<T> : AbstractClass<T>, IInterface<T>
{
}

// Register the generic implementation
[ImplementationAggregation(typeof(ConcreteClass<>))]
// Define create functions for some closed versions of the generic abstraction types
[CreateFunction(typeof(IInterface<int>), "CreateIInterfaceOfInt")]
[CreateFunction(typeof(IInterface<string>), "CreateIInterfaceOfString")]
[CreateFunction(typeof(AbstractClass<int>), "CreateAbstractClassOfInt")]
[CreateFunction(typeof(AbstractClass<string>), "CreateAbstractClassOfString")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var iInterfaceClassOfInt = container.CreateIInterfaceOfInt();
        var iInterfaceOfString = container.CreateIInterfaceOfString();
        var abstractClassOfInt = container.CreateAbstractClassOfInt();
        var abstractClassOfString = container.CreateAbstractClassOfString();
        Console.WriteLine($"{iInterfaceClassOfInt.GetType().Name} {iInterfaceClassOfInt.GetType().GenericTypeArguments.First().Name}"); // ConcreteClass`1 Int32
        Console.WriteLine($"{iInterfaceOfString.GetType().Name} {iInterfaceOfString.GetType().GenericTypeArguments.First().Name}"); // ConcreteClass`1 String
        Console.WriteLine($"{abstractClassOfInt.GetType().Name} {abstractClassOfInt.GetType().GenericTypeArguments.First().Name}"); // ConcreteClass`1 Int32
        Console.WriteLine($"{abstractClassOfString.GetType().Name} {abstractClassOfString.GetType().GenericTypeArguments.First().Name}"); // ConcreteClass`1 String
    }
}
Open Generic Parameter Fill-Ins 🔍
If an abstraction has less generic parameter than its implementation, then upon resolution of the abstraction one or more generic parameters will inevitably remain open. The container is able to configure the fill-ins for these open generic parameters.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.GenericsOpenFillIns)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Generics.OpenFillIns;

// A generic interface with only one generic parameter
internal interface IInterface<T>
{
}

// A generic class implementing the generic interface but with two generic parameters
internal class ConcreteClass<T, TA> : IInterface<T>
{
}

// Register the generic implementation
[ImplementationAggregation(typeof(ConcreteClass<,>))]
// Choose string as the default type for the second generic parameter
[GenericParameterChoice(typeof(ConcreteClass<,>), "TA", typeof(string))]
// Choose some other types for the second generic parameter
[GenericParameterSubstitutesChoice(typeof(ConcreteClass<,>), "TA", typeof(bool), typeof(double))]
// We'll test resolution of a single instance of the interface
[CreateFunction(typeof(IInterface<int>), "CreateSingular")]
// And resolution of multiple instances of the interface
[CreateFunction(typeof(IInterface<int>[]), "CreateMultiple")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        // The singular option will fill the open generic parameter with string
        var singular = container.CreateSingular();
        Console.WriteLine($"{singular.GetType().Name} {singular.GetType().GenericTypeArguments.First().Name} {singular.GetType().GenericTypeArguments.Skip(1).First().Name}"); // ConcreteClass`2 Int32 String
        // The multiple option will fill the open generic parameter with string, bool and double
        // That means the singular option is implicitly included in the multiple option
        var multiple = container.CreateMultiple();
        foreach (var instance in multiple)
        {
            Console.WriteLine($"{instance.GetType().Name} {instance.GetType().GenericTypeArguments.First().Name} {instance.GetType().GenericTypeArguments.Skip(1).First().Name}");
        }
    }
}

Iterables

Group of features concerned with resolving iterable types.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Iterable: IEnumerable<T> 🔍
Resolution of an IEnumerable<T>. It should iterate over an instance of each known implementation type of T.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.IterablesIEnumerable)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Iterables.IEnumerableOfT;

// An interface and its implementations
internal interface IInterface
{
}

internal class ConcreteClass : IInterface
{
}

internal record Record : IInterface;

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Register the implementations mapping them to the interface
        builder.Services.AddTransient<IInterface, ConcreteClass>();
        builder.Services.AddTransient<IInterface, Record>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var iterable = host.Services.GetRequiredService<IEnumerable<IInterface>>();
        foreach (var item in iterable)
        {
            Console.WriteLine(item.GetType().Name);
        }
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.IterablesIEnumerable)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Iterables.IEnumerableOfT;

// An interface and its implementations
internal interface IInterface
{
}

internal class ConcreteClass : IInterface
{
}

internal struct Struct : IInterface
{
}

internal record Record : IInterface;

internal record struct RecordStruct : IInterface;

// Register the implementations
[ImplementationAggregation(typeof(ConcreteClass), typeof(Struct), typeof(Record), typeof(RecordStruct))]
[CreateFunction(typeof(IEnumerable<IInterface>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var iterable = container.Create();
        foreach (var implementation in iterable)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
    }
}
Iterable: Generic Array 🔍
Resolution of an array of T. It should contain an instance of each known implementation type of T.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.IterablesArray)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Iterables.ArrayOfT;

// An interface and its implementations
internal interface IInterface
{
}

internal class ConcreteClass : IInterface
{
}

internal struct Struct : IInterface
{
}

internal record Record : IInterface;

internal record struct RecordStruct : IInterface;

// Register the implementations
[ImplementationAggregation(typeof(ConcreteClass), typeof(Struct), typeof(Record), typeof(RecordStruct))]
[CreateFunction(typeof(IInterface[]), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var iterable = container.Create();
        foreach (var implementation in iterable)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
    }
}
Iterable: IReadOnlyList<T> 🔍
Resolution of IReadOnlyList<T>. It should contain an instance of each known implementation type of T.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.IterablesIReadOnlyList)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Iterables.IReadOnlyListOfT;

// An interface and its implementations
internal interface IInterface
{
}

internal class ConcreteClass : IInterface
{
}

internal struct Struct : IInterface
{
}

internal record Record : IInterface;

internal record struct RecordStruct : IInterface;

// Register the implementations
[ImplementationAggregation(typeof(ConcreteClass), typeof(Struct), typeof(Record), typeof(RecordStruct))]
[CreateFunction(typeof(IReadOnlyList<IInterface>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var iterable = container.Create();
        foreach (var implementation in iterable)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
    }
}
Iterable: IList<T> 🔍
Resolution of IList<T>. It should contain an instance of each known implementation type of T.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.IterablesIList)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Iterables.IListOfT;

// An interface and its implementations
internal interface IInterface
{
}

internal class ConcreteClass : IInterface
{
}

internal struct Struct : IInterface
{
}

internal record Record : IInterface;

internal record struct RecordStruct : IInterface;

// Register the implementations
[ImplementationAggregation(typeof(ConcreteClass), typeof(Struct), typeof(Record), typeof(RecordStruct))]
[CreateFunction(typeof(IList<IInterface>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var iterable = container.Create();
        foreach (var implementation in iterable)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
    }
}
Iterable: ReadOnlyCollection<T> 🔍
Resolution of ReadOnlyCollection<T>. It should contain an instance of each known implementation type of T.
using System.Collections.ObjectModel;
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.IterablesReadOnlyCollection)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Iterables.ReadOnlyCollectionOfT;

// An interface and its implementations
internal interface IInterface
{
}

internal class ConcreteClass : IInterface
{
}

internal struct Struct : IInterface
{
}

internal record Record : IInterface;

internal record struct RecordStruct : IInterface;

// Register the implementations
[ImplementationAggregation(typeof(ConcreteClass), typeof(Struct), typeof(Record), typeof(RecordStruct))]
[CreateFunction(typeof(ReadOnlyCollection<IInterface>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var iterable = container.Create();
        foreach (var implementation in iterable)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
    }
}
Iterable: List<T> 🔍
Resolution of List<T>. It should contain an instance of each known implementation type of T.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.IterablesList)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Iterables.ListOfT;

// An interface and its implementations
internal interface IInterface
{
}

internal class ConcreteClass : IInterface
{
}

internal struct Struct : IInterface
{
}

internal record Record : IInterface;

internal record struct RecordStruct : IInterface;

// Register the implementations
[ImplementationAggregation(typeof(ConcreteClass), typeof(Struct), typeof(Record), typeof(RecordStruct))]
[CreateFunction(typeof(List<IInterface>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var iterable = container.Create();
        foreach (var implementation in iterable)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
    }
}
Iterable: Other Iterable Types 🔍
Resolution of remaining supported collection types. Each of the collection instance should contain an instance of each known implementation type of T.
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.IterablesRemainingIterableTypes)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Iterables.RemainingIterableTypes;

// An interface and its implementations
internal interface IInterface
{
}

internal class ConcreteClass : IInterface
{
}

internal struct Struct : IInterface
{
}

internal record Record : IInterface;

internal record struct RecordStruct : IInterface;

// Register the implementations
[ImplementationAggregation(typeof(ConcreteClass), typeof(Struct), typeof(Record), typeof(RecordStruct))]
[CreateFunction(typeof(ICollection<IInterface>), "CreateICollection")]
[CreateFunction(typeof(IReadOnlyCollection<IInterface>), "CreateIReadOnlyCollection")]
[CreateFunction(typeof(ReadOnlyCollection<IInterface>), "CreateReadOnlyCollection")]
[CreateFunction(typeof(ArraySegment<IInterface>), "CreateArraySegment")]
[CreateFunction(typeof(ConcurrentBag<IInterface>), "CreateConcurrentBag")]
[CreateFunction(typeof(ConcurrentQueue<IInterface>), "CreateConcurrentQueue")]
[CreateFunction(typeof(ConcurrentStack<IInterface>), "CreateConcurrentStack")]
[CreateFunction(typeof(HashSet<IInterface>), "CreateHashSet")]
[CreateFunction(typeof(LinkedList<IInterface>), "CreateLinkedList")]
[CreateFunction(typeof(Queue<IInterface>), "CreateQueue")]
[CreateFunction(typeof(Stack<IInterface>), "CreateStack")]
[CreateFunction(typeof(SortedSet<IInterface>), "CreateSortedSet")]
[CreateFunction(typeof(ImmutableArray<IInterface>), "CreateImmutableArray")]
[CreateFunction(typeof(ImmutableHashSet<IInterface>), "CreateImmutableHashSet")]
[CreateFunction(typeof(ImmutableList<IInterface>), "CreateImmutableList")]
[CreateFunction(typeof(ImmutableQueue<IInterface>), "CreateImmutableQueue")]
[CreateFunction(typeof(ImmutableSortedSet<IInterface>), "CreateImmutableSortedSet")]
[CreateFunction(typeof(ImmutableStack<IInterface>), "CreateImmutableStack")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var iCollection = container.CreateICollection();
        foreach (var implementation in iCollection)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var iReadOnlyCollection = container.CreateIReadOnlyCollection();
        foreach (var implementation in iReadOnlyCollection)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var readOnlyCollection = container.CreateReadOnlyCollection();
        foreach (var implementation in readOnlyCollection)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var arraySegment = container.CreateArraySegment();
        foreach (var implementation in arraySegment)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var concurrentBag = container.CreateConcurrentBag();
        foreach (var implementation in concurrentBag)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var concurrentQueue = container.CreateConcurrentQueue();
        foreach (var implementation in concurrentQueue)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var concurrentStack = container.CreateConcurrentStack();
        foreach (var implementation in concurrentStack)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var hashSet = container.CreateHashSet();
        foreach (var implementation in hashSet)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var linkedList = container.CreateLinkedList();
        foreach (var implementation in linkedList)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var queue = container.CreateQueue();
        foreach (var implementation in queue)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var stack = container.CreateStack();
        foreach (var implementation in stack)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var sortedSet = container.CreateSortedSet();
        foreach (var implementation in sortedSet)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var immutableArray = container.CreateImmutableArray();
        foreach (var implementation in immutableArray)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var immutableHashSet = container.CreateImmutableHashSet();
        foreach (var implementation in immutableHashSet)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var immutableList = container.CreateImmutableList();
        foreach (var implementation in immutableList)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var immutableQueue = container.CreateImmutableQueue();
        foreach (var implementation in immutableQueue)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var immutableSortedSet = container.CreateImmutableSortedSet();
        foreach (var implementation in immutableSortedSet)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
        var immutableStack = container.CreateImmutableStack();
        foreach (var implementation in immutableStack)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
    }
}
Iterable: Implementations Choice 🔍
With this feature an explicit set of implementations is configurable for collection injection. That way the amount of injected implementations can be limited, even if the container actually knows more.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.IterablesImplementationsChoice)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Iterables.ImplementationsChoice;

// An interface and its implementations
internal interface IInterface
{
}

internal class ConcreteClass : IInterface
{
}

internal struct Struct : IInterface
{
}

internal record Record : IInterface;

internal record struct RecordStruct : IInterface;

// Register the implementations
[ImplementationAggregation(typeof(ConcreteClass), typeof(Struct), typeof(Record), typeof(RecordStruct))]
// With following attribute the set of implementations for iterable resolutions is reduced to ConcreteClass, Record
// That may be practical in combination of convenience functions like automatically registration all implementations at once
[ImplementationCollectionChoice(typeof(IInterface), typeof(ConcreteClass), typeof(Record))]
[CreateFunction(typeof(IEnumerable<IInterface>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var iterable = container.Create();
        foreach (var implementation in iterable)
        {
            Console.WriteLine(implementation.GetType().Name);
        }
    }
}

Factories

Group of features concerned with resolving factory types. Factories here are functors (Func<T>, Lazy<T>) that are automatically generated by the DI container.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Factory: Func<T> 🔍
Resolution of a Func<T>. Such a factory delays the resolution of a T-instance to whenever the owning component see fit. Also it allows to resolve multiple instances of T (as long as T isn't shared in the current scope).
Microsoft.Extensions.DependencyInjection does not support factories (Func<T>/Lazy<T>): https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#default-service-container-replacement
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.FactoriesFunc)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Factories.FuncOfT;

internal class ConcreteClass
{
}

[ImplementationAggregation(typeof(ConcreteClass))]
// Instead of returning the implementation type directly, return a factory function that creates instances of the implementation type
[CreateFunction(typeof(Func<ConcreteClass>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var concreteClassFactory = container.Create();
        // Factories defer the time of creation of the instance to when the factory is called
        // Also each call to the factory creates a new instance (as long as the implementation type is not scoped)
        var concreteClassA = concreteClassFactory();
        var concreteClassB = concreteClassFactory();
        Console.WriteLine(concreteClassA.GetType().Name); // ConcreteClass
        Console.WriteLine(concreteClassB.GetType().Name); // ConcreteClass
        Console.WriteLine(concreteClassA == concreteClassB); // False
    }
}
Factory: Func<TA, T> 🔍
Resolution of a Func<TA, T> with TA-parameter being used for TA-dependencies in T. Works much like a resolution of a Func<T>. The difference is that the factory is able to resolve T with the help of a parameter of type TA. This is practical for parameter types which are usually not registered in the container (such as strings, enums or numeric types).
Microsoft.Extensions.DependencyInjection does not support factories (Func<T>/Lazy<T>): https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#default-service-container-replacement
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.FactoriesFuncWithParameter)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Factories.FuncOfTAAndT;

internal class ConcreteClass
{
    // Notice the int parameter
    internal ConcreteClass(int i) { }
}

// Don't register the type of int
[ImplementationAggregation(typeof(ConcreteClass))]
// Make int a parameter of the Func-factory
[CreateFunction(typeof(Func<int, ConcreteClass>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var concreteClassFactory = container.Create();
        // Additionally to the deferred creation, the factory now also takes a parameter from the caller side
        // That means the int-parameter won't be resolved from the container, but is determined by the caller
        var concreteClassA = concreteClassFactory(6);
        var concreteClassB = concreteClassFactory(9);
        Console.WriteLine(concreteClassA.GetType().Name); // ConcreteClass
        Console.WriteLine(concreteClassB.GetType().Name); // ConcreteClass
        Console.WriteLine(concreteClassA == concreteClassB); // False
    }
}
Factory: Func<TA, T> (Sub-Dependencies) 🔍
Resolution of a Func<TA, T> with TA being used for TA-dependencies in T and in T's sub-dependencies transitively. Works like the ordinary Func<TA, T> resolution, but with TA also being used for sub-dependencies.
Microsoft.Extensions.DependencyInjection does not support factories (Func<T>/Lazy<T>): https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#default-service-container-replacement
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.FactoriesFuncWithParameterForSubDependencies)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Factories.FuncOfTAAndTSubDependencies;

internal class ConcreteClass
{
    // Notice the int parameter
    internal ConcreteClass(int i) { }
}

internal class Parent
{
    internal Parent(ConcreteClass child) {}
}

// Don't register the type of int
[ImplementationAggregation(typeof(Parent), typeof(ConcreteClass))]
// Make int a parameter of the Func-factory
[CreateFunction(typeof(Func<int, Parent>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var concreteClassFactory = container.Create();
        // The caller side parameter can also be used for sub-dependencies
        // In this case the Parent class doesn't require an int value, but its sub-dependency ConcreteClass does
        var parentA = concreteClassFactory(6);
        var parentB = concreteClassFactory(9);
        Console.WriteLine(parentA.GetType().Name); // Parent
        Console.WriteLine(parentB.GetType().Name); // Parent
        Console.WriteLine(parentA == parentB); // False
    }
}
Factory: Lazy<T> 🔍
Resolution of a Lazy<T>. Such a factory (via the 'Value'-property of Lazy<T>) delays the resolution of a T-instance to whenever the owning component see fit. The conceptual difference to Func is that it can only resolve a single instance.
Microsoft.Extensions.DependencyInjection does not support factories (Func<T>/Lazy<T>): https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#default-service-container-replacement
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.FactoriesLazy)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Factories.LazyOfT;

internal class ConcreteClass
{
}

[ImplementationAggregation(typeof(ConcreteClass))]
// Instead of returning the implementation type directly, return a Lazy<ConcreteClass>
// which is kind of a factory considering that the Value property creates an instance of the implementation type
[CreateFunction(typeof(Lazy<ConcreteClass>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var concreteClassFactory = container.Create();
        // Lazy objects defer the time of creation of the instance to when the Value property is called
        // Contrary to Func-factories, Lazy-factories only create one instance and can't take parameters
        var concreteClass = concreteClassFactory.Value;
        Console.WriteLine(concreteClass.GetType().Name); // ConcreteClass
    }
}
Factory: ThreadLocal<T> 🔍
Resolution of a ThreadLocal<T>. Such a factory (via the 'Value'-property of ThreadLocal<T>) delays the resolution of a T-instance to whenever the owning component see fit. It also creates a separate instance per thread.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.FactoriesThreadLocal)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Factories.ThreadLocalOfT;

internal class ConcreteClass
{
}

[ImplementationAggregation(typeof(ConcreteClass))]
// Instead of returning the implementation type directly, return a ThreadLocal<ConcreteClass>
// which is kind of a factory considering that the Value property creates an instance of the implementation type
[CreateFunction(typeof(ThreadLocal<ConcreteClass>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var concreteClassFactory = container.Create();
            
        // Action that prints out ThreadName for the current thread
        var action = () =>
        {
            // If ThreadName.IsValueCreated is true, it means that we are not the first action to run on this thread.
            if (concreteClassFactory.IsValueCreated)
            {
                Console.WriteLine("Skipped");
            }
            else
            {
                // ThreadLocal objects defer the time of creation of the instance to when the Value property is called
                // Contrary to Func-factories, ThreadLocal-factories only create one instance per thread
                var value = concreteClassFactory.Value;
                Console.WriteLine("ThreadName = {0} {1}", Environment.CurrentManagedThreadId, value?.GetType().Name);
            }
        };

        // Launch eight times in parallel
        Parallel.Invoke(action, action, action, action, action, action, action, action);
    }
}

Tuples

Group of features concerned with tuple types.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Tuple: Tuple<T> 🔍
Resolution of Tuple. The tuple items should be resolved if they would be a resolution on their own.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.TuplesTuple)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Tuples.Tuple;

internal class ConcreteClass {}

internal interface IInterface {}

internal class Implementation : IInterface {}

[ImplementationAggregation(typeof(ConcreteClass), typeof(Implementation))]
// Return a Tuple<ConcreteClass, IInterface, int> without explicitly registering it
[CreateFunction(typeof(Tuple<ConcreteClass, IInterface, int>), "Create")]
internal partial class Container
{
    private Container() {}
    
    private int DIE_Factory => 42;
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var (concreteClass, implementation, number) = container.Create();
        Console.WriteLine(concreteClass.GetType().Name); // ConcreteClass
        Console.WriteLine(implementation.GetType().Name); // Implementation
        Console.WriteLine(number.GetType().Name); // Int32
        Console.WriteLine(number); // 42
    }
}
Tuple: ValueTuple<T> (Syntax) 🔍
Resolution of the Syntax ValueTuple<T>. The tuple items should be resolved if they would be a resolution on their own.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.TuplesValueTupleSyntax)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Tuples.ValueTupleSyntax;

internal class ConcreteClass {}

internal interface IInterface {}

internal class Implementation : IInterface {}

[ImplementationAggregation(typeof(ConcreteClass), typeof(Implementation))]
// Return a syntax version of ValueTuple<ConcreteClass, IInterface, int> without explicitly registering it
[CreateFunction(typeof((ConcreteClass, IInterface, int)), "Create")]
internal partial class Container
{
    private Container() {}
    
    private int DIE_Factory => 42;
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var (concreteClass, implementation, number) = container.Create();
        Console.WriteLine(concreteClass.GetType().Name); // ConcreteClass
        Console.WriteLine(implementation.GetType().Name); // Implementation
        Console.WriteLine(number.GetType().Name); // Int32
        Console.WriteLine(number); // 42
    }
}
Tuple: ValueTuple<T> 🔍
Resolution of ValueTuple<T>. The tuple items should be resolved if they would be a resolution on their own.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.TuplesValueTuple)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Tuples.ValueTuple;

internal class ConcreteClass {}

internal interface IInterface {}

internal class Implementation : IInterface {}

[ImplementationAggregation(typeof(ConcreteClass), typeof(Implementation))]
// Return a ValueTuple<ConcreteClass, IInterface, int> without explicitly registering it
[CreateFunction(typeof(ValueTuple<ConcreteClass, IInterface, int>), "Create")]
internal partial class Container
{
    private Container() {}
    
    private int DIE_Factory => 42;
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var (concreteClass, implementation, number) = container.Create();
        Console.WriteLine(concreteClass.GetType().Name); // ConcreteClass
        Console.WriteLine(implementation.GetType().Name); // Implementation
        Console.WriteLine(number.GetType().Name); // Int32
        Console.WriteLine(number); // 42
    }
}

Type Initializers

Group of features concerned with type initializers. These are initialization methods that are called after the instance is created and before it is returned from the DI container. They are optionally declarable once per type.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Sync Type Initializer 🔍
There is an option to declare a synchronous (void) method as type initializer per type.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.TypeInitializersSync)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.TypeInitializers.Sync;

internal class ConcreteClass
{
    internal bool Initialized { get; private set; }
    // This method should be called after the instance is created and before it is further injected
    internal void Initialize() => Initialized = true;
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Use the factory parameter in order to call the Initialize-method after the instance is created
        builder.Services.AddTransient<ConcreteClass>(sp =>
        {
            // Don't use sp.GetService<ConcreteClass>() or sp.GetRequiredService<ConcreteClass>() here because that would create an infinite loop
            var concreteClass = ActivatorUtilities.CreateInstance<ConcreteClass>(sp);
            concreteClass.Initialize();
            return concreteClass;
        });
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var concreteClass = host.Services.GetRequiredService<ConcreteClass>();
        Console.WriteLine($"Initialized: {concreteClass.Initialized}"); // Initialized: True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.TypeInitializersSync)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.TypeInitializers.Sync;

internal class ConcreteClassA
{
    internal bool Initialized { get; private set; }
    // This method is called by the container after the instance is created and before it is further injected
    internal void Initialize() => Initialized = true;
}

// You can also use an interface as a marker for type initialization
internal interface ITypeInitializer
{
    void Initialize();
}

internal class ConcreteClassB : ITypeInitializer
{
    internal bool Initialized { get; private set; }
    // With a marker interface you have the option to implement the interface explicitly
    // That way the method isn't directly visible on references of the implementation type without casting to the interface
    void ITypeInitializer.Initialize() => Initialized = true;
}

[ImplementationAggregation(typeof(ConcreteClassA), typeof(ConcreteClassB))]
// Register the type initializer methods
[Initializer(typeof(ConcreteClassA), nameof(ConcreteClassA.Initialize))]
[Initializer(typeof(ITypeInitializer), nameof(ITypeInitializer.Initialize))]

[CreateFunction(typeof(ConcreteClassA), "CreateA")]
[CreateFunction(typeof(ConcreteClassB), "CreateB")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var concreteClassA = container.CreateA();
        Console.WriteLine($"Initialized: {concreteClassA.Initialized}"); // Initialized: True
        var concreteClassB = container.CreateB();
        Console.WriteLine($"Initialized: {concreteClassB.Initialized}"); // Initialized: True
    }
}
Async Type Initializer (Task) 🔍
There is an option to declare a asynchronous (Task) method as type initializer per type.
One could implement it in analogous fashion as the Sync-sample. However because there is no support for async factories (which return either ValueTask<T>/Task<T>), the async initialization is unsafe, as it either forces a blocking call or a fire/forget-handling (which may lead to race conditions).
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.TypeInitializersAsyncTask)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.TypeInitializers.AsyncTask;

internal class ConcreteClassA
{
    internal bool Initialized { get; private set; }
    // This method is called by the container after the instance is created and before it is further synchronously injected (injections which aren't wrapped in ValueTask<T>/Task<T>).
    // With asynchronous injections (injection wrapped into either ValueTask<T> or Task<T>), it may be injected eagerly, then the consuming component may decide to await, block or fire/forget.
    // The container is guaranteed to have no blocking calls.
    internal async Task Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

// You can also use an interface as a marker for type initialization
internal interface IAsyncTypeInitializer
{
    Task Initialize();
}

internal class ConcreteClassB : IAsyncTypeInitializer
{
    internal bool Initialized { get; private set; }
    // With a marker interface you have the option to implement the interface explicitly
    // That way the method isn't directly visible on references of the implementation type without casting to the interface
    async Task IAsyncTypeInitializer.Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

internal class Parent
{
    internal Parent(ValueTask<ConcreteClassB> concreteClassB) => ConcreteClassB = concreteClassB;
    internal ValueTask<ConcreteClassB> ConcreteClassB { get; }
}

[ImplementationAggregation(typeof(ConcreteClassA), typeof(ConcreteClassB), typeof(Parent))]
// Register the type initializer methods
[Initializer(typeof(ConcreteClassA), nameof(ConcreteClassA.Initialize))]
[Initializer(typeof(IAsyncTypeInitializer), nameof(IAsyncTypeInitializer.Initialize))]

[CreateFunction(typeof(ConcreteClassA), "CreateA")]
[CreateFunction(typeof(Parent), "CreateB")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async Task Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // Notice that the Create-function is now async and returns a ValueTask<ConcreteClassA> instead of ConcreteClassA.
        // In order to fulfill the aforementioned guarantees, the container has to await the type initializer before returning the instance.
        // An Create-function with "async void" modifiers would force fire-and-forget semantics, therefore the container wraps the returned type into a ValueTask<T> automatically.
        // It upon the user to decide whether to await the returned ValueTask<T> or not.
        var concreteClassA = await container.CreateA();
        Console.WriteLine($"Initialized: {concreteClassA.Initialized}"); // Initialized: True
        // Notice that the Create-function for the Parent type is still synchronous.
        // That is because its ConcreteClassB-injection is wrapped in a ValueTask<T> and therefore the container doesn't need to generate any awaits.
        var parent = container.CreateB();
        Console.WriteLine($"Initialized: {(await parent.ConcreteClassB).Initialized}"); // Initialized: True
    }
}
Async Type Initializer (ValueTask) 🔍
There is an option to declare a asynchronous (ValueTask) method as type initializer per type.
One could implement it in analogous fashion as the Sync-sample. However because there is no support for async factories (which return either ValueTask<T>/Task<T>), the async initialization is unsafe, as it either forces a blocking call or a fire/forget-handling (which may lead to race conditions).
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.TypeInitializersAsyncValueTask)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.TypeInitializers.AsyncValueTask;

internal class ConcreteClassA
{
    internal bool Initialized { get; private set; }
    // This method is called by the container after the instance is created and before it is further synchronously injected (injections which aren't wrapped in ValueTask<T>/Task<T>).
    // With asynchronous injections (injection wrapped into either ValueTask<T> or Task<T>), it may be injected eagerly, then the consuming component may decide to await, block or fire/forget.
    // The container is guaranteed to have no blocking calls.
    internal async ValueTask Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

// You can also use an interface as a marker for type initialization
internal interface IAsyncValueTypeInitializer
{
    ValueTask Initialize();
}

internal class ConcreteClassB : IAsyncValueTypeInitializer
{
    internal bool Initialized { get; private set; }
    // With a marker interface you have the option to implement the interface explicitly
    // That way the method isn't directly visible on references of the implementation type without casting to the interface
    async ValueTask IAsyncValueTypeInitializer.Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

internal class Parent
{
    internal Parent(Task<ConcreteClassB> concreteClassB) => ConcreteClassB = concreteClassB;
    internal Task<ConcreteClassB> ConcreteClassB { get; }
}

[ImplementationAggregation(typeof(ConcreteClassA), typeof(ConcreteClassB), typeof(Parent))]
// Register the type initializer methods
[Initializer(typeof(ConcreteClassA), nameof(ConcreteClassA.Initialize))]
[Initializer(typeof(IAsyncValueTypeInitializer), nameof(IAsyncValueTypeInitializer.Initialize))]

[CreateFunction(typeof(ConcreteClassA), "CreateA")]
[CreateFunction(typeof(Parent), "CreateB")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async Task Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // Notice that the Create-function is now async and returns a ValueTask<ConcreteClassA> instead of ConcreteClassA.
        // In order to fulfill the aforementioned guarantees, the container has to await the type initializer before returning the instance.
        // An Create-function with "async void" modifiers would force fire-and-forget semantics, therefore the container wraps the returned type into a ValueTask<T> automatically.
        // It upon the user to decide whether to await the returned ValueTask<T> or not.
        var concreteClassA = await container.CreateA();
        Console.WriteLine($"Initialized: {concreteClassA.Initialized}"); // Initialized: True
        // Notice that the Create-function for the Parent type is still synchronous.
        // That is because its ConcreteClassB-injection is wrapped in a Task<T> and therefore the container doesn't need to generate any awaits.
        var parent = container.CreateB();
        Console.WriteLine($"Initialized: {(await parent.ConcreteClassB).Initialized}"); // Initialized: True
    }
}

Injections

Group of features concerned with injections.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Constructor Parameter Injection 🔍
Injection of a dependency into a constructor parameter.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.InjectionsConstructorParameter)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Injections.ConstructorParameter;

// Simple class that we want to inject into another class
internal class ConcreteClass
{
}

internal class Parent
{
    // Inject it here as a constructor parameter
    internal Parent(ConcreteClass child) => Dependency = child;
    internal ConcreteClass Dependency { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<ConcreteClass>();
        builder.Services.AddTransient<Parent>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine(parent.Dependency.GetType().Name); // ConcreteClass
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.InjectionsConstructorParameter)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Injections.ConstructorParameter;

// Simple class that we want to inject into another class
internal class ConcreteClass
{
}

internal class Parent
{
    // Inject it here as a constructor parameter
    internal Parent(ConcreteClass child) => Dependency = child;
    internal ConcreteClass Dependency { get; }
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClass))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine(parent.Dependency.GetType().Name); // ConcreteClass
    }
}
Init Properties Implicit Injection 🔍
With this feature properties which have an init-accessor will be selected for property injection implicitly per default. Init-only properties can only be set during instantiation, so if the container resolves this type, then it makes sense that it automatically injects these properties.
Microsoft.Extensions.DependencyInjection does not support property injection: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#default-service-container-replacement
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.InjectionsInitPropertyImplicit)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Injections.InitPropertyImplicit;

// Simple classes that we want to inject into another class
internal class ConcreteClassA { }
internal class ConcreteClassB { }

internal class Parent
{
    // Non-required properties of reference types need to be nullable (C# requirement for nullability feature)
    internal ConcreteClassA? Dependency { get; init; }
    // Required properties don't have to be nullable
    internal required ConcreteClassB DependencyRequired { get; init; }
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClassA), typeof(ConcreteClassB))]
// No need for explicit registration of properties (like in the "ExplicitPropertyChoice"-sample) to be injected, because they are all init-properties.
// The rationale for this convention is: if the container is instantiating the Parent-object, then the user has no chance to set the init-properties themselves.
// That means it would be strange not have the container inject them.
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine(parent.Dependency!.GetType().Name); // ConcreteClassA
        Console.WriteLine(parent.DependencyRequired.GetType().Name); // ConcreteClassB
    }
}
Explicit Properties Choice 🔍
A set of properties (with either init- or set-accessor) is configurable for property injection.
Microsoft.Extensions.DependencyInjection does not support property injection: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#default-service-container-replacement
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.InjectionsExplicitPropertyChoice)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Injections.ExplicitPropertyChoice;

// Simple classes that we want to inject into another class
internal class ConcreteClassA {}
internal class ConcreteClassB {}
internal class ConcreteClassC {}
internal class ConcreteClassD {}

internal class Parent
{
    // Possibly inject ConcreteClassA here as an nullable init-property (but we'll later choose not to inject it).
    // The benefit of an init-property is that it can only be set by the container.
    internal ConcreteClassA? Dependency { get; init; }
    // Possibly inject ConcreteClassB here as an nullable set-property
    internal ConcreteClassB? DependencySet { get; set; }
    // Inject ConcreteClassC here as a required set-property
    internal required ConcreteClassC DependencyRequiredSet { get; set; }
    // Inject ConcreteClassA here as a required nullable init-property.
    // The benefit of an init-property is that it can only be set by the container.
    internal required ConcreteClassD DependencyRequiredInit { get; init; }
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClassA), typeof(ConcreteClassB), typeof(ConcreteClassC), typeof(ConcreteClassD))]
// Explicitly choose which properties should be injected.
// The "PropertyChoice" configuration is only required if there are set-properties to be injected or if the set of injected properties should be limited.
// In the default case, all init-properties are injected automatically (see "InitPropertyImplicit"-sample).
[PropertyChoice(typeof(Parent), nameof(Parent.DependencySet), nameof(Parent.DependencyRequiredSet), nameof(Parent.DependencyRequiredInit))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Parent.Dependency is null: {parent.Dependency is null}"); // Parent.Dependency is null: True
        Console.WriteLine($"Parent.DependencySet is null: {parent.DependencySet is null}"); // Parent.DependencySet is null: False
        Console.WriteLine(parent.DependencySet!.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyRequiredSet.GetType().Name); // ConcreteClassC
        Console.WriteLine(parent.DependencyRequiredInit.GetType().Name); // ConcreteClassD
    }
}
Type Initializer Parameter Injection 🔍
Injection of a dependency into a type initializer parameter.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.InjectionsTypeInitializerParameter)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Injections.TypeInitializerParameter;

// Simple class that we want to inject into another class
internal class ConcreteClass {}

internal class Parent
{
    internal ConcreteClass? Dependency { get; private set; }
    internal void Initialize(ConcreteClass dependency) => Dependency = dependency;
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<ConcreteClass>();
        builder.Services.AddTransient<Parent>(sp =>
        {
            // Don't use sp.GetService<Parent>() or sp.GetRequiredService<Parent>() here because that would create an infinite loop
            var parent = ActivatorUtilities.CreateInstance<Parent>(sp);
            var concreteClass = sp.GetRequiredService<ConcreteClass>();
            parent.Initialize(concreteClass);
            return parent;
        });
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine(parent.Dependency!.GetType().Name); // ConcreteClass
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.InjectionsTypeInitializerParameter)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Injections.TypeInitializerParameter;

// Simple class that we want to inject into another class
internal class ConcreteClass {}

internal class Parent
{
    internal ConcreteClass? Dependency { get; private set; }
    // The type initializer method can have dependencies which are resolved by the container as well
    internal void Initialize(ConcreteClass dependency) => Dependency = dependency;
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClass))]
[Initializer(typeof(Parent), nameof(Parent.Initialize))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Is null: {parent.Dependency is null}"); // Is null: False
        Console.WriteLine(parent.Dependency!.GetType().Name); // ConcreteClass
    }
}

Keyed Injections

Group of features concerned with keyed injections.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
One Key: Single Item ([Key] T) 🔍
Injections of a single items for a single key.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.KeyedInjectionsOneKeySingleItem)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.KeyedInjections.OneKeySingleItem;

// Simple enum that will be used as a key for the injections
internal enum Key
{
    A,
    B
}

// Simple interface that will work as an abstraction for the implementations
internal interface IInterface {}

// Implementation that'll get the key A assigned during registration
internal class ConcreteClassA : IInterface {}

// Implementation that'll get the key B assigned during registration
internal class ConcreteClassB : IInterface {}

internal class Parent
{
    internal IInterface Dependency { get; }
    
    // Inject a single implementation for the key A
    public Parent([FromKeyedServices(Key.A)] IInterface dependency) => Dependency = dependency;
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<Parent>();
        
        // Register the implementations with the appropriate keys
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(Key.A);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(Key.B);
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var instance = host.Services.GetRequiredService<Parent>();
        Console.WriteLine(instance.Dependency.GetType().Name); // ConcreteClassA
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.KeyedInjectionsOneKeySingleItem)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.KeyedInjections.OneKeySingleItem;

// Simple enum that will be used as a key for the injections
internal enum Key
{
    A,
    B,
    C,
    D
}

// Simple attribute that will be used to configure a key directly on an implementation type
// or assign a key to a parameter or property.
// The attribute type requires to have a constructor that has its first parameter of type object.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Parameter | AttributeTargets.Property)]
internal class KeyAssignmentAttribute : Attribute
{
    internal KeyAssignmentAttribute(object value) {}
}

// Simple interface that will work as an abstraction for the implementations
internal interface IInterface {}

// ConcreteClassA gets the key A assigned via the attribute
[KeyAssignment(Key.A)]
internal class ConcreteClassA : IInterface {}

// ConcreteClassB will get the key assigned via the container's configuration (see below)
internal class ConcreteClassB : IInterface {}

// ConcreteClassC gets the key C assigned via the attribute
[KeyAssignment(Key.C)]
internal class ConcreteClassC : IInterface {}

// The next two implementations will get the key D assigned via the attribute
[KeyAssignment(Key.D)]
internal class ConcreteClassD0 : IInterface {}
[KeyAssignment(Key.D)]
internal class ConcreteClassD1 : IInterface {}

// This class gets multiple instances of IInterface injected distinguished by keys
internal class Parent
{
    internal IInterface DependencyA { get; }
    // This property will be injected directly by property injection. Hence, it gets the key B assigned
    [KeyAssignment(Key.B)]
    internal required IInterface DependencyB { get; init; }
    internal IInterface? DependencyC { get; private set; }
    // Because the key D is assigned to multiple implementations and because the property type is nullable,
    // the property will get injected with null instead of one of the implementations.
    // If the property type was not nullable, a compile error would occur.
    [KeyAssignment(Key.D)]
    internal required IInterface? DependencyD { get; init; }
    
    // To make a constructor parameter a keyed injection just assign the key via the attribute
    internal Parent([KeyAssignment(Key.A)] IInterface dependencyA) => DependencyA = dependencyA;

    // In the same manner, parameter of initializer method can be keyed as well
    internal void Initialize([KeyAssignment(Key.C)] IInterface dependencyC) => DependencyC = dependencyC;
}

// Registering all the implementations
[ImplementationAggregation(
    typeof(Parent),
    typeof(ConcreteClassA), 
    typeof(ConcreteClassB), 
    typeof(ConcreteClassC), 
    typeof(ConcreteClassD0), 
    typeof(ConcreteClassD1))]
[Initializer(typeof(Parent), nameof(Parent.Initialize))]

// Configure a mapping to the custom key assigning attribute
[InjectionKeyMapping(typeof(KeyAssignmentAttribute))]
// Register the key B to the implementation ConcreteClassB explicitly
// The injection key choice is convenient for configuring keys types from external libraries,
// where you can't append the custom key assigning attribute to the implementation type.
[InjectionKeyChoice(Key.B, typeof(ConcreteClassB))]

[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine(parent.DependencyA.GetType().Name); // ConcreteClassA
        Console.WriteLine(parent.DependencyB.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyC!.GetType().Name); // ConcreteClassC
        Console.WriteLine(parent.DependencyD is null); // True
    }
}
One Key: Multiple Items ([Key] IReadOnlyList<T>) 🔍
Injections of a multiple items for a single key.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.KeyedInjectionsOneKeyMultipleItems)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.KeyedInjections.OneKeyMultipleItems;

// Simple enum that will be used as a key for the injections
internal enum Key
{
    A,
    B
}

// Simple interface that will work as an abstraction for the implementations
internal interface IInterface {}

// Implementations that'll get the key A assigned during registration
internal class ConcreteClassA0 : IInterface {}
internal class ConcreteClassA1 : IInterface {}

// Implementations that'll get the key B assigned during registration
internal class ConcreteClassB0 : IInterface {}
internal class ConcreteClassB1 : IInterface {}

internal class Parent
{
    internal IInterface[] Dependencies { get; }
    
    // Inject a multiple implementations for the key A
    public Parent([FromKeyedServices(Key.A)] IEnumerable<IInterface> dependencies) => 
        Dependencies = dependencies.ToArray();
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<Parent>();
        
        // Register the implementations with the appropriate keys
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA0>(Key.A);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA1>(Key.A);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB0>(Key.B);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB1>(Key.B);
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var instance = host.Services.GetRequiredService<Parent>();
        
        Console.WriteLine(instance.Dependencies.Length); // 2
        foreach (var dependency in instance.Dependencies)
            Console.WriteLine(dependency is ConcreteClassA0 or ConcreteClassA1); // True
    }
}
using System.Collections.Immutable;
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.KeyedInjectionsOneKeyMultipleItems)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.KeyedInjections.OneKeyMultipleItems;

// Simple enum that will be used as a key for the injections
internal enum Key
{
    A,
    B,
    C,
    D
}

// Simple attribute that will be used to configure a key directly on an implementation type
// or assign a key to a parameter or property.
// The attribute type requires to have a constructor that has its first parameter of type object.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Parameter | AttributeTargets.Property)]
internal class KeyAssignmentAttribute : Attribute
{
    internal KeyAssignmentAttribute(object value) {}
}

// Simple interface that will work as an abstraction for the implementations
internal interface IInterface {}

// ConcreteClassA0 & ConcreteClassA1 get the key A assigned via the attribute
[KeyAssignment(Key.A)]
internal class ConcreteClassA0 : IInterface {}
[KeyAssignment(Key.A)]
internal class ConcreteClassA1 : IInterface {}

// ConcreteClassB0 and ConcreteClassB1 will get the key assigned via the container's configuration (see below)
internal class ConcreteClassB0 : IInterface {}
internal class ConcreteClassB1 : IInterface {}

// ConcreteClassC0 & ConcreteClassC1 gets the key C assigned via the attribute
[KeyAssignment(Key.C)]
internal class ConcreteClassC0 : IInterface {}
[KeyAssignment(Key.C)]
internal class ConcreteClassC1 : IInterface {}

// This class gets multiple instances of IInterface injected distinguished by keys
internal class Parent
{
    internal IEnumerable<IInterface> DependencyA { get; }
    // This property will be injected directly by property injection. Hence, it gets the key B assigned
    [KeyAssignment(Key.B)]
    internal required IReadOnlyList<IInterface> DependencyB { get; init; }
    internal List<IInterface>? DependencyC { get; private set; }
    [KeyAssignment(Key.D)]
    internal required ImmutableArray<IInterface> DependencyD { get; init; }
    
    // To make a constructor parameter a keyed injection just assign the key via the attribute
    internal Parent([KeyAssignment(Key.A)] IEnumerable<IInterface> dependencyA) => DependencyA = dependencyA;

    // In the same manner, parameter of initializer method can be keyed as well
    internal void Initialize([KeyAssignment(Key.C)] List<IInterface> dependencyC) => DependencyC = dependencyC;
}

// Registering all the implementations
[ImplementationAggregation(
    typeof(Parent),
    typeof(ConcreteClassA0), 
    typeof(ConcreteClassA1), 
    typeof(ConcreteClassB0),  
    typeof(ConcreteClassB1),
    typeof(ConcreteClassC0),
    typeof(ConcreteClassC1))]
[Initializer(typeof(Parent), nameof(Parent.Initialize))]

// Configure a mapping to the custom key assigning attribute
[InjectionKeyMapping(typeof(KeyAssignmentAttribute))]
// Register the key B to the implementations ConcreteClassB0 & ConcreteClassB1 explicitly
// The injection key choice is convenient for configuring keys types from external libraries,
// where you can't append the custom key assigning attribute to the implementation type.
[InjectionKeyChoice(Key.B, typeof(ConcreteClassB0))]
[InjectionKeyChoice(Key.B, typeof(ConcreteClassB1))]

[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine(parent.DependencyA.Count()); // 2
        foreach (var a in parent.DependencyA)
            Console.WriteLine(a is ConcreteClassA0 or ConcreteClassA1); // True
        Console.WriteLine(parent.DependencyB.Count); // 2
        foreach (var b in parent.DependencyB)
            Console.WriteLine(b is ConcreteClassB0 or ConcreteClassB1); // True
        Console.WriteLine(parent.DependencyC!.Count); // 2
        foreach (var c in parent.DependencyC!)
            Console.WriteLine(c is ConcreteClassC0 or ConcreteClassC1); // True
        Console.WriteLine(parent.DependencyD.Length); // 0
    }
}
All Keys: Single Item (IReadOnlyDictionary<Key, T>) 🔍
Injections of a single items for all keys.
using System.Collections.Immutable;
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.KeyedInjectionsAllKeysSingleItem)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.KeyedInjections.AllKeysSingleItem;

// Simple enum that will be used as a key for the injections
internal enum Key
{
    A,
    B,
    C,
    D
}

// Simple attribute that will be used to configure a key directly on an implementation type
// or assign a key to a parameter or property.
// The attribute type requires to have a constructor that has its first parameter of type object.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Parameter | AttributeTargets.Property)]
internal class KeyAssignmentAttribute : Attribute
{
    internal KeyAssignmentAttribute(object value) {}
}

// Simple interface that will work as an abstraction for the implementations
internal interface IInterface {}

// ConcreteClassA gets the key A assigned via the attribute
[KeyAssignment(Key.A)]
internal class ConcreteClassA : IInterface {}

// ConcreteClassB will get the key assigned via the container's configuration (see below)
internal class ConcreteClassB : IInterface {}

// ConcreteClassC gets the key C assigned via the attribute
[KeyAssignment(Key.C)]
internal class ConcreteClassC : IInterface {}

// The next two implementations will get the key D assigned via the attribute
[KeyAssignment(Key.D)]
internal class ConcreteClassD0 : IInterface {}
[KeyAssignment(Key.D)]
internal class ConcreteClassD1 : IInterface {}

// This class will get a map from the key type to the interface type injected
internal class Parent
{
    // Not necessarily a map, but you can inject to an enumerable of key-value-pairs as well
    internal IEnumerable<KeyValuePair<Key, IInterface>> Dependency0 { get; }
    internal required IReadOnlyDictionary<Key, IInterface> Dependency1 { get; init; }
    internal ImmutableSortedDictionary<Key, IInterface>? Dependency2 { get; private set; }
    
    internal Parent(IEnumerable<KeyValuePair<Key, IInterface>> dependency0) => Dependency0 = dependency0;

    internal void Initialize(ImmutableSortedDictionary<Key, IInterface> dependency2) => Dependency2 = dependency2;
}

// Registering all the implementations
[ImplementationAggregation(
    typeof(Parent),
    typeof(ConcreteClassA), 
    typeof(ConcreteClassB), 
    typeof(ConcreteClassC), 
    typeof(ConcreteClassD0), 
    typeof(ConcreteClassD1))]
[Initializer(typeof(Parent), nameof(Parent.Initialize))]

// Configure a mapping to the custom key assigning attribute
[InjectionKeyMapping(typeof(KeyAssignmentAttribute))]
// Register the key B to the implementation ConcreteClassB explicitly
// The injection key choice is convenient for configuring keys types from external libraries,
// where you can't append the custom key assigning attribute to the implementation type.
[InjectionKeyChoice(Key.B, typeof(ConcreteClassB))]

[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Check(parent.Dependency0);
        Check(parent.Dependency1);
        Check(parent.Dependency2!);
        return;

        void Check(IEnumerable<KeyValuePair<Key, IInterface>> map)
        {
            var containsD = false;
            Console.WriteLine(map.Count()); // 3
            foreach (var (key, value) in map)
            {
                Console.WriteLine(key);
                Console.WriteLine(value.GetType().Name);
                if (key == Key.D) containsD = true;
            }
            // Because the key D is assigned to multiple implementations and a single value type is expected,
            // there won't be an entry for key D in the map.
            Console.WriteLine(containsD); // False
        }
    }
}
All Keys: Multiple Items (IReadOnlyDictionary<Key, IReadOnlyList<T>>) 🔍
Injections of a multiple items for all keys.
using System.Collections.Immutable;
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.KeyedInjectionsAllKeysMultipleItems)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.KeyedInjections.AllKeysMultipleItems;

// Simple enum that will be used as a key for the injections
internal enum Key
{
    A,
    B,
    C,
    D
}

// Simple attribute that will be used to configure a key directly on an implementation type
// or assign a key to a parameter or property.
// The attribute type requires to have a constructor that has its first parameter of type object.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Parameter | AttributeTargets.Property)]
internal class KeyAssignmentAttribute : Attribute
{
    internal KeyAssignmentAttribute(object value) {}
}

// Simple interface that will work as an abstraction for the implementations
internal interface IInterface {}

// ConcreteClassA0 & ConcreteClassA1 get the key A assigned via the attribute
[KeyAssignment(Key.A)]
internal class ConcreteClassA0 : IInterface {}
[KeyAssignment(Key.A)]
internal class ConcreteClassA1 : IInterface {}

// ConcreteClassB0 and ConcreteClassB1 will get the key assigned via the container's configuration (see below)
internal class ConcreteClassB0 : IInterface {}
internal class ConcreteClassB1 : IInterface {}

// ConcreteClassC0 & ConcreteClassC1 gets the key C assigned via the attribute
[KeyAssignment(Key.C)]
internal class ConcreteClassC0 : IInterface {}
[KeyAssignment(Key.C)]
internal class ConcreteClassC1 : IInterface {}

// This class will get a map from the key type to the interface type injected
internal class Parent
{
    // Not necessarily a map, but you can inject to an enumerable of an iterable of key-value-pairs as well
    internal IEnumerable<KeyValuePair<Key, IEnumerable<IInterface>>> Dependency0 { get; }
    internal required IReadOnlyDictionary<Key, IReadOnlyList<IInterface>> Dependency1 { get; init; }
    internal ImmutableDictionary<Key, ImmutableArray<IInterface>>? Dependency2 { get; private set; }
    
    internal Parent(IEnumerable<KeyValuePair<Key, IEnumerable<IInterface>>> dependency0) => Dependency0 = dependency0;

    internal void Initialize(ImmutableDictionary<Key, ImmutableArray<IInterface>> dependency2) => Dependency2 = dependency2;
}

// Registering all the implementations
[ImplementationAggregation(
    typeof(Parent),
    typeof(ConcreteClassA0),
    typeof(ConcreteClassA1), 
    typeof(ConcreteClassB0), 
    typeof(ConcreteClassB1), 
    typeof(ConcreteClassC0), 
    typeof(ConcreteClassC1))]
[Initializer(typeof(Parent), nameof(Parent.Initialize))]

// Configure a mapping to the custom key assigning attribute
[InjectionKeyMapping(typeof(KeyAssignmentAttribute))]
// Register the key B to the implementations ConcreteClassB0 & ConcreteClassB1 explicitly
// The injection key choice is convenient for configuring keys types from external libraries,
// where you can't append the custom key assigning attribute to the implementation type.
[InjectionKeyChoice(Key.B, typeof(ConcreteClassB0))]
[InjectionKeyChoice(Key.B, typeof(ConcreteClassB1))]

[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        {
            var map = parent.Dependency0;
            var containsD = false;
            Console.WriteLine(map.Count()); // 3
            foreach (var (key, value) in map)
            {
                Console.WriteLine(key);
                foreach (var item in value)
                    Console.WriteLine(item.GetType().Name);
                if (key == Key.D) containsD = true;
            }
            // Because the key D is assigned to multiple implementations and a single value type is expected,
            // there won't be an entry for key D in the map.
            Console.WriteLine(containsD); // False
        }
        {
            var map = parent.Dependency1;
            Console.WriteLine(map.Count); // 3
            Check(Key.A);
            Check(Key.B);
            Check(Key.C);
            Check(Key.D);
            
            void Check(Key key)
            {
                Console.WriteLine(key);
                if (map.TryGetValue(key, out var list))
                    foreach (var item in list)
                        Console.WriteLine(item.GetType().Name);
                else Console.WriteLine($"No value for key {key}");
            }
        }
        {
            var map = parent.Dependency2!;
            Console.WriteLine(map.Count); // 3
            Check(Key.A);
            Check(Key.B);
            Check(Key.C);
            Check(Key.D);
            
            void Check(Key key)
            {
                Console.WriteLine(key);
                if (map.TryGetValue(key, out var list))
                    foreach (var item in list)
                        Console.WriteLine(item.GetType().Name);
                else Console.WriteLine($"No value for key {key}");
            }
        }
    }
}
Supported Key Types 🔍
Showcase of supported key types.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.KeyedInjectionsSupportedKeyTypes)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.KeyedInjections.SupportedKeyTypes;

// Simple enum that will be used as a key for the injections
internal enum Key
{
    A,
    B,
    C
}

// Simple interface that will work as an abstraction for the implementations
internal interface IInterface { }

internal class ConcreteClassA : IInterface
{
    internal const byte Byte = 0;
    internal const sbyte SByte = 1;
    internal const short Short = 2;
    internal const ushort UShort = 3;
    internal const int Int = 4;
    internal const uint UInt = 5;
    internal const long Long = 6;
    internal const ulong ULong = 7;
    internal const char Char = (char) 8;
    internal const float Float = 9.0f;
    internal const double Double = 10.0;
    internal const string String = "11";
    internal const bool Bool = true;
}

internal class ConcreteClassB : IInterface
{
    internal const byte Byte = 12;
    internal const sbyte SByte = 13;
    internal const short Short = 14;
    internal const ushort UShort = 15;
    internal const int Int = 16;
    internal const uint UInt = 17;
    internal const long Long = 18;
    internal const ulong ULong = 19;
    internal const char Char = (char) 20;
    internal const float Float = 21.0f;
    internal const double Double = 22.0;
    internal const string String = "23";
    internal const bool Bool = false;
}

internal class ConcreteClassC : IInterface
{
    internal const byte Byte = 24;
    internal const sbyte SByte = 25;
    internal const short Short = 26;
    internal const ushort UShort = 27;
    internal const int Int = 28;
    internal const uint UInt = 29;
    internal const long Long = 30;
    internal const ulong ULong = 31;
    internal const char Char = (char) 32;
    internal const float Float = 33.0f;
    internal const double Double = 34.0;
    internal const string String = "35";
    internal const bool Bool = true;
}

internal class Parent
{
    public Parent(
        [FromKeyedServices(Key.B)] IInterface dependencyEnum, 
        [FromKeyedServices(ConcreteClassB.Byte)] IInterface dependencyByte, 
        [FromKeyedServices(ConcreteClassB.SByte)] IInterface dependencySByte, 
        [FromKeyedServices(ConcreteClassB.Short)] IInterface dependencyShort,
        [FromKeyedServices(ConcreteClassB.UShort)] IInterface dependencyUShort, 
        [FromKeyedServices(ConcreteClassB.Int)] IInterface dependencyInt,
        [FromKeyedServices(ConcreteClassB.UInt)] IInterface dependencyUInt, 
        [FromKeyedServices(ConcreteClassB.Long)] IInterface dependencyLong, 
        [FromKeyedServices(ConcreteClassB.ULong)] IInterface dependencyULong,
        [FromKeyedServices(ConcreteClassB.Char)] IInterface dependencyChar,
        [FromKeyedServices(ConcreteClassB.Float)] IInterface dependencyFloat, 
        [FromKeyedServices(ConcreteClassB.Double)] IInterface dependencyDouble,
        [FromKeyedServices(ConcreteClassB.String)] IInterface dependencyString,
        [FromKeyedServices(ConcreteClassB.Bool)] IInterface dependencyBool,
        [FromKeyedServices(typeof(ConcreteClassB))] IInterface dependencyType)
    {
        DependencyEnum = dependencyEnum;
        DependencyByte = dependencyByte;
        DependencySByte = dependencySByte;
        DependencyShort = dependencyShort;
        DependencyUShort = dependencyUShort;
        DependencyInt = dependencyInt;
        DependencyUInt = dependencyUInt;
        DependencyLong = dependencyLong;
        DependencyULong = dependencyULong;
        DependencyChar = dependencyChar;
        DependencyFloat = dependencyFloat;
        DependencyDouble = dependencyDouble;
        DependencyString = dependencyString;
        DependencyBool = dependencyBool;
        DependencyType = dependencyType;
    }
    
    public required IInterface DependencyEnum { get; init; }
    
    public required IInterface DependencyByte { get; init; }
    
    public required IInterface DependencySByte { get; init; }
    
    public required IInterface DependencyShort { get; init; }
    
    public required IInterface DependencyUShort { get; init; }
    
    public required IInterface DependencyInt { get; init; }
    
    public required IInterface DependencyUInt { get; init; }
    
    public required IInterface DependencyLong { get; init; }
    
    public required IInterface DependencyULong { get; init; }
    
    public required IInterface DependencyChar { get; init; }
    
    public required IInterface DependencyFloat { get; init; }
    
    public required IInterface DependencyDouble { get; init; }
    
    public required IInterface DependencyString { get; init; }
    
    public required IInterface DependencyBool { get; init; }
    
    public required IInterface DependencyType { get; init; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder(object anyObjectKeyA, object anyObjectKeyB, object anyObjectKeyC)
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<Parent>();
        
        // Register the implementations with the appropriate keys
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(Key.A);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.Byte);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.SByte);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.Short);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.UShort);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.Int);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.UInt);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.Long);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.ULong);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.Char);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.Float);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.Double);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.String);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(ConcreteClassA.Bool);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(typeof(ConcreteClassA));
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(Key.B);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.Byte);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.SByte);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.Short);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.UShort);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.Int);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.UInt);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.Long);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.ULong);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.Char);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.Float);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.Double);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.String);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(ConcreteClassB.Bool);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(typeof(ConcreteClassB));
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(Key.C);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.Byte);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.SByte);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.Short);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.UShort);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.Int);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.UInt);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.Long);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.ULong);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.Char);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.Float);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.Double);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.String);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(ConcreteClassC.Bool);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(typeof(ConcreteClassC));
        
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(anyObjectKeyA);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(anyObjectKeyB);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassC>(anyObjectKeyC);
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        var anyObjectKeyA = new object();
        var anyObjectKeyB = new object();
        var anyObjectKeyC = new object();
        using var host = Builder.CreateBuilder(anyObjectKeyA, anyObjectKeyB, anyObjectKeyC).Build();

        var parent = host.Services.GetRequiredService<Parent>();
        Console.WriteLine(parent.DependencyEnum.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyByte.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencySByte.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyShort.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyUShort.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyInt.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyUInt.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyLong.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyULong.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyChar.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyFloat.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyDouble.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyString.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyBool.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyType.GetType().Name); // ConcreteClassB

        var instanceByAnyObject = host.Services.GetRequiredKeyedService<IInterface>(anyObjectKeyB);
        Console.WriteLine(instanceByAnyObject.GetType().Name); // ConcreteClassB
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.KeyedInjectionsSupportedKeyTypes)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.KeyedInjections.SupportedKeyTypes;

// Simple enum that will be used as a key for the injections
internal enum Key
{
    A,
    B,
    C
}

// Simple attribute that will be used to configure a key directly on an implementation type
// or assign a key to a parameter or property.
// The attribute type requires to have a constructor that has its first parameter of type object.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = true)]
internal class KeyAssignmentAttribute : Attribute
{
    internal KeyAssignmentAttribute(object value) {}
}

// Simple interface that will work as an abstraction for the implementations
internal interface IInterface
{
}

[KeyAssignment(Key.A)]
[KeyAssignment(Byte)]
[KeyAssignment(SByte)]
[KeyAssignment(Short)]
[KeyAssignment(UShort)]
[KeyAssignment(Int)]
[KeyAssignment(UInt)]
[KeyAssignment(Long)]
[KeyAssignment(ULong)]
[KeyAssignment(Char)]
[KeyAssignment(Float)]
[KeyAssignment(Double)]
[KeyAssignment(String)]
[KeyAssignment(Bool)]
[KeyAssignment(typeof(ConcreteClassA))]
internal class ConcreteClassA : IInterface
{
    internal const byte Byte = 0;
    internal const sbyte SByte = 1;
    internal const short Short = 2;
    internal const ushort UShort = 3;
    internal const int Int = 4;
    internal const uint UInt = 5;
    internal const long Long = 6;
    internal const ulong ULong = 7;
    internal const char Char = (char) 8;
    internal const float Float = 9.0f;
    internal const double Double = 10.0;
    internal const string String = "11";
    internal const bool Bool = true;
}

[KeyAssignment(Key.B)]
[KeyAssignment(Byte)]
[KeyAssignment(SByte)]
[KeyAssignment(Short)]
[KeyAssignment(UShort)]
[KeyAssignment(Int)]
[KeyAssignment(UInt)]
[KeyAssignment(Long)]
[KeyAssignment(ULong)]
[KeyAssignment(Char)]
[KeyAssignment(Float)]
[KeyAssignment(Double)]
[KeyAssignment(String)]
[KeyAssignment(Bool)]
[KeyAssignment(typeof(ConcreteClassB))]
internal class ConcreteClassB : IInterface
{
    internal const byte Byte = 12;
    internal const sbyte SByte = 13;
    internal const short Short = 14;
    internal const ushort UShort = 15;
    internal const int Int = 16;
    internal const uint UInt = 17;
    internal const long Long = 18;
    internal const ulong ULong = 19;
    internal const char Char = (char) 20;
    internal const float Float = 21.0f;
    internal const double Double = 22.0;
    internal const string String = "23";
    internal const bool Bool = false;
}


[KeyAssignment(Key.C)]
[KeyAssignment(Byte)]
[KeyAssignment(SByte)]
[KeyAssignment(Short)]
[KeyAssignment(UShort)]
[KeyAssignment(Int)]
[KeyAssignment(UInt)]
[KeyAssignment(Long)]
[KeyAssignment(ULong)]
[KeyAssignment(Char)]
[KeyAssignment(Float)]
[KeyAssignment(Double)]
[KeyAssignment(String)]
[KeyAssignment(Bool)]
[KeyAssignment(typeof(ConcreteClassC))]
internal class ConcreteClassC : IInterface
{
    internal const byte Byte = 24;
    internal const sbyte SByte = 25;
    internal const short Short = 26;
    internal const ushort UShort = 27;
    internal const int Int = 28;
    internal const uint UInt = 29;
    internal const long Long = 30;
    internal const ulong ULong = 31;
    internal const char Char = (char) 32;
    internal const float Float = 33.0f;
    internal const double Double = 34.0;
    internal const string String = "35";
    internal const bool Bool = true;
}

internal class Parent
{
    [KeyAssignment(Key.B)]
    public required IInterface DependencyEnum { get; init; }
    
    [KeyAssignment(ConcreteClassB.Byte)]
    public required IInterface DependencyByte { get; init; }
    
    [KeyAssignment(ConcreteClassB.SByte)]
    public required IInterface DependencySByte { get; init; }

    [KeyAssignment(ConcreteClassB.Short)]
    public required IInterface DependencyShort { get; init; }

    [KeyAssignment(ConcreteClassB.UShort)]
    public required IInterface DependencyUShort { get; init; }

    [KeyAssignment(ConcreteClassB.Int)]
    public required IInterface DependencyInt { get; init; }

    [KeyAssignment(ConcreteClassB.UInt)]
    public required IInterface DependencyUInt { get; init; }

    [KeyAssignment(ConcreteClassB.Long)]
    public required IInterface DependencyLong { get; init; }

    [KeyAssignment(ConcreteClassB.ULong)]
    public required IInterface DependencyULong { get; init; }

    [KeyAssignment(ConcreteClassB.Char)]
    public required IInterface DependencyChar { get; init; }

    [KeyAssignment(ConcreteClassB.Float)]
    public required IInterface DependencyFloat { get; init; }

    [KeyAssignment(ConcreteClassB.Double)]
    public required IInterface DependencyDouble { get; init; }

    [KeyAssignment(ConcreteClassB.String)]
    public required IInterface DependencyString { get; init; }

    [KeyAssignment(ConcreteClassB.Bool)]
    public required IInterface DependencyBool { get; init; }

    [KeyAssignment(typeof(ConcreteClassB))]
    public required IInterface DependencyType { get; init; }
}

// Registering all the implementations
[ImplementationAggregation(
    typeof(Parent),
    typeof(ConcreteClassA), 
    typeof(ConcreteClassB), 
    typeof(ConcreteClassC))]

// Configure a mapping to the custom key assigning attribute
[InjectionKeyMapping(typeof(KeyAssignmentAttribute))]

[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine(parent.DependencyEnum.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyByte.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencySByte.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyShort.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyUShort.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyInt.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyUInt.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyLong.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyULong.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyChar.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyFloat.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyDouble.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyString.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyBool.GetType().Name); // ConcreteClassB
        Console.WriteLine(parent.DependencyType.GetType().Name); // ConcreteClassB
    }
}
Supported Map Types 🔍
Showcase of supported map types (dictionaries or iterables of KeyValuePair<Key, T>).
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.KeyedInjectionsSupportedMapTypes)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.KeyedInjections.SupportedMapTypes;

// Simple enum that will be used as a key for the injections
internal enum Key
{
    A,
    B,
    C,
    D
}

// Simple attribute that will be used to configure a key directly on an implementation type
// or assign a key to a parameter or property.
// The attribute type requires to have a constructor that has its first parameter of type object.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Parameter | AttributeTargets.Property)]
internal class KeyAssignmentAttribute : Attribute
{
    internal KeyAssignmentAttribute(object value) {}
}

// Simple interface that will work as an abstraction for the implementations
internal interface IInterface
{
}

[KeyAssignment(Key.A)]
internal class ConcreteClassA : IInterface { }

[KeyAssignment(Key.B)]
internal class ConcreteClassB : IInterface { }

[KeyAssignment(Key.C)]
internal class ConcreteClassC : IInterface { }

internal interface IAsyncInterface
{
}

[KeyAssignment(Key.A)]
internal class AsyncConcreteClassA : IAsyncInterface
{
    public Task InitializeAsync() => Task.CompletedTask;
}

[KeyAssignment(Key.B)]
internal class AsyncConcreteClassB : IAsyncInterface
{
    public async ValueTask InitializeAsync() => await Task.Yield();
}

[KeyAssignment(Key.C)]
internal class AsyncConcreteClassC : IAsyncInterface
{
    public Task InitializeAsync() => Task.CompletedTask;
}

internal class Parent
{
    public required IEnumerable<KeyValuePair<Key, IInterface>> Dependency { get; init; }
    public required IAsyncEnumerable<KeyValuePair<Key, IAsyncInterface>> DependencyIAsyncEnumerable { get; init; }
    public required ValueTask<IEnumerable<KeyValuePair<Key, IInterface>>> DependencyValueTaskIEnumerable { get; init; }
    public required Task<IEnumerable<KeyValuePair<Key, IInterface>>> DependencyTaskIEnumerable { get; init; }
    public required IEnumerable<KeyValuePair<Key, IAsyncInterface>> DependencyAsyncIEnumerable { get; init; }
    public required IDictionary<Key, IInterface> DependencyIDictionary { get; init; }
    public required IReadOnlyDictionary<Key, IInterface> DependencyIReadOnlyDictionary { get; init; }
    public required Dictionary<Key, IInterface> DependencyDictionary { get; init; }
    public required ReadOnlyDictionary<Key, IInterface> DependencyReadOnlyDictionary { get; init; }
    public required SortedDictionary<Key, IInterface> DependencySortedDictionary { get; init; }
    public required SortedList<Key, IInterface> DependencySortedList { get; init; }
    public required ImmutableDictionary<Key, IInterface> DependencyImmutableDictionary { get; init; }
    public required ImmutableSortedDictionary<Key, IInterface> DependencyImmutableSortedDictionary { get; init; }
    public required ValueTask<IDictionary<Key, IInterface>> DependencyValueTaskIDictionary { get; init; }
    public required ValueTask<IReadOnlyDictionary<Key, IInterface>> DependencyValueTaskIReadOnlyDictionary { get; init; }
    public required ValueTask<Dictionary<Key, IInterface>> DependencyValueTaskDictionary { get; init; }
    public required ValueTask<ReadOnlyDictionary<Key, IInterface>> DependencyValueTaskReadOnlyDictionary { get; init; }
    public required ValueTask<SortedDictionary<Key, IInterface>> DependencyValueTaskSortedDictionary { get; init; }
    public required ValueTask<SortedList<Key, IInterface>> DependencyValueTaskSortedList { get; init; }
    public required ValueTask<ImmutableDictionary<Key, IInterface>> DependencyValueTaskImmutableDictionary { get; init; }
    public required ValueTask<ImmutableSortedDictionary<Key, IInterface>> DependencyValueTaskImmutableSortedDictionary { get; init; }
    public required Task<IDictionary<Key, IInterface>> DependencyTaskIDictionary { get; init; }
    public required Task<IReadOnlyDictionary<Key, IInterface>> DependencyTaskIReadOnlyDictionary { get; init; }
    public required Task<Dictionary<Key, IInterface>> DependencyTaskDictionary { get; init; }
    public required Task<ReadOnlyDictionary<Key, IInterface>> DependencyTaskReadOnlyDictionary { get; init; }
    public required Task<SortedDictionary<Key, IInterface>> DependencyTaskSortedDictionary { get; init; }
    public required Task<SortedList<Key, IInterface>> DependencyTaskSortedList { get; init; }
    public required Task<ImmutableDictionary<Key, IInterface>> DependencyTaskImmutableDictionary { get; init; }
    public required Task<ImmutableSortedDictionary<Key, IInterface>> DependencyTaskImmutableSortedDictionary { get; init; }
    public required IDictionary<Key, IAsyncInterface> DependencyAsyncIDictionary { get; init; }
    public required IReadOnlyDictionary<Key, IAsyncInterface> DependencyAsyncIReadOnlyDictionary { get; init; }
    public required Dictionary<Key, IAsyncInterface> DependencyAsyncDictionary { get; init; }
    public required ReadOnlyDictionary<Key, IAsyncInterface> DependencyAsyncReadOnlyDictionary { get; init; }
    public required SortedDictionary<Key, IAsyncInterface> DependencyAsyncSortedDictionary { get; init; }
    public required SortedList<Key, IAsyncInterface> DependencyAsyncSortedList { get; init; }
    public required ImmutableDictionary<Key, IAsyncInterface> DependencyAsyncImmutableDictionary { get; init; }
    public required ImmutableSortedDictionary<Key, IAsyncInterface> DependencyAsyncImmutableSortedDictionary { get; init; }
}

// Registering all the implementations
[ImplementationAggregation(
    typeof(Parent),
    typeof(ConcreteClassA), 
    typeof(ConcreteClassB), 
    typeof(ConcreteClassC), 
    typeof(AsyncConcreteClassA), 
    typeof(AsyncConcreteClassB), 
    typeof(AsyncConcreteClassC))]
[Initializer(typeof(AsyncConcreteClassA), nameof(AsyncConcreteClassA.InitializeAsync))]
[Initializer(typeof(AsyncConcreteClassB), nameof(AsyncConcreteClassB.InitializeAsync))]
[Initializer(typeof(AsyncConcreteClassC), nameof(AsyncConcreteClassC.InitializeAsync))]

// Configure a mapping to the custom key assigning attribute
[InjectionKeyMapping(typeof(KeyAssignmentAttribute))]

[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async Task Use()
    {
        await using var container = Container.DIE_CreateContainer();
        var parent = await container.Create();
        Check(parent.Dependency);
        await CheckAsync(parent.DependencyIAsyncEnumerable);
        Check(await parent.DependencyValueTaskIEnumerable);
        Check(await parent.DependencyTaskIEnumerable);
        CheckIAsyncInterface(parent.DependencyAsyncIEnumerable);
        Check(parent.DependencyIDictionary);
        Check(parent.DependencyIReadOnlyDictionary);
        Check(parent.DependencyDictionary);
        Check(parent.DependencyReadOnlyDictionary);
        Check(parent.DependencySortedDictionary);
        Check(parent.DependencySortedList);
        Check(parent.DependencyImmutableDictionary);
        Check(parent.DependencyImmutableSortedDictionary);
        Check(await parent.DependencyValueTaskIDictionary);
        Check(await parent.DependencyValueTaskIReadOnlyDictionary);
        Check(await parent.DependencyValueTaskDictionary);
        Check(await parent.DependencyValueTaskReadOnlyDictionary);
        Check(await parent.DependencyValueTaskSortedDictionary);
        Check(await parent.DependencyValueTaskSortedList);
        Check(await parent.DependencyValueTaskImmutableDictionary);
        Check(await parent.DependencyValueTaskImmutableSortedDictionary);
        Check(await parent.DependencyTaskIDictionary);
        Check(await parent.DependencyTaskIReadOnlyDictionary);
        Check(await parent.DependencyTaskDictionary);
        Check(await parent.DependencyTaskReadOnlyDictionary);
        Check(await parent.DependencyTaskSortedDictionary);
        Check(await parent.DependencyTaskSortedList);
        Check(await parent.DependencyTaskImmutableDictionary);
        Check(await parent.DependencyTaskImmutableSortedDictionary);
        CheckIAsyncInterface(parent.DependencyAsyncIDictionary);
        CheckIAsyncInterface(parent.DependencyAsyncIReadOnlyDictionary);
        CheckIAsyncInterface(parent.DependencyAsyncDictionary);
        CheckIAsyncInterface(parent.DependencyAsyncReadOnlyDictionary);
        CheckIAsyncInterface(parent.DependencyAsyncSortedDictionary);
        CheckIAsyncInterface(parent.DependencyAsyncSortedList);
        CheckIAsyncInterface(parent.DependencyAsyncImmutableDictionary);
        CheckIAsyncInterface(parent.DependencyAsyncImmutableSortedDictionary);
        return;

        void Check(IEnumerable<KeyValuePair<Key, IInterface>> map)
        {
            Console.WriteLine(map.GetType().Name);
            var containsD = false;
            Console.WriteLine(map.Count()); // 3
            foreach (var (key, value) in map)
            {
                Console.WriteLine(key);
                Console.WriteLine(value.GetType().Name);
                if (key == Key.D) containsD = true;
            }
            // Because the key D is assigned to multiple implementations and a single value type is expected,
            // there won't be an entry for key D in the map.
            Console.WriteLine(containsD); // False
        }

        void CheckIAsyncInterface(IEnumerable<KeyValuePair<Key, IAsyncInterface>> map)
        {
            Console.WriteLine(map.GetType().Name);
            var containsD = false;
            Console.WriteLine(map.Count()); // 3
            foreach (var (key, value) in map)
            {
                Console.WriteLine(key);
                Console.WriteLine(value.GetType().Name);
                if (key == Key.D) containsD = true;
            }
            // Because the key D is assigned to multiple implementations and a single value type is expected,
            // there won't be an entry for key D in the map.
            Console.WriteLine(containsD); // False
        }

        async Task CheckAsync(IAsyncEnumerable<KeyValuePair<Key, IAsyncInterface>> map)
        {
            Console.WriteLine(map.GetType().Name);
            var containsD = false;
            var count = 0;
            await foreach (var (key, value) in map)
            {
                count++;
                Console.WriteLine(key);
                Console.WriteLine(value.GetType().Name);
                if (key == Key.D) containsD = true;
            }
            Console.WriteLine(count); // 3
            // Because the key D is assigned to multiple implementations and a single value type is expected,
            // there won't be an entry for key D in the map.
            Console.WriteLine(containsD); // False
        }
    }
}
Key Value Injection 🔍
Keyed implementation types can get their key value injected.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.KeyedInjectionsKeyValueInjection)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.KeyedInjections.KeyValueInjection;

// Simple enum that will be used as a key for the injections
internal enum Key
{
    A,
    B
}

// Simple interface that will work as an abstraction for the implementations
internal interface IInterface
{
    Key KeyValue { get; }
}

// Implementation that'll get the key A assigned during registration
internal class ConcreteClassA : IInterface
{
    public ConcreteClassA([ServiceKey] Key keyValue)
    {
        KeyValue = keyValue;
    }

    public Key KeyValue { get; }
}

// Implementation that'll get the key B assigned during registration
internal class ConcreteClassB : IInterface
{
    public ConcreteClassB([ServiceKey] Key keyValue)
    {
        KeyValue = keyValue;
    }

    public Key KeyValue { get; }
}

internal class Parent
{
    internal IInterface Dependency { get; }
    
    // Inject a single implementation for the key A
    public Parent([FromKeyedServices(Key.A)] IInterface dependency) => Dependency = dependency;
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<Parent>();
        
        // Register the implementations with the appropriate keys
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassA>(Key.A);
        builder.Services.AddKeyedTransient<IInterface, ConcreteClassB>(Key.B);
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var instance = host.Services.GetRequiredService<Parent>();
        Console.WriteLine(instance.Dependency.KeyValue); // A
    }
}
Imagined use cases didn't justify the implementation effort. If this particular feature is still crucial for you, let's get into discussion about it: https://github.com/Yeah69/MrMeeseeks.DIE/discussions

Async

Group of features concerned with support for asynchronous programming.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Injecting Async Dependency (Not Wrapped) 🔍
Injection of an async dependency (e.g. one which has an async type initializer), where the dependency isn't wrapped into a Task<T>, a ValueTask<T> or an IAsyncEnumerable<T>.
Microsoft.Extensions.DependencyInjection does not support async initialization (see async samples in "Type Initializers" group).
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AsyncNotWrapped)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Async.NotWrapped;

// Simple class that we want to inject into another class and which is initialized asynchronously
internal class ConcreteClassTask
{
    internal bool Initialized { get; private set; }
    internal async Task Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

// Another simple class that we want to inject into another class and which is initialized asynchronously (this time with ValueTask)
internal class ConcreteClassValueTask
{
    internal bool Initialized { get; private set; }
    internal async ValueTask Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

internal class Parent
{
    // Both of the simple classes are injected without being wrapped in a Task<T> or ValueTask<T>
    internal Parent(ConcreteClassTask taskBasedDependency, ConcreteClassValueTask valueTaskBasedDependency)
    {
        TaskBasedDependency = taskBasedDependency;
        ValueTaskBasedDependency = valueTaskBasedDependency;
    }
    
    internal ConcreteClassTask TaskBasedDependency { get; }
    internal ConcreteClassValueTask ValueTaskBasedDependency { get; }
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClassTask), typeof(ConcreteClassValueTask))]
[Initializer(typeof(ConcreteClassTask), nameof(ConcreteClassTask.Initialize))]
[Initializer(typeof(ConcreteClassValueTask), nameof(ConcreteClassValueTask.Initialize))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async Task Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // Notice that the Create-function returns a ValueTask<Parent> and not a Parent.
        // This is due to the necessary await in the Create-function, because the simple classes are initialized asynchronously but injected synchronously (not wrapped).
        var parent = await container.Create();
        Console.WriteLine($"Initialized: {parent.TaskBasedDependency.Initialized}"); // Initialized: True
        Console.WriteLine($"Initialized: {parent.ValueTaskBasedDependency.Initialized}"); // Initialized: True
    }
}
Injecting Async Dependency (Task<T>) 🔍
Injection of an async dependency (e.g. one which has an async type initializer), where the dependency is wrapped into a Task<T>.
Microsoft.Extensions.DependencyInjection does not support async initialization (see async samples in "Type Initializers" group).
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AsyncWrappedTask)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Async.WrappedTask;

// Simple class that we want to inject into another class and which is initialized asynchronously
internal class ConcreteClassTask
{
    internal bool Initialized { get; private set; }
    internal async Task Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

// Another simple class that we want to inject into another class and which is initialized asynchronously (this time with ValueTask)
internal class ConcreteClassValueTask
{
    internal bool Initialized { get; private set; }
    internal async ValueTask Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

internal class Parent
{
    // Both of the simple classes are injected wrapped into Task<T>
    internal Parent(Task<ConcreteClassTask> taskBasedDependency, Task<ConcreteClassValueTask> valueTaskBasedDependency)
    {
        TaskBasedDependency = taskBasedDependency;
        ValueTaskBasedDependency = valueTaskBasedDependency;
    }
    
    internal Task<ConcreteClassTask> TaskBasedDependency { get; }
    internal Task<ConcreteClassValueTask> ValueTaskBasedDependency { get; }
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClassTask), typeof(ConcreteClassValueTask))]
[Initializer(typeof(ConcreteClassTask), nameof(ConcreteClassTask.Initialize))]
[Initializer(typeof(ConcreteClassValueTask), nameof(ConcreteClassValueTask.Initialize))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async Task Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // Notice that the Create-function stays synchronous, because the injections of the simple classes are wrapped in a Task<T>.
        var parent = container.Create();
        Console.WriteLine($"Initialized: {(await parent.TaskBasedDependency).Initialized}"); // Initialized: True
        Console.WriteLine($"Initialized: {(await parent.ValueTaskBasedDependency).Initialized}"); // Initialized: True
    }
}
Injecting Async Dependency (ValueTask<T>) 🔍
Injection of an async dependency (e.g. one which has an async type initializer), where the dependency is wrapped into a ValueTask<T>.
Microsoft.Extensions.DependencyInjection does not support async initialization (see async samples in "Type Initializers" group).
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AsyncWrappedValueTask)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Async.WrappedValueTask;

// Simple class that we want to inject into another class and which is initialized asynchronously
internal class ConcreteClassTask
{
    internal bool Initialized { get; private set; }
    internal async Task Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

// Another simple class that we want to inject into another class and which is initialized asynchronously (this time with ValueTask)
internal class ConcreteClassValueTask
{
    internal bool Initialized { get; private set; }
    internal async ValueTask Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

internal class Parent
{
    // Both of the simple classes are injected wrapped into ValueTask<T>
    internal Parent(ValueTask<ConcreteClassTask> taskBasedDependency, ValueTask<ConcreteClassValueTask> valueTaskBasedDependency)
    {
        TaskBasedDependency = taskBasedDependency;
        ValueTaskBasedDependency = valueTaskBasedDependency;
    }
    
    internal ValueTask<ConcreteClassTask> TaskBasedDependency { get; }
    internal ValueTask<ConcreteClassValueTask> ValueTaskBasedDependency { get; }
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClassTask), typeof(ConcreteClassValueTask))]
[Initializer(typeof(ConcreteClassTask), nameof(ConcreteClassTask.Initialize))]
[Initializer(typeof(ConcreteClassValueTask), nameof(ConcreteClassValueTask.Initialize))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async Task Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // Notice that the Create-function stays synchronous, because the injections of the simple classes are wrapped in a ValueTask<T>.
        var parent = container.Create();
        Console.WriteLine($"Initialized: {(await parent.TaskBasedDependency).Initialized}"); // Initialized: True
        Console.WriteLine($"Initialized: {(await parent.ValueTaskBasedDependency).Initialized}"); // Initialized: True
    }
}
Injecting Async Dependency (IEnumerable<T>) 🔍
Injection of an iterable of async dependencies (e.g. one which has an async type initializer), where the iterable of dependencies is not wrapped into an IAsyncEnumerable<T>.
Microsoft.Extensions.DependencyInjection does not support async initialization (see async samples in "Type Initializers" group).
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AsyncNotWrappedIEnumerable)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Async.NotWrappedIEnumerable;

internal interface IInterface
{
    bool Initialized { get; }
}

// Simple class that we want to inject into another class and which is initialized asynchronously
internal class ConcreteClassTask : IInterface
{
    public bool Initialized { get; private set; }
    internal async Task Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

// Another simple class that we want to inject into another class and which is initialized asynchronously (this time with ValueTask)
internal class ConcreteClassValueTask : IInterface
{
    public bool Initialized { get; private set; }
    internal async ValueTask Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

internal class Parent
{
    // The injection of the simple classes is wrapped in a IEnumerable<T>.
    // However, it is still not wrapped in manner of asynchronous injection.
    // Same follows for all other iterables (IReadOnlyList<T>, ICollection<T>, IList<T>, IReadOnlyCollection<T>, etc.) except for IAsyncEnumerable<T>.
    internal Parent(IEnumerable<IInterface> dependencies) => Dependencies = dependencies;
    internal IEnumerable<IInterface> Dependencies { get; }
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClassTask), typeof(ConcreteClassValueTask))]
[Initializer(typeof(ConcreteClassTask), nameof(ConcreteClassTask.Initialize))]
[Initializer(typeof(ConcreteClassValueTask), nameof(ConcreteClassValueTask.Initialize))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async Task Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // Notice that the Create-function returns a ValueTask<Parent> and not a Parent.
        // This is due to the necessary await in the Create-function, because the simple classes are initialized asynchronously but injected synchronously (not asynchronously wrapped).
        var parent = await container.Create();
        foreach (var dependency in parent.Dependencies)
        {
            Console.WriteLine($"Initialized: {dependency.Initialized}"); // Initialized: True
        }
    }
}
Injecting Async Dependencies (IAsyncEnumerable<T>) 🔍
Injection of an iterable of async dependencies (e.g. one which has an async type initializer), where the iterable of the dependencies is wrapped into an IAsyncEnumerable<T>.
Microsoft.Extensions.DependencyInjection does not support async initialization (see async samples in "Type Initializers" group).
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.AsyncWrappedIAsyncEnumerable)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Async.WrappedIAsyncEnumerable;

internal interface IInterface
{
    bool Initialized { get; }
}

// Simple class that we want to inject into another class and which is initialized asynchronously
internal class ConcreteClassTask : IInterface
{
    public bool Initialized { get; private set; }
    internal async Task Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

// Another simple class that we want to inject into another class and which is initialized asynchronously (this time with ValueTask)
internal class ConcreteClassValueTask : IInterface
{
    public bool Initialized { get; private set; }
    internal async ValueTask Initialize()
    {
        await Task.Yield();
        Initialized = true;
    }
}

internal class Parent
{
    // Injecting the simple classes wrapped in an IAsyncEnumerable<T>
    internal Parent(IAsyncEnumerable<IInterface> dependencies) => Dependencies = dependencies;
    internal IAsyncEnumerable<IInterface> Dependencies { get; }
}

[ImplementationAggregation(typeof(Parent), typeof(ConcreteClassTask), typeof(ConcreteClassValueTask))]
[Initializer(typeof(ConcreteClassTask), nameof(ConcreteClassTask.Initialize))]
[Initializer(typeof(ConcreteClassValueTask), nameof(ConcreteClassValueTask.Initialize))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async Task Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // Notice that the Create-function stays synchronous, because the injection of the simple classes is wrapped in an IAsyncEnumerable<T>.
        var parent = container.Create();
        await foreach (var dependency in parent.Dependencies)
        {
            Console.WriteLine($"Initialized: {dependency.Initialized}"); // Initialized: True
        }
    }
}

Scopes

Group of features concerned with scopes.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Creation of a Simple Scope 🔍
This container can create a simple scope. Simple means that the scope doesn't have a self-determined lifetime. It will be disposed when its parent scope is disposed.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ScopesSimple)]
[assembly:FeatureSample(Feature.ScopesTransient)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Scopes.Simple;

// Simple class that will be instantiated inside a scope
internal class ConcreteClass : IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        // Create a new scope
        var scope = host.Services.CreateScope();

        var concreteClass = scope.ServiceProvider.GetRequiredService<ConcreteClass>();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: False
        // Disposing the scope will dispose all its managed disposable dependencies.
        scope.Dispose();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ScopesSimple)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Scopes.Simple;

// Simple class that will be implicitly inside a scope, because it'll be injected into a scope root
internal class ConcreteClass : IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

// A scope root. Anytime that this class is created or injected, a new scope is created which will take over creation of this scope root.
internal class ScopeRoot
{
    internal ScopeRoot(ConcreteClass dependency) => Dependency = dependency;
    internal ConcreteClass Dependency { get; }
}

[ImplementationAggregation(typeof(ScopeRoot), typeof(ConcreteClass))]
// Following configuration makes the ScopeRoot a scope root.
// The scope that creates the scope root will be generated into the container.
[ScopeRootImplementationAggregation(typeof(ScopeRoot))]
[CreateFunction(typeof(ScopeRoot), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        var container = Container.DIE_CreateContainer();
        // Resolving a scope root
        var scopeRoot = container.Create();
        Console.WriteLine($"Disposed: {scopeRoot.Dependency.Disposed}"); // Disposed: False
        // Disposing the container will dispose all its scopes which in turn will dispose all their managed disposable dependencies.
        container.Dispose();
        Console.WriteLine($"Disposed: {scopeRoot.Dependency.Disposed}"); // Disposed: True
    }
}
Creation of a Transient Scope 🔍
This container can create a transient scope. Transient means that it can be disposed independently of its parent scope.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ScopesSimple)]
[assembly:FeatureSample(Feature.ScopesTransient)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Scopes.Simple;

// Simple class that will be instantiated inside a scope
internal class ConcreteClass : IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        // Create a new scope
        var scope = host.Services.CreateScope();

        var concreteClass = scope.ServiceProvider.GetRequiredService<ConcreteClass>();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: False
        // Disposing the scope will dispose all its managed disposable dependencies.
        scope.Dispose();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ScopesTransient)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Scopes.Transient;

// Simple class that will be implicitly inside a scope, because it'll be injected into a scope root
internal class ConcreteClass : IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

// A transient scope root. Anytime that this class is created or injected, a new transient scope is created which will take over creation of this transient scope root.
internal class TransientScopeRoot
{
    private readonly IDisposable _transientScopeDisposal;

    internal TransientScopeRoot(
        ConcreteClass dependency, 
        // A transient scope root can get an IDisposable instance which can be used to trigger the disposal of the transient scope eagerly
        IDisposable transientScopeDisposal)
    {
        _transientScopeDisposal = transientScopeDisposal;
        Dependency = dependency;
    }

    internal ConcreteClass Dependency { get; }
    // A transient scope root can decide on its own when to dispose the transient scope and all its managed disposable dependencies.
    internal void CleanUp() => _transientScopeDisposal.Dispose();
}

[ImplementationAggregation(typeof(TransientScopeRoot), typeof(ConcreteClass))]
// Following configuration makes the TransientScopeRoot a transient scope root.
// The transient scope that creates the transient scope root will be generated into the container.
[TransientScopeRootImplementationAggregation(typeof(TransientScopeRoot))]
[CreateFunction(typeof(TransientScopeRoot), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        // Creating a transient scope root
        var transientScopeRoot = container.Create();
        Console.WriteLine($"Disposed: {transientScopeRoot.Dependency.Disposed}"); // Disposed: False
        // When a transient scope root triggers the disposal of its transient scope, the transient scope and all its managed disposable dependencies will be disposed.
        // This includes also all ordinary child scopes that were created from within the transient scope.
        // Disposing the container will dispose all transient scopes that weren't disposed yet.
        transientScopeRoot.CleanUp();
        Console.WriteLine($"Disposed: {transientScopeRoot.Dependency.Disposed}"); // Disposed: True
    }
}
Configuration of Root Types 🔍
Dependency Types can be declared root types for scopes. That means, whenever the root type gets injected, it starts a new scope around it.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ScopesRootTypes)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Scopes.RootTypes;

internal class ConcreteClass : IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

internal class ScopeRoot
{
    internal ScopeRoot(ConcreteClass dependency) => Dependency = dependency;
    internal ConcreteClass Dependency { get; }
}

internal class TransientScopeRoot
{
    private readonly IDisposable _transientScopeDisposal;

    internal TransientScopeRoot(
        ConcreteClass dependency, 
        // A transient scope root can get an IDisposable instance which can be used to trigger the disposal of the transient scope eagerly
        IDisposable transientScopeDisposal)
    {
        _transientScopeDisposal = transientScopeDisposal;
        Dependency = dependency;
    }

    internal ConcreteClass Dependency { get; }
    internal void CleanUp() => _transientScopeDisposal.Dispose();
}

internal class Parent
{
    internal Parent(
        // Scope roots can be injected like any other dependency. The container will wrap a scope around such a scope root dependency.
        ScopeRoot scopeRoot, 
        // The same applies to transient scope roots
        TransientScopeRoot transientScopeRoot,
        // This transient scope root won't be disposed eagerly but will be disposed along the container's disposal
        TransientScopeRoot disposedByContainer)
    {
        ScopeRoot = scopeRoot;
        TransientScopeRoot = transientScopeRoot;
        DisposedByContainer = disposedByContainer;
    }

    internal ScopeRoot ScopeRoot { get; }
    internal TransientScopeRoot TransientScopeRoot { get; }
    public TransientScopeRoot DisposedByContainer { get; }
}


[ImplementationAggregation(typeof(Parent), typeof(ScopeRoot), typeof(TransientScopeRoot), typeof(ConcreteClass))]
[ScopeRootImplementationAggregation(typeof(ScopeRoot))]
[TransientScopeRootImplementationAggregation(typeof(TransientScopeRoot))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        Console.WriteLine($"Disposed: {parent.ScopeRoot.Dependency.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {parent.TransientScopeRoot.Dependency.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {parent.DisposedByContainer.Dependency.Disposed}"); // Disposed: False
        // Disposing a transient scope root will dispose the transient scope and all its managed disposable dependencies.
        parent.TransientScopeRoot.CleanUp();
        Console.WriteLine($"Disposed: {parent.ScopeRoot.Dependency.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {parent.TransientScopeRoot.Dependency.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {parent.DisposedByContainer.Dependency.Disposed}"); // Disposed: False
        // Disposing the container will dispose every scope and all managed disposable dependencies that weren't disposed yet.
        container.Dispose();
        Console.WriteLine($"Disposed: {parent.ScopeRoot.Dependency.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {parent.TransientScopeRoot.Dependency.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {parent.DisposedByContainer.Dependency.Disposed}"); // Disposed: True
    }
}
Reconfiguration of a Scope 🔍
Scopes can be reconfigured to a certain degree.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ScopesReconfiguration)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Scopes.Reconfiguration;

internal interface IInterface {}

// The names of following simple classes indicate in which kind of scope they'll be created.
internal class ConcreteClassContainer : IInterface { }
internal class ConcreteClassScope : IInterface { }
internal class ConcreteClassTransientScope : IInterface { }

internal class ScopeRoot
{
    internal ScopeRoot(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class TransientScopeRoot
{
    internal TransientScopeRoot(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class Parent
{
    internal Parent(
        IInterface dependency,
        ScopeRoot scopeRoot, 
        TransientScopeRoot transientScopeRoot)
    {
        Dependency = dependency;
        ScopeRoot = scopeRoot;
        TransientScopeRoot = transientScopeRoot;
    }

    public IInterface Dependency { get; }
    internal ScopeRoot ScopeRoot { get; }
    internal TransientScopeRoot TransientScopeRoot { get; }
}


[ImplementationAggregation(typeof(Parent), typeof(ScopeRoot), typeof(TransientScopeRoot), typeof(ConcreteClassContainer), typeof(ConcreteClassScope), typeof(ConcreteClassTransientScope))]
[ScopeRootImplementationAggregation(typeof(ScopeRoot))]
[TransientScopeRootImplementationAggregation(typeof(TransientScopeRoot))]
[ImplementationChoice(typeof(IInterface), typeof(ConcreteClassContainer))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
    
    // Per default scopes inherit all attributed configurations from the container.
    // Optionally, they can be reconfigured.
    // This is done by creating a partial class which name starts with "DIE_Scope" and has following attribute which specifies the scope root type for which it is applied.
    [CustomScopeForRootTypes(typeof(ScopeRoot))]
    // Here we reconfigure the implementation choice for IInterface to ConcreteClassScope
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassScope))]
    private sealed partial class DIE_Scope {}
    
    // Reconfiguration for transient scopes works the same way but the partial class name must start with "DIE_TransientScope".
    [CustomScopeForRootTypes(typeof(TransientScopeRoot))]
    // Here we reconfigure the implementation choice for IInterface to ConcreteClassTransientScope
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassTransientScope))]
    private sealed partial class DIE_TransientScope {}
}

internal static class Usage
{
    internal static void Use()
    {
        var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        // Each scope gets its configured implementation choice for IInterface injected.
        Console.WriteLine($"Container: {parent.Dependency.GetType().Name}"); // Container: ConcreteClassContainer
        Console.WriteLine($"Scope: {parent.ScopeRoot.Dependency.GetType().Name}"); // , Scope: ConcreteClassScope
        Console.WriteLine($"TransientScope: {parent.TransientScopeRoot.Dependency.GetType().Name}"); // TransientScope: ConcreteClassTransientScope
    }
}
Fragmentation by Root Types 🔍
Ability to use different scopes for different scope root types.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ScopesFragmentationByRootTypes)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Scopes.FragmentationByRootTypes;

internal interface IInterface {}

internal class ConcreteClassContainer : IInterface { }
internal class ConcreteClassScopeA : IInterface { }
internal class ConcreteClassScopeB : IInterface { }
internal class ConcreteClassTransientScopeA : IInterface { }
internal class ConcreteClassTransientScopeB : IInterface { }


internal class ScopeRootA
{
    internal ScopeRootA(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class ScopeRootB
{
    internal ScopeRootB(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class TransientScopeRootA
{
    internal TransientScopeRootA(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class TransientScopeRootB
{
    internal TransientScopeRootB(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class Parent
{
    internal Parent(
        IInterface dependency,
        ScopeRootA scopeRootA,
        ScopeRootB scopeRootB,
        TransientScopeRootA transientScopeRootA,
        TransientScopeRootB transientScopeRootB)
    {
        Dependency = dependency;
        ScopeRootA = scopeRootA;
        ScopeRootB = scopeRootB;
        TransientScopeRootA = transientScopeRootA;
        TransientScopeRootB = transientScopeRootB;
    }

    public IInterface Dependency { get; }
    public ScopeRootA ScopeRootA { get; }
    public ScopeRootB ScopeRootB { get; }
    public TransientScopeRootA TransientScopeRootA { get; }
    public TransientScopeRootB TransientScopeRootB { get; }
}

[ImplementationAggregation(typeof(Parent), typeof(ScopeRootA), typeof(ScopeRootB), typeof(TransientScopeRootA), typeof(TransientScopeRootB), typeof(ConcreteClassContainer), typeof(ConcreteClassScopeA), typeof(ConcreteClassScopeB), typeof(ConcreteClassTransientScopeA), typeof(ConcreteClassTransientScopeB))]
[ScopeRootImplementationAggregation(typeof(ScopeRootA), typeof(ScopeRootB))]
[TransientScopeRootImplementationAggregation(typeof(TransientScopeRootA), typeof(TransientScopeRootB))]
[ImplementationChoice(typeof(IInterface), typeof(ConcreteClassContainer))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
    
    // Each (transient) scope root can also get its own dedicated (transient) scope configuration.
    [CustomScopeForRootTypes(typeof(ScopeRootA))]
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassScopeA))]
    private sealed partial class DIE_ScopeA {}
    
    [CustomScopeForRootTypes(typeof(ScopeRootB))]
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassScopeB))]
    private sealed partial class DIE_ScopeB {}
    
    [CustomScopeForRootTypes(typeof(TransientScopeRootA))]
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassTransientScopeA))]
    private sealed partial class DIE_TransientScopeA {}
    
    [CustomScopeForRootTypes(typeof(TransientScopeRootB))]
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassTransientScopeB))]
    private sealed partial class DIE_TransientScopeB {}
}

internal static class Usage
{
    internal static void Use()
    {
        var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        // Each scope gets its configured implementation choice for IInterface injected.
        Console.WriteLine($"Container: {parent.Dependency.GetType().Name}"); // Container: ConcreteClassContainer
        Console.WriteLine($"ScopeA: {parent.ScopeRootA.Dependency.GetType().Name}"); // ScopeA: ConcreteClassScopeA
        Console.WriteLine($"ScopeB: {parent.ScopeRootB.Dependency.GetType().Name}"); // ScopeB: ConcreteClassScopeB
        Console.WriteLine($"TransientScopeA: {parent.TransientScopeRootA.Dependency.GetType().Name}"); // TransientScopeA: ConcreteClassTransientScopeA
        Console.WriteLine($"TransientScopeB: {parent.TransientScopeRootB.Dependency.GetType().Name}"); // TransientScopeB: ConcreteClassTransientScopeB
    }
}
Default Fragment 🔍
If a scope root type has no specific scope fragment, then there is the option fallback to a configurable default scope (fragment).
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ScopesDefaultFragment)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Scopes.DefaultFragment;

internal interface IInterface {}

internal class ConcreteClassContainer : IInterface { }
internal class ConcreteClassScopeDefault : IInterface { }
internal class ConcreteClassScopeA : IInterface { }
internal class ConcreteClassScopeB : IInterface { }
internal class ConcreteClassTransientScopeDefault : IInterface { }
internal class ConcreteClassTransientScopeA : IInterface { }
internal class ConcreteClassTransientScopeB : IInterface { }


internal class ScopeRootDefault
{
    internal ScopeRootDefault(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}


internal class ScopeRootA
{
    internal ScopeRootA(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class ScopeRootB
{
    internal ScopeRootB(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class TransientScopeRootDefault
{
    internal TransientScopeRootDefault(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class TransientScopeRootA
{
    internal TransientScopeRootA(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class TransientScopeRootB
{
    internal TransientScopeRootB(IInterface dependency) => Dependency = dependency;
    internal IInterface Dependency { get; }
}

internal class Parent
{
    internal Parent(
        IInterface dependency,
        ScopeRootDefault scopeRootDefault,
        ScopeRootA scopeRootA,
        ScopeRootB scopeRootB,
        TransientScopeRootDefault transientScopeRootDefault,
        TransientScopeRootA transientScopeRootA,
        TransientScopeRootB transientScopeRootB)
    {
        Dependency = dependency;
        ScopeRootDefault = scopeRootDefault;
        ScopeRootA = scopeRootA;
        ScopeRootB = scopeRootB;
        TransientScopeRootDefault = transientScopeRootDefault;
        TransientScopeRootA = transientScopeRootA;
        TransientScopeRootB = transientScopeRootB;
    }

    public IInterface Dependency { get; }
    public ScopeRootDefault ScopeRootDefault { get; }
    public ScopeRootA ScopeRootA { get; }
    public ScopeRootB ScopeRootB { get; }
    public TransientScopeRootDefault TransientScopeRootDefault { get; }
    public TransientScopeRootA TransientScopeRootA { get; }
    public TransientScopeRootB TransientScopeRootB { get; }
}

[ImplementationAggregation(typeof(Parent), typeof(ScopeRootDefault), typeof(ScopeRootA), typeof(ScopeRootB), typeof(TransientScopeRootDefault), typeof(TransientScopeRootA), typeof(TransientScopeRootB), typeof(ConcreteClassContainer), typeof(ConcreteClassScopeDefault), typeof(ConcreteClassScopeA), typeof(ConcreteClassScopeB), typeof(ConcreteClassTransientScopeDefault), typeof(ConcreteClassTransientScopeA), typeof(ConcreteClassTransientScopeB))]
[ScopeRootImplementationAggregation(typeof(ScopeRootDefault), typeof(ScopeRootA), typeof(ScopeRootB))]
[TransientScopeRootImplementationAggregation(typeof(TransientScopeRootDefault), typeof(TransientScopeRootA), typeof(TransientScopeRootB))]
[ImplementationChoice(typeof(IInterface), typeof(ConcreteClassContainer))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
    
    // The default scope doesn't need to be assigned to a scope root type
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassScopeDefault))]
    private sealed partial class DIE_DefaultScope {}
    
    [CustomScopeForRootTypes(typeof(ScopeRootA))]
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassScopeA))]
    private sealed partial class DIE_ScopeA {}
    
    [CustomScopeForRootTypes(typeof(ScopeRootB))]
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassScopeB))]
    private sealed partial class DIE_ScopeB {}
    
    // The default transient scope doesn't need to be assigned to a transient scope root type
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassTransientScopeDefault))]
    private sealed partial class DIE_DefaultTransientScope {}
    
    [CustomScopeForRootTypes(typeof(TransientScopeRootA))]
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassTransientScopeA))]
    private sealed partial class DIE_TransientScopeA {}
    
    [CustomScopeForRootTypes(typeof(TransientScopeRootB))]
    [ImplementationChoice(typeof(IInterface), typeof(ConcreteClassTransientScopeB))]
    private sealed partial class DIE_TransientScopeB {}
}

internal static class Usage
{
    internal static void Use()
    {
        var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        // Each scope gets its configured implementation choice for IInterface injected.
        Console.WriteLine($"Container: {parent.Dependency.GetType().Name}"); // Container: ConcreteClassContainer
        Console.WriteLine($"DefaultScope: {parent.ScopeRootDefault.Dependency.GetType().Name}"); // DefaultScope: ConcreteClassScopeDefault
        Console.WriteLine($"ScopeA: {parent.ScopeRootA.Dependency.GetType().Name}"); // ScopeA: ConcreteClassScopeA
        Console.WriteLine($"ScopeB: {parent.ScopeRootB.Dependency.GetType().Name}"); // ScopeB: ConcreteClassScopeB
        Console.WriteLine($"DefaultTransientScope: {parent.TransientScopeRootDefault.Dependency.GetType().Name}"); // DefaultTransientScope: ConcreteClassTransientScopeDefault
        Console.WriteLine($"TransientScopeA: {parent.TransientScopeRootA.Dependency.GetType().Name}"); // TransientScopeA: ConcreteClassTransientScopeA
        Console.WriteLine($"TransientScopeB: {parent.TransientScopeRootB.Dependency.GetType().Name}"); // TransientScopeB: ConcreteClassTransientScopeB
    }
}

Scoped Instances

Group of features concerned with scoped instances. That means instances which are guaranteed to be created only once per configured scope. 'Scope' includes the container itself as the biggest possible scope as well here.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Scoped Instances (Scope) 🔍
With this feature a type should be configurable to be instantiated once per scope and shared for all injections.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ScopedInstancesScope)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.ScopedInstances.Scope;

// This simple class will be shared per scope
internal class ConcreteClass { }

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Register the class as scoped
        builder.Services.AddScoped<ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        // The topmost resolved instances are equal
        var concreteClassA = host.Services.GetRequiredService<ConcreteClass>();
        var concreteClassB = host.Services.GetRequiredService<ConcreteClass>();
        Console.WriteLine(concreteClassA == concreteClassB); // True

        using var scope = host.Services.CreateScope();
        var concreteClassC = scope.ServiceProvider.GetRequiredService<ConcreteClass>();
        var concreteClassD = scope.ServiceProvider.GetRequiredService<ConcreteClass>();
            
        // The scope instances are equal, but not to the topmost instance
        Console.WriteLine(concreteClassC == concreteClassD); // True
        Console.WriteLine(concreteClassC == concreteClassA); // False

        using var scopeNested = host.Services.CreateScope();
        var concreteClassE = scopeNested.ServiceProvider.GetRequiredService<ConcreteClass>();
        var concreteClassF = scopeNested.ServiceProvider.GetRequiredService<ConcreteClass>();
        
        // The nested scope instances are equal, but not to the topmost or the other scope instance
        Console.WriteLine(concreteClassE == concreteClassF); // True
        Console.WriteLine(concreteClassE == concreteClassC); // False
        Console.WriteLine(concreteClassE == concreteClassA); // False
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ScopedInstancesScope)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.ScopedInstances.Scope;

// The name of these simple classes indicates their lifetime scope. That means, how broad they will be shared.
internal class ConcreteClassPerScope { }
internal class ConcreteClassPerTransientScope { }

// The scope root has one dependency per kind
internal class ScopeRoot
{
    internal ScopeRoot(
        ConcreteClassPerScope concreteClassPerScope,
        ConcreteClassPerTransientScope concreteClassPerTransientScope)
    {
        ConcreteClassPerScope = concreteClassPerScope;
        ConcreteClassPerTransientScope = concreteClassPerTransientScope;
    }

    internal ConcreteClassPerScope ConcreteClassPerScope { get; }
    internal ConcreteClassPerTransientScope ConcreteClassPerTransientScope { get; }
}

// The transient scope root has one dependency per kind as well and an own child scope
internal class TransientScopeRoot
{
    
    internal TransientScopeRoot(
        ConcreteClassPerScope concreteClassPerScope,
        ConcreteClassPerTransientScope concreteClassPerTransientScope,
        ScopeRoot scopeRoot)
    {
        ConcreteClassPerScope = concreteClassPerScope;
        ConcreteClassPerTransientScope = concreteClassPerTransientScope;
        ScopeRoot = scopeRoot;
    }

    internal ConcreteClassPerScope ConcreteClassPerScope { get; }
    internal ConcreteClassPerTransientScope ConcreteClassPerTransientScope { get; }
    public ScopeRoot ScopeRoot { get; }
}

// The Parent class is no (transient) scope root, but it has a dependency of each kind (including the scope roots)
// Therefore, it will be created from within the container itself.
// For scoped instances, the container also works as the topmost (transient) scope.
internal class Parent
{
    internal Parent(
        ConcreteClassPerScope concreteClassPerScope,
        ConcreteClassPerTransientScope concreteClassPerTransientScope,
        ScopeRoot scopeRoot, 
        TransientScopeRoot transientScopeRoot)
    {
        ConcreteClassPerScope = concreteClassPerScope;
        ConcreteClassPerTransientScope = concreteClassPerTransientScope;
        ScopeRoot = scopeRoot;
        TransientScopeRoot = transientScopeRoot;
    }

    public ConcreteClassPerScope ConcreteClassPerScope { get; }
    public ConcreteClassPerTransientScope ConcreteClassPerTransientScope { get; }
    internal ScopeRoot ScopeRoot { get; }
    internal TransientScopeRoot TransientScopeRoot { get; }
}


[ImplementationAggregation(typeof(Parent), typeof(ScopeRoot), typeof(TransientScopeRoot), typeof(ConcreteClassPerScope), typeof(ConcreteClassPerTransientScope))]
[ScopeRootImplementationAggregation(typeof(ScopeRoot))]
[TransientScopeRootImplementationAggregation(typeof(TransientScopeRoot))]
// Register ConcreteClassPerScope as a scoped instance.
// That means, that it will be created once per scope and will be shared within that scope.
[ScopeInstanceImplementationAggregation(typeof(ConcreteClassPerScope))]
// Register ConcreteClassPerTransientScope as a transient scoped instance.
// That means, that it will be created once per transient scope and will be shared within that transient scope and its ordinary child scopes transitively.
[TransientScopeInstanceImplementationAggregation(typeof(ConcreteClassPerTransientScope))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        
        // Because a ConcreteClassPerScope is shared per scope, the container's instance is unequal to the other scope's instances
        Console.WriteLine(parent.ConcreteClassPerScope == parent.ScopeRoot.ConcreteClassPerScope); // False
        Console.WriteLine(parent.ConcreteClassPerScope == parent.TransientScopeRoot.ConcreteClassPerScope); // False
        Console.WriteLine(parent.ConcreteClassPerScope == parent.TransientScopeRoot.ScopeRoot.ConcreteClassPerScope); // False
        
        // Same follows for the scope's instance
        Console.WriteLine(parent.ScopeRoot.ConcreteClassPerScope == parent.TransientScopeRoot.ConcreteClassPerScope); // False
        Console.WriteLine(parent.ScopeRoot.ConcreteClassPerScope == parent.TransientScopeRoot.ScopeRoot.ConcreteClassPerScope); // False
        
        // And the transient scope's instance
        Console.WriteLine(parent.TransientScopeRoot.ConcreteClassPerScope == parent.TransientScopeRoot.ScopeRoot.ConcreteClassPerScope); // False
        
        // Because a ConcreteClassPerTransientScope is shared per transient scope, the container's instance is equal to the instance of its child scope
        // But not equal to the instances of the container's transient scope
        Console.WriteLine(parent.ConcreteClassPerTransientScope == parent.ScopeRoot.ConcreteClassPerTransientScope); // True
        Console.WriteLine(parent.ConcreteClassPerTransientScope == parent.TransientScopeRoot.ConcreteClassPerTransientScope); // False
        Console.WriteLine(parent.ConcreteClassPerTransientScope == parent.TransientScopeRoot.ScopeRoot.ConcreteClassPerTransientScope); // False
        
        // Consequently, the scope's instance is not equal to the transient scope's instance
        Console.WriteLine(parent.ScopeRoot.ConcreteClassPerTransientScope == parent.TransientScopeRoot.ConcreteClassPerTransientScope); // False
        Console.WriteLine(parent.ScopeRoot.ConcreteClassPerTransientScope == parent.TransientScopeRoot.ScopeRoot.ConcreteClassPerTransientScope); // False
        
        // But the transient scope's instance is equal to the instance of its child scope
        Console.WriteLine(parent.TransientScopeRoot.ConcreteClassPerTransientScope == parent.TransientScopeRoot.ScopeRoot.ConcreteClassPerTransientScope); // True
    }
}
Scoped Instances (Container) 🔍
With this feature a type should be configurable to be instantiated once for the whole container and shared for all injections. This is also commonly known as singleton or 'single instance'.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.ScopedInstancesContainer)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.ScopedInstances.Container;

// Simple class that will be created once per container
internal class ConcreteClass { }

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Register the class as singleton
        builder.Services.AddSingleton<ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        // All resolved instances are equal
        var concreteClassA = host.Services.GetRequiredService<ConcreteClass>();
        var concreteClassB = host.Services.GetRequiredService<ConcreteClass>();
        Console.WriteLine(concreteClassA == concreteClassB); // True

        using var scope = host.Services.CreateScope();
        var concreteClassC = scope.ServiceProvider.GetRequiredService<ConcreteClass>();
        var concreteClassD = scope.ServiceProvider.GetRequiredService<ConcreteClass>();
            
        Console.WriteLine(concreteClassC == concreteClassD); // True
        Console.WriteLine(concreteClassC == concreteClassA); // True

        using var scopeNested = host.Services.CreateScope();
        var concreteClassE = scopeNested.ServiceProvider.GetRequiredService<ConcreteClass>();
        var concreteClassF = scopeNested.ServiceProvider.GetRequiredService<ConcreteClass>();
                
        Console.WriteLine(concreteClassE == concreteClassF); // True
        Console.WriteLine(concreteClassE == concreteClassC); // True
        Console.WriteLine(concreteClassE == concreteClassA); // True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.ScopedInstancesContainer)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.ScopedInstances.Container;

// Simple class that will be created once per container
internal class ConcreteClassPerContainer { }

internal class ScopeRoot
{
    internal ScopeRoot(ConcreteClassPerContainer concreteClassPerContainer) =>
        ConcreteClassPerContainer = concreteClassPerContainer;
    
    internal ConcreteClassPerContainer ConcreteClassPerContainer { get; }
}

// We'll build a similar scope structure as in the "Scope" sample
internal class TransientScopeRoot
{
    internal TransientScopeRoot(
        ConcreteClassPerContainer concreteClassPerContainer,
        ScopeRoot scopeRoot)
    {
        ConcreteClassPerContainer = concreteClassPerContainer;
        ScopeRoot = scopeRoot;
    }

    internal ConcreteClassPerContainer ConcreteClassPerContainer { get; }
    public ScopeRoot ScopeRoot { get; }
}

internal class Parent
{
    internal Parent(
        ConcreteClassPerContainer concreteClassPerContainer,
        ScopeRoot scopeRoot, 
        TransientScopeRoot transientScopeRoot)
    {
        ConcreteClassPerContainer = concreteClassPerContainer;
        ScopeRoot = scopeRoot;
        TransientScopeRoot = transientScopeRoot;
    }

    internal ConcreteClassPerContainer ConcreteClassPerContainer { get; }
    internal ScopeRoot ScopeRoot { get; }
    internal TransientScopeRoot TransientScopeRoot { get; }
}


[ImplementationAggregation(typeof(Parent), typeof(ScopeRoot), typeof(TransientScopeRoot), typeof(ConcreteClassPerContainer))]
[ScopeRootImplementationAggregation(typeof(ScopeRoot))]
[TransientScopeRootImplementationAggregation(typeof(TransientScopeRoot))]
// Register ConcreteClassPerContainer as a container instance (otherwise known as "single instance" or "singleton").
// That means, that it will be created once per container and will be shared for each injection of that type.
[ContainerInstanceImplementationAggregation(typeof(ConcreteClassPerContainer))]
[CreateFunction(typeof(Parent), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        var container = Container.DIE_CreateContainer();
        var parent = container.Create();
        
        // All dependencies are equal to each other
        Console.WriteLine(parent.ConcreteClassPerContainer == parent.ScopeRoot.ConcreteClassPerContainer); // True
        Console.WriteLine(parent.ConcreteClassPerContainer == parent.TransientScopeRoot.ConcreteClassPerContainer); // True
        Console.WriteLine(parent.ConcreteClassPerContainer == parent.TransientScopeRoot.ScopeRoot.ConcreteClassPerContainer); // True
        
        Console.WriteLine(parent.ScopeRoot.ConcreteClassPerContainer == parent.TransientScopeRoot.ConcreteClassPerContainer); // True
        Console.WriteLine(parent.ScopeRoot.ConcreteClassPerContainer == parent.TransientScopeRoot.ScopeRoot.ConcreteClassPerContainer); // True
        
        Console.WriteLine(parent.TransientScopeRoot.ConcreteClassPerContainer == parent.TransientScopeRoot.ScopeRoot.ConcreteClassPerContainer); // True
    }
}

Disposal

Group of features concerned with disposal of containers & other scopes and disposable resolved dependencies.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
I Disposable 🔍
The container can manage the disposal of dependencies implementing IDisposable.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.DisposalIDisposable)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Disposal.IDisposable;

// This simple class is disposable
internal class ConcreteClass : System.IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        var host = Builder.CreateBuilder().Build();

        var concreteClass = host.Services.GetRequiredService<ConcreteClass>();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: False
        // As soon as the container is disposed, all its managed disposable dependencies are disposed as well.
        host.Dispose();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DisposalIDisposable)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Disposal.IDisposable;

// This simple class is disposable
internal class ConcreteClass : System.IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

[ImplementationAggregation(typeof(ConcreteClass))]
// The container manages the disposal of IDisposable instances per default.
// Therefore, there is no need to configure the container to do so.
[CreateFunction(typeof(ConcreteClass), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        var container = Container.DIE_CreateContainer();
        var concreteClass = container.Create();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: False
        // As soon as the container is disposed, all its managed disposable dependencies are disposed as well.
        container.Dispose();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: True
    }
}
I Async Disposable 🔍
The container can manage the disposal of dependencies implementing IAsyncDisposable.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.DisposalIAsyncDisposable)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Disposal.IAsyncDisposable;

// This simple class is asynchronously disposable
internal class ConcreteClass : System.IAsyncDisposable
{
    internal bool Disposed { get; private set; }
    public ValueTask DisposeAsync()
    {
        Disposed = true;
        return ValueTask.CompletedTask;
    }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<ConcreteClass>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        var host = Builder.CreateBuilder().Build();

        var concreteClass = host.Services.GetRequiredService<ConcreteClass>();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: False
        // As soon as the container is disposed, all its managed disposable dependencies are disposed as well.
        host.Dispose();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DisposalIAsyncDisposable)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Disposal.IAsyncDisposable;

// This simple class is asynchronously disposable
internal class ConcreteClass : System.IAsyncDisposable
{
    internal bool Disposed { get; private set; }
    public ValueTask DisposeAsync()
    {
        Disposed = true;
        return ValueTask.CompletedTask;
    }
}

[ImplementationAggregation(typeof(ConcreteClass))]
// The container manages the disposal of IAsyncDisposable instances per default.
// Therefore, there is no need to configure the container to do so.
[CreateFunction(typeof(ConcreteClass), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async ValueTask Use()
    {
        var container = Container.DIE_CreateContainer();
        var concreteClass = container.Create();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: False
        // As soon as the container is disposed, all its managed disposable dependencies are disposed as well.
        // Notice that the container has only the asynchronous DisposeAsync method, but no synchronous Dispose method.
        // If the container manages at least one IAsyncDisposable dependency, it will only have the asynchronous DisposeAsync method.
        await container.DisposeAsync();
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: True
    }
}
Transient IDisposable 🔍
The container can hand over responsibility to manage the disposal of dependencies implementing IDisposable to the user.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DisposalTransient)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Disposal.Transient;

// Following two simple classes are disposable
internal class ConcreteClassSync : System.IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

internal class ConcreteClass : System.IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

[ImplementationAggregation(typeof(ConcreteClassSync), typeof(ConcreteClass))]
// In order to prevent the container from managing the disposal of IDisposable instances, implementation types can be declared as transient.
// Following attribute corresponds only to IDisposable instances. That means if a type would implement both IDisposable and IAsyncDisposable, the container would still manage the disposal but only with IAsyncDisposable.DisposeAsync().
[SyncTransientImplementationAggregation(typeof(ConcreteClassSync))]
// Following attribute corresponds to both IDisposable and IAsyncDisposable instances. That means independent of which disposable interfaces are implemented by the type, the container won't manage the disposal for it.
[TransientImplementationAggregation(typeof(ConcreteClass))]
[CreateFunction(typeof(ConcreteClassSync), "CreateSync")]
[CreateFunction(typeof(ConcreteClass), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        var container = Container.DIE_CreateContainer();
        var concreteClassSync = container.CreateSync();
        var concreteClass = container.Create();
        Console.WriteLine($"Disposed: {concreteClassSync.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: False
        // Disposing the container won't dispose the disposable dependencies.
        container.Dispose();
        Console.WriteLine($"Disposed: {concreteClassSync.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: False
        concreteClassSync.Dispose();
        concreteClass.Dispose();
        Console.WriteLine($"Disposed: {concreteClassSync.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: True
    }
}
Transient IAsyncDisposal 🔍
The container can hand over responsibility to manage the disposal of dependencies implementing IAsyncDisposable to the user.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DisposalAsyncTransient)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Disposal.AsyncTransient;

// Following two simple classes are disposable
internal class ConcreteClassAsync : System.IAsyncDisposable
{
    internal bool Disposed { get; private set; }
    public ValueTask DisposeAsync()
    {
        Disposed = true;
        return ValueTask.CompletedTask;
    }
}

internal class ConcreteClass : System.IAsyncDisposable
{
    internal bool Disposed { get; private set; }
    public ValueTask DisposeAsync()
    {
        Disposed = true;
        return ValueTask.CompletedTask;
    }
}

[ImplementationAggregation(typeof(ConcreteClassAsync), typeof(ConcreteClass))]
// In order to prevent the container from managing the disposal of IAsyncDisposable instances, implementation types can be declared as transient.
// Following attribute corresponds only to IAsyncDisposable instances. That means if a type would implement both IDisposable and IAsyncDisposable, the container would still manage the disposal but only with IDisposable.Dispose().
[AsyncTransientImplementationAggregation(typeof(ConcreteClassAsync))]
// Following attribute corresponds to both IDisposable and IAsyncDisposable instances. That means independent of which disposable interfaces are implemented by the type, the container won't manage the disposal for it.
[TransientImplementationAggregation(typeof(ConcreteClass))]
[CreateFunction(typeof(ConcreteClassAsync), "AsyncCreate")]
[CreateFunction(typeof(ConcreteClass), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async ValueTask Use()
    {
        var container = Container.DIE_CreateContainer();
        var concreteClassAsync = container.AsyncCreate();
        var concreteClass = container.Create();
        Console.WriteLine($"Disposed: {concreteClassAsync.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: False
        // Disposing the container won't dispose the disposable dependencies.
        await container.DisposeAsync();
        Console.WriteLine($"Disposed: {concreteClassAsync.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: False
        await concreteClassAsync.DisposeAsync();
        await concreteClass.DisposeAsync();
        Console.WriteLine($"Disposed: {concreteClassAsync.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {concreteClass.Disposed}"); // Disposed: True
    }
}
Scope Disposal 🔍
Transient scopes can be disposed eagerly (before the parent container is disposed).
using ContainerFeatureSampleComparison.FeatureDefinitions;
using ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Disposal.IAsyncDisposable;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.DisposalScope)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Disposal.Scope;

// Two simple classes that are either synchronously or asynchronously disposable
internal class ConcreteClassSync : System.IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

internal class ConcreteClassAsync : System.IAsyncDisposable
{
    internal bool Disposed { get; private set; }
    public ValueTask DisposeAsync()
    {
        Disposed = true;
        return ValueTask.CompletedTask;
    }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        builder.Services.AddTransient<ConcreteClass>();
        builder.Services.AddTransient<ConcreteClassAsync>();
        
        return builder;
    }
}

internal static class Usage
{
    internal static async ValueTask Use()
    {
        using var host = IAsyncDisposable.Builder.CreateBuilder().Build();

        var syncScope = host.Services.CreateScope();
        var syncConcreteClass = syncScope.ServiceProvider.GetRequiredService<ConcreteClassSync>();
        
        Console.WriteLine($"Disposed: {syncConcreteClass.Disposed}"); // Disposed: False
        // As soon as the scope is disposed, all its managed disposable dependencies are disposed as well.
        syncScope.Dispose();
        Console.WriteLine($"Disposed: {syncConcreteClass.Disposed}"); // Disposed: True

        var asyncScope = host.Services.CreateAsyncScope();
        var asyncConcreteClass = syncScope.ServiceProvider.GetRequiredService<ConcreteClassAsync>();
        
        Console.WriteLine($"Disposed: {asyncConcreteClass.Disposed}"); // Disposed: False
        // As soon as the scope is disposed, all its managed disposable dependencies are disposed as well.
        await asyncScope.DisposeAsync();
        Console.WriteLine($"Disposed: {asyncConcreteClass.Disposed}"); // Disposed: True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DisposalScope)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Disposal.Scope;

// Some simple disposable classes for which the disposal will be either managed by the container/scope or not.
internal class ConcreteClassSync : System.IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

internal class ConcreteClassAsync : System.IAsyncDisposable
{
    internal bool Disposed { get; private set; }
    public ValueTask DisposeAsync()
    {
        Disposed = true;
        return ValueTask.CompletedTask;
    }
}

internal class ConcreteClassSyncTransient : System.IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

internal class ConcreteClassAsyncTransient : System.IAsyncDisposable
{
    internal bool Disposed { get; private set; }
    public ValueTask DisposeAsync()
    {
        Disposed = true;
        return ValueTask.CompletedTask;
    }
}

// Inject them all into the transient scope root.
internal class TransientScopeRoot
{
    internal TransientScopeRoot(
        ConcreteClassSync concreteClassSync,
        ConcreteClassSyncTransient concreteClassSyncTransient,
        ConcreteClassAsync concreteClassAsync,
        ConcreteClassAsyncTransient concreteClassAsyncTransient,
        System.IAsyncDisposable transientScopeDisposalHandle)
    {
        ConcreteClassSync = concreteClassSync;
        ConcreteClassSyncTransient = concreteClassSyncTransient;
        ConcreteClassAsync = concreteClassAsync;
        ConcreteClassAsyncTransient = concreteClassAsyncTransient;
        TransientScopeDisposalHandle = transientScopeDisposalHandle;
    }
    
    internal ConcreteClassSync ConcreteClassSync { get; }
    internal ConcreteClassSyncTransient ConcreteClassSyncTransient { get; }
    internal ConcreteClassAsync ConcreteClassAsync { get; }
    internal ConcreteClassAsyncTransient ConcreteClassAsyncTransient { get; }
    internal System.IAsyncDisposable TransientScopeDisposalHandle { get; }
}

[ImplementationAggregation(typeof(TransientScopeRoot), typeof(ConcreteClassSync), typeof(ConcreteClassSyncTransient), typeof(ConcreteClassAsync), typeof(ConcreteClassAsyncTransient))]
// Make half of the transient/unmanaged
[TransientScopeRootImplementationAggregation(typeof(TransientScopeRoot))]
[TransientImplementationAggregation(typeof(ConcreteClassSyncTransient), typeof(ConcreteClassAsyncTransient))]
[CreateFunction(typeof(TransientScopeRoot), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static async ValueTask Use()
    {
        await using var container = Container.DIE_CreateContainer();
        var transientScopeRoot = container.Create();
        
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassSync.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassSyncTransient.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassAsync.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassAsyncTransient.Disposed}"); // Disposed: False

        // Eagerly dispose the transient scope root
        await transientScopeRoot.TransientScopeDisposalHandle.DisposeAsync().ConfigureAwait(false);
        
        // The managed instances are disposed
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassSync.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassSyncTransient.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassAsync.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassAsyncTransient.Disposed}"); // Disposed: False
        
        // Just because it is good manners to dispose everything ;)
        transientScopeRoot.ConcreteClassSyncTransient.Dispose();
        await transientScopeRoot.ConcreteClassAsyncTransient.DisposeAsync();
        
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassSync.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassSyncTransient.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassAsync.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {transientScopeRoot.ConcreteClassAsyncTransient.Disposed}"); // Disposed: True
    }
}
Add For Disposal 🔍
The container (or the scope) offers a way to add externally created instances to the disposal. That can become handy for disposable instances which are created via custom factories.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DisposalAddForDisposal)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Disposal.AddForDisposal;

// Some simple disposable classes for which the disposal will be either managed by the container/scope or not.
internal class ConcreteClassSync : System.IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

internal class ConcreteClassAsync : System.IAsyncDisposable
{
    internal bool Disposed { get; private set; }
    public ValueTask DisposeAsync()
    {
        Disposed = true;
        return ValueTask.CompletedTask;
    }
}

internal class ConcreteClassSyncTransient : System.IDisposable
{
    internal bool Disposed { get; private set; }
    public void Dispose() => Disposed = true;
}

internal class ConcreteClassAsyncTransient : System.IAsyncDisposable
{
    internal bool Disposed { get; private set; }
    public ValueTask DisposeAsync()
    {
        Disposed = true;
        return ValueTask.CompletedTask;
    }
}

internal class TransientScopeRoot
{
    internal TransientScopeRoot(
        ConcreteClassSyncTransient concreteClassSyncTransient,
        ConcreteClassAsyncTransient concreteClassAsyncTransient,
        System.IAsyncDisposable transientScopeDisposalHandle)
    {
        ConcreteClassSyncTransient = concreteClassSyncTransient;
        ConcreteClassAsyncTransient = concreteClassAsyncTransient;
        TransientScopeDisposalHandle = transientScopeDisposalHandle;
    }
    internal ConcreteClassSyncTransient ConcreteClassSyncTransient { get; }
    internal ConcreteClassAsyncTransient ConcreteClassAsyncTransient { get; }
    internal System.IAsyncDisposable TransientScopeDisposalHandle { get; }
}

internal class Root
{
    internal Root(
        ConcreteClassSync concreteClassSync,
        ConcreteClassAsync concreteClassAsync,
        TransientScopeRoot transientScopeRoot)
    {
        ConcreteClassSync = concreteClassSync;
        ConcreteClassAsync = concreteClassAsync;
        TransientScopeRoot = transientScopeRoot;
    }
    internal ConcreteClassSync ConcreteClassSync { get; }
    internal ConcreteClassAsync ConcreteClassAsync { get; }
    internal TransientScopeRoot TransientScopeRoot { get; }
}

[ImplementationAggregation(typeof(TransientScopeRoot), typeof(Root))]
[TransientScopeRootImplementationAggregation(typeof(TransientScopeRoot))]
[CreateFunction(typeof(Root), "Create")]
internal partial class Container
{
    private Container() {}
    
    // Prepare partial function signature for a generated function that'll append sync disposables for the container to manage.
    private partial void DIE_AddForDisposal(System.IDisposable disposable);
    // Prepare partial function signature for a generated function that'll append async disposables for the container to manage.
    private partial void DIE_AddForDisposalAsync(System.IAsyncDisposable asyncDisposable);
    
    private ConcreteClassSync DIE_Factory_ConcreteClassSync()
    {
        // This instance isn't created by the container itself but we would like to get it disposed with the container.
        var instance = new ConcreteClassSync();
        // So we add it to the container's disposal list.
        DIE_AddForDisposal(instance);
        return instance;
    }

    private ConcreteClassAsync DIE_Factory_ConcreteClassAsync()
    {
        // This instance isn't created by the container itself but we would like to get it disposed with the container.
        var instance = new ConcreteClassAsync();
        // So we add it to the container's disposal list.
        DIE_AddForDisposalAsync(instance);
        return instance;
    }

    private partial class DIE_DefaultTransientScope
    {
        // Prepare partial function signature for a generated function that'll append sync disposables for the transient scope to manage.
        private partial void DIE_AddForDisposal(System.IDisposable disposable);
        // Prepare partial function signature for a generated function that'll append async disposables for the transient scope to manage.
        private partial void DIE_AddForDisposalAsync(System.IAsyncDisposable asyncDisposable);
    
        private ConcreteClassSyncTransient DIE_Factory_ConcreteClassSyncTransient()
        {
            // This instance isn't created by the transient scope itself but we would like to get it disposed with the transient scope.
            var instance = new ConcreteClassSyncTransient();
            // So we add it to the transient scope's disposal list.
            DIE_AddForDisposal(instance);
            return instance;
        }

        private ConcreteClassAsyncTransient DIE_Factory_ConcreteClassAsyncTransient()
        {
            // This instance isn't created by the transient scope itself but we would like to get it disposed with the transient scope.
            var instance = new ConcreteClassAsyncTransient();
            // So we add it to the transient scope's disposal list.
            DIE_AddForDisposalAsync(instance);
            return instance;
        }
    }
}

internal static class Usage
{
    internal static async ValueTask Use()
    {
        var container = Container.DIE_CreateContainer();
        var root = container.Create();
        
        Console.WriteLine($"Disposed: {root.ConcreteClassSync.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {root.TransientScopeRoot.ConcreteClassSyncTransient.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {root.ConcreteClassAsync.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {root.TransientScopeRoot.ConcreteClassAsyncTransient.Disposed}"); // Disposed: False

        // Eagerly dispose the transient scope root
        await root.TransientScopeRoot.TransientScopeDisposalHandle.DisposeAsync().ConfigureAwait(false);
        
        // The instances from the transient scope root are disposed
        Console.WriteLine($"Disposed: {root.ConcreteClassSync.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {root.TransientScopeRoot.ConcreteClassSyncTransient.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {root.ConcreteClassAsync.Disposed}"); // Disposed: False
        Console.WriteLine($"Disposed: {root.TransientScopeRoot.ConcreteClassAsyncTransient.Disposed}"); // Disposed: True

        await container.DisposeAsync();
        
        Console.WriteLine($"Disposed: {root.ConcreteClassSync.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {root.TransientScopeRoot.ConcreteClassSyncTransient.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {root.ConcreteClassAsync.Disposed}"); // Disposed: True
        Console.WriteLine($"Disposed: {root.TransientScopeRoot.ConcreteClassAsyncTransient.Disposed}"); // Disposed: True
    }
}

Customization

Group of features concerned with customization of containers & other scopes.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Custom Factory 🔍
As part of the configuration of the container and/or the scopes the user has the option to embed own custom factory function which will be used for the returned type.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.CustomizationCustomFactory)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Customization.CustomFactory;

// This class won't be created by the container but by a custom factory.
internal class Person
{
    internal Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    internal string Name { get; }
    public int Age { get; }
}

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        // Use the factory parameter in order to customize the creation of the instance.
        builder.Services.AddTransient<Person>(_ => new Person("Jane Doe", 42));
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var person = host.Services.GetRequiredService<Person>();
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // Name: Jane Doe, Age: 42
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.CustomizationCustomFactory)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Customization.CustomFactory;

// This class won't be created by the container but by a custom factory.
internal class Person
{
    internal Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    internal string Name { get; }
    public int Age { get; }
}

// This class will be created by the container but its dependencies will be created by custom factories.
internal class RandomData
{
    internal RandomData(decimal randomDecimal, char randomChar)
    {
        RandomDecimal = randomDecimal;
        RandomChar = randomChar;
    }

    public char RandomChar { get; }

    public decimal RandomDecimal { get; }
}

[ImplementationAggregation(typeof(RandomData))]
[CreateFunction(typeof(Person), "Create")]
[CreateFunction(typeof(RandomData), "CreateRandomData")]
internal partial class Container
{
    // Custom factories are members of the container class. Their name must start with "DIE_Factory".
    // They can be fields, …
    private int DIE_Factory_Age = 42;
    
    private Container() {}
    
    // … properties with a get-accessor …
    private string DIE_Factory_Name => "Jane Doe";
    
    // … and functions.
    // The function variant can have dependencies itself, that it can use to create the instance.
    private Person DIE_Factory_ConcreteClass(int age, string name) => new(name, age);
    
    // Factories can also be async by wrapping the return type in a Task<T> …
    private async Task<decimal> DIE_Factory_RandomDecimal()
    {
        await Task.Yield();
        return 42.42m;
    }
    
    // … or a ValueTask<T>.
    private ValueTask<char> DIE_Factory_RandomChar => new('a');
}

internal static class Usage
{
    internal static async ValueTask Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // For creation of following instances the container had to use the custom factories.
        var person = container.Create();
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // Name: Jane Doe, Age: 42
        var randomData = await container.CreateRandomData().ConfigureAwait(false);
        Console.WriteLine($"RandomDecimal: {randomData.RandomDecimal}, RandomChar: {randomData.RandomChar}"); // RandomDecimal: 42.42, RandomChar: a
    }
}
Custom Constructor Parameter 🔍
Configurable customization of injection of specific constructor parameters.
Microsoft.Extensions.DependencyInjection does not support "injection based on name": https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#default-service-container-replacement
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.CustomizationCustomConstructorParameterInjection)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Customization.CustomConstructorParameterInjection;

// The dependencies of this class will be custom constructor parameter injections.
internal class Person
{
    internal Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    internal string Name { get; }
    public int Age { get; }
}

[ImplementationAggregation(typeof(Person))]
[CreateFunction(typeof(Person), "Create")]
internal partial class Container
{
    private Container() {}
    
    // Just a helpful custom factory.
    private byte DIE_Factory_AgeHalf => 21;
    
    // A method which has the name started with "DIE_ConstrParams" is needed for custom constructor parameter injections.
    // Each constructor parameter which should be injected in a custom way has to have an out-parameter with matching type and name of the original constructor parameter.
    // Also the method requires following attribute in order to assign it to an implementation type.
    [UserDefinedConstructorParametersInjection(typeof(Person))]
    private void DIE_ConstrParams_Person(byte ageHalf, out string name, out int age)
    {
        // Set the out-parameters to the values you want to inject.
        name = "Jane Doe";
        age = ageHalf * 2;
    }
}

internal static class Usage
{
    internal static async ValueTask Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // The dependencies of the following instance will be injected by the custom constructor parameter injection.
        var person = container.Create();
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // Name: Jane Doe, Age: 42
    }
}
Custom Property 🔍
Configurable customization of injection of specific properties.
Microsoft.Extensions.DependencyInjection does not support property injection: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#default-service-container-replacement
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.CustomizationCustomPropertyInjection)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Customization.CustomPropertyInjection;

// The dependencies of this class will be custom property injections.
internal class Person
{
    internal string Name { get; init; } = "";
    public int Age { get; init; }
}

[ImplementationAggregation(typeof(Person))]
[CreateFunction(typeof(Person), "Create")]
internal partial class Container
{
    private Container() {}
    
    // Just a helpful custom factory.
    private byte DIE_Factory_AgeHalf => 21;
    
    // A method which has the name started with "DIE_Props" is needed for custom property injections.
    // Each property which should be injected in a custom way has to have an out-parameter with matching type and name of the original property.
    // Also the method requires following attribute in order to assign it to an implementation type.
    [UserDefinedPropertiesInjection(typeof(Person))]
    private void DIE_Props_Person(byte ageHalf, out string Name, out int Age)
    {
        // Set the out-parameters to the values you want to inject.
        Name = "Jane Doe";
        Age = ageHalf * 2;
    }
}

internal static class Usage
{
    internal static async ValueTask Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // The dependencies of the following instance will be injected by the custom constructor parameter injection.
        var person = container.Create();
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // Name: Jane Doe, Age: 42
    }
}
Custom Type Initializer Parameter 🔍
Configurable customization of injection of specific type initializer parameters.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.CustomizationCustomTypeInitializerInjection)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Customization.CustomCustomTypeInitializerInjection;

// The dependencies of this class will be custom type initializer parameter injections.
internal class Person
{
    internal void Initialize(string name, int age)
    {
        Name = name;
        Age = age;
    }
    
    internal string Name { get; private set; } = "";
    public int Age { get; private set; }
}

[ImplementationAggregation(typeof(Person))]
[Initializer(typeof(Person), nameof(Person.Initialize))]
[CreateFunction(typeof(Person), "Create")]
internal partial class Container
{
    private Container() {}
    
    // Just a helpful custom factory.
    private byte DIE_Factory_AgeHalf => 21;
    
    // A method which has the name started with "DIE_InitParams" is needed for custom type initializer parameter injections.
    // Each type initializer parameter which should be injected in a custom way has to have an out-parameter with matching type and name of the original type initializer parameter.
    // Also the method requires following attribute in order to assign it to an implementation type.
    [UserDefinedInitializerParametersInjection(typeof(Person))]
    private void DIE_InitParams_Person(byte ageHalf, out string name, out int age)
    {
        // Set the out-parameters to the values you want to inject.
        name = "Jane Doe";
        age = ageHalf * 2;
    }
}

internal static class Usage
{
    internal static async ValueTask Use()
    {
        await using var container = Container.DIE_CreateContainer();
        // The dependencies of the following instance will be injected by the custom type initializer parameter injection.
        var person = container.Create();
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // Name: Jane Doe, Age: 42
    }
}

Decorator Pattern

Group of features concerned with the Decorator pattern.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Single Decorator 🔍
A single Decorator can be specified for an interface type.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DecoratorPatternSingleDecorator)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.DecoratorPattern.SingleDecorator;

// This is the interface which will be decorated.
internal interface IInterface
{
    IInterface Decorated { get; }
}

// This is the implementation that will be decorated.
internal class DecoratedImplementation : IInterface
{
    public IInterface Decorated => this;
}

// This interface marks a decorator. Its generic parameter should be the decorated interface.
internal interface IDecorator<T> {}

// This is the decorator. It must implement the decorated interface and the decorator interface.
internal class Decorator : IInterface, IDecorator<IInterface>
{
    // Also it can have a dependency of the decorated interface. The decorated implementation instance or another decorator will be injected here.
    internal Decorator(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

[ImplementationAggregation(typeof(DecoratedImplementation), typeof(Decorator))]
// We need to specify the decorator interface.
[DecoratorAbstractionAggregation(typeof(IDecorator<>))]
[CreateFunction(typeof(IInterface), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var instance = container.Create();
        // The topmost instance is the decorator.
        Console.WriteLine(instance is Decorator); // True
        // The decorated instance is nested.
        Console.WriteLine(instance.Decorated is DecoratedImplementation); // True
    }
}
Multiple Decorator 🔍
Multiple Decorators can be specified for an interface type.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DecoratorPatternMultipleDecorators)]
[assembly:FeatureSample(Feature.DecoratorPatternExplicitOrder)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.DecoratorPattern.MultipleDecorators;

// This is the interface which will be decorated.
internal interface IInterface
{
    IInterface Decorated { get; }
}

// This is the implementation that will be decorated.
internal class DecoratedImplementation : IInterface
{
    public IInterface Decorated => this;
}

// This interface marks a decorator. Its generic parameter should be the decorated interface.
internal interface IDecorator<T> {}

// This time we define multiple decorators.
internal class DecoratorA : IInterface, IDecorator<IInterface>
{
    internal DecoratorA(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

internal class DecoratorB : IInterface, IDecorator<IInterface>
{
    internal DecoratorB(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

internal class DecoratorC : IInterface, IDecorator<IInterface>
{
    internal DecoratorC(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

[ImplementationAggregation(typeof(DecoratedImplementation), typeof(DecoratorA), typeof(DecoratorB), typeof(DecoratorC))]
// We need to specify the decorator interface.
[DecoratorAbstractionAggregation(typeof(IDecorator<>))]
// We define an explicit order for the decorators with the following attribute.
// First parameter should be the decorated interface. The second parameter should be the type for which we want to set a decoration order.
// Then a list of decorators sorted from innermost to outermost decorator.
[DecoratorSequenceChoice(typeof(IInterface), typeof(DecoratedImplementation), typeof(DecoratorA), typeof(DecoratorB), typeof(DecoratorC))]
[CreateFunction(typeof(IInterface), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var instance = container.Create();
        // Checking the decoration order
        Console.WriteLine(instance is DecoratorC); // True
        Console.WriteLine(instance.Decorated is DecoratorB); // True
        Console.WriteLine(instance.Decorated.Decorated is DecoratorA); // True
        Console.WriteLine(instance.Decorated.Decorated.Decorated is DecoratedImplementation); // True
    }
}
Explicit Order 🔍
If several Decorators are configured for an interface, then an explicit order can be configured. That way the user can make sure that one Decoration logic is executed before another.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DecoratorPatternMultipleDecorators)]
[assembly:FeatureSample(Feature.DecoratorPatternExplicitOrder)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.DecoratorPattern.MultipleDecorators;

// This is the interface which will be decorated.
internal interface IInterface
{
    IInterface Decorated { get; }
}

// This is the implementation that will be decorated.
internal class DecoratedImplementation : IInterface
{
    public IInterface Decorated => this;
}

// This interface marks a decorator. Its generic parameter should be the decorated interface.
internal interface IDecorator<T> {}

// This time we define multiple decorators.
internal class DecoratorA : IInterface, IDecorator<IInterface>
{
    internal DecoratorA(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

internal class DecoratorB : IInterface, IDecorator<IInterface>
{
    internal DecoratorB(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

internal class DecoratorC : IInterface, IDecorator<IInterface>
{
    internal DecoratorC(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

[ImplementationAggregation(typeof(DecoratedImplementation), typeof(DecoratorA), typeof(DecoratorB), typeof(DecoratorC))]
// We need to specify the decorator interface.
[DecoratorAbstractionAggregation(typeof(IDecorator<>))]
// We define an explicit order for the decorators with the following attribute.
// First parameter should be the decorated interface. The second parameter should be the type for which we want to set a decoration order.
// Then a list of decorators sorted from innermost to outermost decorator.
[DecoratorSequenceChoice(typeof(IInterface), typeof(DecoratedImplementation), typeof(DecoratorA), typeof(DecoratorB), typeof(DecoratorC))]
[CreateFunction(typeof(IInterface), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var instance = container.Create();
        // Checking the decoration order
        Console.WriteLine(instance is DecoratorC); // True
        Console.WriteLine(instance.Decorated is DecoratorB); // True
        Console.WriteLine(instance.Decorated.Decorated is DecoratorA); // True
        Console.WriteLine(instance.Decorated.Decorated.Decorated is DecoratedImplementation); // True
    }
}
Different Order per Type 🔍
If several Decorators and implementation types are configured for an interface, then an explicit order can be configured per implementation type.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DecoratorPatternDifferentOrderPerType)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.DecoratorPattern.DifferentOrderPerType;

// This is the interface which will be decorated.
internal interface IInterface
{
    IInterface Decorated { get; }
}

// This time we'll have multiple decorated implementations.
internal class DecoratedImplementationA : IInterface
{
    public IInterface Decorated => this;
}

internal class DecoratedImplementationB : IInterface
{
    public IInterface Decorated => this;
}

internal class DecoratedImplementationC : IInterface
{
    public IInterface Decorated => this;
}

// This interface marks a decorator. Its generic parameter should be the decorated interface.
internal interface IDecorator<T> {}

// This time we also define multiple decorators.
internal class DecoratorA : IInterface, IDecorator<IInterface>
{
    internal DecoratorA(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

internal class DecoratorB : IInterface, IDecorator<IInterface>
{
    internal DecoratorB(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

internal class DecoratorC : IInterface, IDecorator<IInterface>
{
    internal DecoratorC(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

[ImplementationAggregation(typeof(DecoratedImplementationA), typeof(DecoratedImplementationB), typeof(DecoratedImplementationC), typeof(DecoratorA), typeof(DecoratorB), typeof(DecoratorC))]
// We need to specify the decorator interface.
[DecoratorAbstractionAggregation(typeof(IDecorator<>))]
// For each decorated implementation we define a decorator sequence.
[DecoratorSequenceChoice(typeof(IInterface), typeof(DecoratedImplementationA), typeof(DecoratorA), typeof(DecoratorB), typeof(DecoratorC))]
[DecoratorSequenceChoice(typeof(IInterface), typeof(DecoratedImplementationB), typeof(DecoratorC), typeof(DecoratorB), typeof(DecoratorA))]
// Notice that we can even define an empty decorator sequence. That means that this implementation will not be decorated.
// Optionally, just leaving some decorators away would be possible as well. 
[DecoratorSequenceChoice(typeof(IInterface), typeof(DecoratedImplementationC))]
[CreateFunction(typeof(IEnumerable<IInterface>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var instances = container.Create();
        // Check the decorator sequences depending on the topmost instance type
        foreach (var instance in instances)
        {
            if (instance is DecoratorC)
            {
                Console.WriteLine(nameof(DecoratedImplementationA));
                Console.WriteLine(instance is DecoratorC); // True
                Console.WriteLine(instance.Decorated is DecoratorB); // True
                Console.WriteLine(instance.Decorated.Decorated is DecoratorA); // True
                Console.WriteLine(instance.Decorated.Decorated.Decorated is DecoratedImplementationA); // True
            }
            if (instance is DecoratorA)
            {
                Console.WriteLine(nameof(DecoratedImplementationB));
                Console.WriteLine(instance is DecoratorA); // True
                Console.WriteLine(instance.Decorated is DecoratorB); // True
                Console.WriteLine(instance.Decorated.Decorated is DecoratorC); // True
                Console.WriteLine(instance.Decorated.Decorated.Decorated is DecoratedImplementationB); // True
            }
            if (instance is DecoratedImplementationC)
            {
                Console.WriteLine(nameof(DecoratedImplementationC));
                Console.WriteLine(instance is DecoratedImplementationC); // True
            }
        }
    }
}
Default Decorator Order 🔍
If several Decorators are configured for an interface, then a default order can be configured. That order will be applied to implementation types which don't have an explicit order configured.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.DecoratorPatternDefaultOrder)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.DecoratorPattern.DefaultOrder;

// This is the interface which will be decorated.
internal interface IInterface
{
    IInterface Decorated { get; }
}

// This time we'll have multiple decorated implementations.
internal class DecoratedImplementationA : IInterface
{
    public IInterface Decorated => this;
}

internal class DecoratedImplementationB : IInterface
{
    public IInterface Decorated => this;
}

internal class DecoratedImplementationC : IInterface
{
    public IInterface Decorated => this;
}

// This interface marks a decorator. Its generic parameter should be the decorated interface.
internal interface IDecorator<T> {}

// This time we also define multiple decorators.
internal class DecoratorA : IInterface, IDecorator<IInterface>
{
    internal DecoratorA(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

internal class DecoratorB : IInterface, IDecorator<IInterface>
{
    internal DecoratorB(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

internal class DecoratorC : IInterface, IDecorator<IInterface>
{
    internal DecoratorC(IInterface decorated) => Decorated = decorated;
    public IInterface Decorated { get; }
}

[ImplementationAggregation(typeof(DecoratedImplementationA), typeof(DecoratedImplementationB), typeof(DecoratedImplementationC), typeof(DecoratorA), typeof(DecoratorB), typeof(DecoratorC))]
// We need to specify the decorator interface.
[DecoratorAbstractionAggregation(typeof(IDecorator<>))]
// One decorated implementation gets an explicit decorator sequence.
[DecoratorSequenceChoice(typeof(IInterface), typeof(DecoratedImplementationA), typeof(DecoratorA), typeof(DecoratorB), typeof(DecoratorC))]
// For the others we define a default decorator sequence by setting the second parameter to the decorated interface.
// If a decorated implementation has no explicit decorator sequence, the default decorator sequence will be used.
[DecoratorSequenceChoice(typeof(IInterface), typeof(IInterface), typeof(DecoratorB))]
[CreateFunction(typeof(IEnumerable<IInterface>), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var instances = container.Create();
        // Check the decorator sequences depending on the topmost instance type
        foreach (var instance in instances)
        {
            if (instance is DecoratorC)
            {
                Console.WriteLine(nameof(DecoratedImplementationA));
                Console.WriteLine(instance is DecoratorC); // True
                Console.WriteLine(instance.Decorated is DecoratorB); // True
                Console.WriteLine(instance.Decorated.Decorated is DecoratorA); // True
                Console.WriteLine(instance.Decorated.Decorated.Decorated is DecoratedImplementationA); // True
            }
            if (instance is DecoratorB)
            {
                Console.WriteLine(instance.Decorated.GetType().Name);
                Console.WriteLine(instance is DecoratorB); // True
                Console.WriteLine(instance.Decorated is DecoratedImplementationB or DecoratedImplementationC); // True
            }
        }
    }
}

Composite Pattern

Group of features concerned with the Composite pattern.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Iterable Injection for Composites 🔍
Composite can get an Iterable of its interface injected in order to get the interface instances that it manages.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.CompositePatternIEnumerable)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.CompositePattern.IEnumerable;

// This is the interface of which the implementations will be composited.
internal interface IInterface
{
    IEnumerable<IInterface> Composition { get; }
}

// Multiple implementations of the interface.
internal class ComposedImplementationA : IInterface
{
    public IEnumerable<IInterface> Composition => new[] { this };
}

internal class ComposedImplementationB : IInterface
{
    public IEnumerable<IInterface> Composition => new[] { this };
}

internal class ComposedImplementationC : IInterface
{
    public IEnumerable<IInterface> Composition => new[] { this };
}

// This interface marks a composite. Its generic parameter should be the composited interface.
internal interface IComposite<T> {}

// This is the composite. It must implement the composited interface and the composite interface.
internal class Composite : IInterface, IComposite<IInterface>
{
    // Also it can have a iterable dependency of the composited interface. The composited implementation instances will be injected here.
    internal Composite(IEnumerable<IInterface> composition) => Composition = composition;
    public IEnumerable<IInterface> Composition { get; }
}

[ImplementationAggregation(typeof(ComposedImplementationA), typeof(ComposedImplementationB), typeof(ComposedImplementationC), typeof(Composite))]
// We need to specify the composite interface.
[CompositeAbstractionAggregation(typeof(IComposite<>))]
[CreateFunction(typeof(IInterface), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var instance = container.Create();
        // Check the types of the composite and its composition.
        Console.WriteLine(instance.GetType().Name);
        foreach (var @interface in instance.Composition)
        {
            Console.WriteLine(@interface.GetType().Name);
        }
    }
}
Composite Can Be Decorated 🔍
Composite can be decorated as well.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.CompositePatternDecorated)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.CompositePattern.Decorated;

// This is the interface of which the implementations will be composited and decorated.
internal interface IInterface
{
    IEnumerable<IInterface> Composition { get; }
}

// Multiple implementations of the interface.
internal class ComposedImplementationA : IInterface
{
    public IEnumerable<IInterface> Composition => new[] { this };
}

internal class ComposedImplementationB : IInterface
{
    public IEnumerable<IInterface> Composition => new[] { this };
}

internal class ComposedImplementationC : IInterface
{
    public IEnumerable<IInterface> Composition => new[] { this };
}

// This interface marks a decorator. Its generic parameter should be the decorated interface.
internal interface IDecorator<T> {}

// Multiple decorators of the interface.
internal class DecoratorA : IInterface, IDecorator<IInterface>
{
    internal DecoratorA(IInterface decorated) => Composition = new []{ decorated };
    public IEnumerable<IInterface> Composition { get; }
}

internal class DecoratorB : IInterface, IDecorator<IInterface>
{
    internal DecoratorB(IInterface decorated) => Composition = new []{ decorated };
    public IEnumerable<IInterface> Composition { get; }
}

internal class DecoratorC : IInterface, IDecorator<IInterface>
{
    internal DecoratorC(IInterface decorated) => Composition = new []{ decorated };
    public IEnumerable<IInterface> Composition { get; }
}

// This interface marks a composite. Its generic parameter should be the composited interface.
internal interface IComposite<T> {}

// This is the composite. It must implement the composited interface and the composite interface.
internal class Composite : IInterface, IComposite<IInterface>
{
    internal Composite(IEnumerable<IInterface> composition) => Composition = composition;
    public IEnumerable<IInterface> Composition { get; }
}

[ImplementationAggregation(typeof(Composite), typeof(ComposedImplementationA), typeof(ComposedImplementationB), typeof(ComposedImplementationC), typeof(DecoratorA), typeof(DecoratorB), typeof(DecoratorC))]
[DecoratorAbstractionAggregation(typeof(IDecorator<>))]
// We need to specify the composite interface.
[CompositeAbstractionAggregation(typeof(IComposite<>))]
// Specify the decorator sequences for the composited implementations.
[DecoratorSequenceChoice(typeof(IInterface), typeof(ComposedImplementationA), typeof(DecoratorA), typeof(DecoratorB), typeof(DecoratorC))]
[DecoratorSequenceChoice(typeof(IInterface), typeof(ComposedImplementationB), typeof(DecoratorC), typeof(DecoratorB), typeof(DecoratorA))]
[DecoratorSequenceChoice(typeof(IInterface), typeof(ComposedImplementationC))]
// The composite can be decorated, too.
[DecoratorSequenceChoice(typeof(IInterface), typeof(Composite), typeof(DecoratorB))]
[CreateFunction(typeof(IInterface), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var root = container.Create();
        // Check the composition and the decorations
        Console.WriteLine(nameof(Composite));
        Console.WriteLine(root is DecoratorB); // True
        Console.WriteLine(root.Composition.First() is Composite); // True
        foreach (var instance in root.Composition.First().Composition)
        {
            if (instance is DecoratorC)
            {
                Console.WriteLine(nameof(ComposedImplementationA));
                Console.WriteLine(instance is DecoratorC); // True
                Console.WriteLine(instance.Composition.First() is DecoratorB); // True
                Console.WriteLine(instance.Composition.First().Composition.First() is DecoratorA); // True
                Console.WriteLine(instance.Composition.First().Composition.First().Composition.First() is ComposedImplementationA); // True
            }
            if (instance is DecoratorA)
            {
                Console.WriteLine(nameof(ComposedImplementationB));
                Console.WriteLine(instance is DecoratorA); // True
                Console.WriteLine(instance.Composition.First() is DecoratorB); // True
                Console.WriteLine(instance.Composition.First().Composition.First() is DecoratorC); // True
                Console.WriteLine(instance.Composition.First().Composition.First().Composition.First() is ComposedImplementationB); // True
            }
            if (instance is ComposedImplementationC)
            {
                Console.WriteLine(nameof(ComposedImplementationC));
                Console.WriteLine(instance is ComposedImplementationC); // True
            }
        }
    }
}

Misc

Group of features of miscellaneous nature.

Feature Microsoft.Extensions.DependencyInjection MrMeeseeks.DIE
Initialized Instances 🔍
Initialized Instances can be defined per container and/or scope and will be created automatically when its container/scope is created.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.MiscInitializedInstance)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Misc.InitializedInstance;

// Simple class that we want to create objects from using a container
internal class ConcreteClass { }

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();
        
        // Create in instance manually and register it as a singleton to make it an initialized instance on container level
        var initializedInstance = new ConcreteClass();
        builder.Services.AddSingleton(initializedInstance);
        
        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();

        var initializedInstanceA = host.Services.GetRequiredService<ConcreteClass>();
        Console.WriteLine(initializedInstanceA.GetType().Name); // ConcreteClass
        
        var initializedInstanceB = host.Services.GetRequiredService<ConcreteClass>();
        Console.WriteLine(initializedInstanceA == initializedInstanceB); // True
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.MiscInitializedInstance)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Misc.InitializedInstance;

// This simple class will be used as an initialized instance in the container and the scope
internal class ConcreteClass { }

internal class ScopeRoot
{
    internal ScopeRoot(ConcreteClass dependency) => Dependency = dependency;
    internal ConcreteClass Dependency { get; }
}

[ImplementationAggregation(typeof(ConcreteClass), typeof(ScopeRoot))]
// Initialized instances can be defined for a container.
// They will be created as soon as the container is created and then used for injections to their type.
[InitializedInstances(typeof(ConcreteClass))]
[ScopeRootImplementationAggregation(typeof(ScopeRoot))]
[CreateFunction(typeof(ConcreteClass), "Create")]
[CreateFunction(typeof(ScopeRoot), "CreateScope")]
internal partial class Container
{
    private Container() {}
    
    // Initialized instances can also be defined for a scope.
    // They will be created as soon as the scope is created and then used for injections to their type.
    [InitializedInstances(typeof(ConcreteClass))]
    private partial class DIE_DefaultScope {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var concreteClassA = container.Create();
        var concreteClassB = container.Create();
        // Initialized instances are implicitly and effectively much like singletons/"container instances"
        Console.WriteLine(concreteClassA == concreteClassB); // True
        
        var concreteClassSA = container.CreateScope().Dependency;
        var concreteClassSB = container.CreateScope().Dependency;
        // Or initialized instances are implicitly and effectively much like scoped instances, if used in a scope.
        Console.WriteLine(concreteClassSA != concreteClassSB); // True
    }
}
Marker Interfaces 🔍
Marker interfaces can be declared to mark certain dependency injection properties to implementation types (such as lifetime, disposal behavior and so on).
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.MiscMarkerInterface)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Misc.MarkerInterface;

// Define a marker interface for each dependency injection setting
public interface IContainerInstance { }
public interface ITransientScopeInstance { }
public interface IScopeInstance { }
public interface ITransientScopeRoot { }
public interface IScopeRoot { }
public interface ITransient { }
public interface ISyncTransient { }
public interface IAsyncTransient { }
// ReSharper disable once UnusedTypeParameter
public interface IDecorator<T> { }
// ReSharper disable once UnusedTypeParameter
public interface IComposite<T> { }
public interface IInitializer
{
    void Initialize();
}
public interface ITaskInitializer
{
    Task InitializeAsync();
}
public interface IValueTaskInitializer
{
    ValueTask InitializeAsync();
}

// A helpful interface (not marker interface)
internal interface IInterface {}

// Some implementations, composites and decorators which get marked with the marker interfaces.
internal class ImplementationA : IContainerInstance, IInitializer, ITransient, IDisposable, IAsyncDisposable
{
    public void Initialize() { }
    public void Dispose() { }
    public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}

internal class ImplementationB : ITransientScopeInstance, IValueTaskInitializer, ISyncTransient, IDisposable
{
    public ValueTask InitializeAsync() => ValueTask.CompletedTask;
    public void Dispose() { }
}

internal class ImplementationC : IScopeInstance, ITaskInitializer, IAsyncTransient, IAsyncDisposable
{
    public Task InitializeAsync() => Task.CompletedTask;
    public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}

internal class Decorator : IInterface, IDecorator<IInterface>
{
    internal Decorator(IInterface decorated) {}
}

internal class Composite : IInterface, IComposite<IInterface>
{
    internal Composite(IEnumerable<IInterface> composited) {}
}

internal class TransientScopeRoot : ITransientScopeRoot
{
    internal TransientScopeRoot(IInterface dependency) {}
}

internal class ScopeRoot : IScopeRoot
{
    internal ScopeRoot(IInterface dependency) {}
}

internal class Root
{
    internal Root(
        IInterface dependency,
        TransientScopeRoot transientScopeRoot,
        ScopeRoot scopeRoot) {}
}

[ImplementationAggregation(typeof(ImplementationA), typeof(ImplementationB), typeof(ImplementationC), typeof(Decorator), typeof(Composite), typeof(TransientScopeRoot), typeof(ScopeRoot), typeof(Root))]

// Register marker interfaces instead of implementations for the diverse dependency injection settings.
[ContainerInstanceAbstractionAggregation(typeof(IContainerInstance))]
[TransientScopeInstanceAbstractionAggregation(typeof(ITransientScopeInstance))]
[ScopeInstanceAbstractionAggregation(typeof(IScopeInstance))]
[TransientScopeRootAbstractionAggregation(typeof(ITransientScopeRoot))]
[ScopeRootAbstractionAggregation(typeof(IScopeRoot))]
[TransientAbstractionAggregation(typeof(ITransient))]
[SyncTransientAbstractionAggregation(typeof(ISyncTransient))]
[AsyncTransientAbstractionAggregation(typeof(IAsyncTransient))]
[DecoratorAbstractionAggregation(typeof(IDecorator<>))]
[CompositeAbstractionAggregation(typeof(IComposite<>))]
[Initializer(typeof(IInitializer), nameof(IInitializer.Initialize))]
[Initializer(typeof(ITaskInitializer), nameof(ITaskInitializer.InitializeAsync))]
[Initializer(typeof(IValueTaskInitializer), nameof(IValueTaskInitializer.InitializeAsync))]

[CreateFunction(typeof(IInterface), "Create")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var root = container.Create();
        // Checking would be overkill here.
    }
}
Register All Implementations 🔍
Register all implementation types of the container-hosting assembly and all referenced assemblies with a single configuration.
using System.Reflection;
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.MiscRegisterAllImplementations)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Misc.RegisterAllImplementations;

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        var assembly = typeof(Usage).Assembly;
        var types = assembly
            .GetReferencedAssemblies()
            .Select(Assembly.Load)
            .Prepend(assembly)
            .SelectMany(a => a.GetTypes())
            .ToArray();
        var implementations = types
            .Where(t => t is { IsClass: true, IsAbstract: false })
            .ToArray();
        
        foreach (var implementation in implementations)
        {
            builder.Services.AddTransient(implementation);
        }

        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();
        
        var featureEnumInfoAttribute = host.Services.GetRequiredService<FeatureEnumInfoAttribute>();
        Console.WriteLine(featureEnumInfoAttribute.GetType().Name); // FeatureEnumInfoAttribute
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.MiscRegisterAllImplementations)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Misc.AllImplementations;

// No explicit aggregation of implementations is required.
// Just the following attribute is enough to register all implementations of the current assembly and all referenced assemblies.
[AllImplementationsAggregation]

// Create-functions for types of a referenced assembly
[ConstructorChoice(typeof(DateTime))]
[ConstructorChoice(typeof(DateOnly))]
[ConstructorChoice(typeof(TimeOnly))]
[CreateFunction(typeof(DateTime), "CreateDateTime")]
[CreateFunction(typeof(DateOnly), "CreateDateOnly")]
[CreateFunction(typeof(TimeOnly), "CreateTimeOnly")]

// Create-function for a type of another namespace in the same assembly
[CreateFunction(typeof(Implementations.ConcreteClass.ConcreteClass), "CreateConcreteClass")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var dateTime = container.CreateDateTime();
        var dateOnly = container.CreateDateOnly();
        var timeOnly = container.CreateDateOnly();
        var concreteClass = container.CreateConcreteClass();
        // Do something with the created instances
    }
}
Register All Implementations of a Specific Assembly 🔍
Register all implementation types of a specified assembly with a single configuration.
using ContainerFeatureSampleComparison.FeatureDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

[assembly:FeatureSample(Feature.MiscRegisterAssemblyImplementations)]

namespace ContainerFeatureSampleComparison.FeatureSamples.Microsoft.Extensions.DependencyInjection.Misc.RegisterAssemblyImplementations;

internal static class Builder
{
    internal static HostApplicationBuilder CreateBuilder()
    {
        var builder = Host.CreateApplicationBuilder();

        var assembly = typeof(Feature).Assembly;
        var types = assembly.GetTypes();
        var implementations = types
            .Where(t => t is { IsClass: true, IsAbstract: false })
            .ToArray();
        
        foreach (var implementation in implementations)
        {
            builder.Services.AddTransient(implementation);
        }

        return builder;
    }
}

internal static class Usage
{
    internal static void Use()
    {
        using var host = Builder.CreateBuilder().Build();
        
        var featureEnumInfoAttribute = host.Services.GetRequiredService<FeatureEnumInfoAttribute>();
        Console.WriteLine(featureEnumInfoAttribute.GetType().Name); // FeatureEnumInfoAttribute
    }
}
using ContainerFeatureSampleComparison.FeatureDefinitions;
using MrMeeseeks.DIE.Configuration.Attributes;

[assembly:FeatureSample(Feature.MiscRegisterAssemblyImplementations)]

namespace ContainerFeatureSampleComparison.FeatureSamples.MrMeeseeks.DIE.Misc.RegisterAssemblyImplementations;

// No explicit aggregation of implementations is required.
// Just the following attribute is enough to register all implementations of the owning assemblies of the passed types.
[AssemblyImplementationsAggregation(typeof(DateTime))]

// Create-functions for types of a referenced assembly
[ConstructorChoice(typeof(DateTime))]
[ConstructorChoice(typeof(DateOnly))]
[ConstructorChoice(typeof(TimeOnly))]
[CreateFunction(typeof(DateTime), "CreateDateTime")]
[CreateFunction(typeof(DateOnly), "CreateDateOnly")]
[CreateFunction(typeof(TimeOnly), "CreateTimeOnly")]
internal partial class Container
{
    private Container() {}
}

internal static class Usage
{
    internal static void Use()
    {
        using var container = Container.DIE_CreateContainer();
        var dateTime = container.CreateDateTime();
        var dateOnly = container.CreateDateOnly();
        var timeOnly = container.CreateDateOnly();
        // Do something with the created instances
    }
}