Mal.SourceGeneratedDI 2.0.2

dotnet add package Mal.SourceGeneratedDI --version 2.0.2
                    
NuGet\Install-Package Mal.SourceGeneratedDI -Version 2.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Mal.SourceGeneratedDI" Version="2.0.2">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Mal.SourceGeneratedDI" Version="2.0.2" />
                    
Directory.Packages.props
<PackageReference Include="Mal.SourceGeneratedDI">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Mal.SourceGeneratedDI --version 2.0.2
                    
#r "nuget: Mal.SourceGeneratedDI, 2.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Mal.SourceGeneratedDI@2.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Mal.SourceGeneratedDI&version=2.0.2
                    
Install as a Cake Addin
#tool nuget:?package=Mal.SourceGeneratedDI&version=2.0.2
                    
Install as a Cake Tool

Mal.SourceGeneratedDI

Source-generated dependency injection with compile-time registrations and runtime composition.

What is Dependency Injection? Dependency injection is a design pattern where objects receive their dependencies from external sources rather than creating them. This makes code more testable, maintainable, and flexible. This library uses source generation to wire up your dependencies at compile-time, giving you type safety and performance without runtime reflection.

Table of Contents

Quick Start

Installation

dotnet add package Mal.SourceGeneratedDI
dotnet add package Mal.SourceGeneratedDI.Abstractions

Both packages are required (.NET Standard 2.0+):

  • Mal.SourceGeneratedDI - source generator and analyzers
  • Mal.SourceGeneratedDI.Abstractions - runtime attributes, contracts, builder, and container

Note: The source generator package cannot automatically include the Abstractions package as a transitive dependency, so both must be explicitly referenced.

1. Declare services

using Mal.SourceGeneratedDI;

[Singleton]
public class DatabaseService { }

[Instance]
public class RequestHandler
{
    public RequestHandler(DatabaseService db) { }
}

2. Build and resolve

// GeneratedRegistry is auto-generated in your assembly's root namespace
var container = new DependencyContainerBuilder()
    .AddRegistry<GeneratedRegistry>()
    .Build();

var handler = container.Resolve<RequestHandler>();

Migrating from V1

V1 used direct container construction. V2 uses registry-based composition for cross-assembly support.

Before (v1)

var container = new DependencyContainer();
var service = container.Resolve<MyService>();

After (v2)

var container = new DependencyContainerBuilder()
    .AddRegistry<GeneratedRegistry>()
    .Build();
    
var service = container.Resolve<MyService>();

Key changes:

  • Use DependencyContainerBuilder with registries instead of direct container construction
  • Manual factories: add via builder or generated registry partial hooks

Understanding the Basics

Service Lifetimes

Singleton - One instance is created and shared across all requests:

[Singleton]
public class DatabaseConnection
{
    // This will be created once and reused everywhere
}

Instance (Transient) - A new instance is created every time:

[Instance]
public class RequestHandler
{
    // A fresh instance is created for each Resolve<RequestHandler>() call
}

Service Registration

Mark your classes with lifetime attributes to register them:

// Register as the concrete type
[Singleton]
public class UserService { }

// Register as an interface
[Singleton<IUserService>]
public class UserService : IUserService { }

The source generator scans your assembly and creates registration code at compile-time.

Dependency Injection

Dependencies are automatically injected through constructor parameters:

[Singleton]
public class DatabaseService { }

[Instance]
public class UserRepository
{
    private readonly DatabaseService _db;
    
    // DatabaseService is automatically injected
    public UserRepository(DatabaseService db)
    {
        _db = db;
    }
}

The Container

The source generator creates a GeneratedRegistry class in your assembly's root namespace. You compose one or more registries into a container, then resolve services:

// MyApp.dll → MyApp.GeneratedRegistry
var container = new DependencyContainerBuilder()
    .AddRegistry<GeneratedRegistry>()
    .Build();

var userRepo = container.Resolve<UserRepository>();

Advanced Usage

Assembly-Level Registrations

When you can't or don't want to put attributes on the class itself, use assembly-level attributes:

// In any .cs file in your project
[assembly: Singleton<ILogger, ConsoleLogger>]
[assembly: Instance<IHttpClient, HttpClientWrapper>]

This is useful for:

  • Registering third-party types you don't control
  • Mapping interfaces to implementations without modifying the implementation class
  • Centralizing all registrations in one place

Lazy Dependencies

Use Lazy<T> when you want to defer creation until needed, or to break circular dependencies:

Breaking Circular Dependencies

[Singleton]
public class ServiceA
{
    private readonly Lazy<ServiceB> _serviceB;
    
    public ServiceA(Lazy<ServiceB> serviceB)
    {
        _serviceB = serviceB;
    }
    
    public void DoWork()
    {
        _serviceB.Value.Process();
    }
}

[Singleton]
public class ServiceB
{
    private readonly ServiceA _serviceA;
    
    // ServiceA → ServiceB → ServiceA would be circular,
    // but Lazy<ServiceB> breaks the cycle
    public ServiceB(ServiceA serviceA)
    {
        _serviceA = serviceA;
    }
}

Deferring Expensive Operations

[Instance]
public class ExpensiveOperation
{
    private readonly Lazy<DatabaseService> _db;
    
    public ExpensiveOperation(Lazy<DatabaseService> db)
    {
        _db = db;
    }
    
    public void Execute()
    {
        // Database is only created if/when Value is accessed
        _db.Value.Query(...);
    }
}

Cross-Assembly Composition

When your application uses multiple libraries that each contribute services, compose their registries together:

var container = new DependencyContainerBuilder()
    .AddRegistry<CoreLib.GeneratedRegistry>()      // From CoreLib.dll
    .AddRegistry<FeatureA.GeneratedRegistry>()     // From FeatureA.dll
    .AddRegistry<FeatureB.GeneratedRegistry>()     // From FeatureB.dll
    .AddRegistry<GeneratedRegistry>()              // From your app
    .Build();

Each assembly that references the source generator will have its own GeneratedRegistry in its own root namespace.

Named Containers

When different parts of your application need isolated sets of services — for example, per-window services in a multi-window desktop app — use named containers.

Marking services for a named container

// Goes into the default GeneratedRegistry
[Singleton]
public class AppSettingsService { }

// Goes into WindowGeneratedRegistry
[Singleton(Container = "Window")]
public class OverlayService { }

[Singleton<IOverlayService>(Container = "Window")]
public class OverlayService : IOverlayService { }

The Container name becomes part of the generated class name: Container = "Window"WindowGeneratedRegistry.

Composing named and default registries

Each registry is independent. Because Container = "Window" explicitly partitions services, there should be no overlap between WindowGeneratedRegistry and GeneratedRegistry — so you can compose them directly without any duplicate policy:

var windowContainer = new DependencyContainerBuilder()
    .AddRegistry<WindowGeneratedRegistry>()
    .AddRegistry<GeneratedRegistry>()
    .Build();

The default policy is Throw, which means any accidental duplicate registration will surface immediately as a build-time error rather than silently misbehaving at runtime. This is intentional: if you're partitioning services correctly, duplicates shouldn't exist. The error is a signal that your partitioning needs attention.

If you have a situation where duplicates are genuinely unavoidable — for example, composing third-party registries you don't control — you can opt out:

var windowContainer = new DependencyContainerBuilder()
    .WithDuplicatePolicy(DuplicateRegistrationPolicy.FirstWins)
    .AddRegistry<WindowGeneratedRegistry>()  // window-specific services win
    .AddRegistry<GeneratedRegistry>()
    .Build();

Every window gets its own container instance with its own OverlayService, while all windows share the same AppSettingsService.

Assembly-level attributes

[assembly: Singleton<IOverlayService, OverlayService>(Container = "Window")]

Manual Registrations

Sometimes you need to register services at runtime:

Option 1: Host-level (in your startup code)

var container = new DependencyContainerBuilder()
    .AddRegistry<GeneratedRegistry>()
    .RegisterSingleton<IClock>(() => new UtcClock())
    .RegisterInstance<IConfig>(() => LoadConfigFromFile())
    .Build();

Option 2: Assembly-level (via partial method)

// In your project, create a partial class matching your assembly's root namespace
namespace MyApp;

public sealed partial class GeneratedRegistry
{
    static partial void AddManualFactories(IServiceRegistry registry)
    {
        registry.AddSingleton(typeof(IClock), _ => new UtcClock());
        registry.AddInstance(typeof(IConfig), _ => LoadConfigFromFile());
    }
}

The generated registry class includes a partial hook that gets called during registration.

Fallback Provider

Integrate with other DI systems (like ASP.NET Core's built-in DI):

var container = new DependencyContainerBuilder()
    .AddRegistry<GeneratedRegistry>()
    .UseFallback(serviceProvider)  // IServiceProvider from another DI system
    .Build();

If a service isn't found in your generated registrations, it falls back to the provided IServiceProvider.

Duplicate Registration Policy

When composing multiple registries, you might register the same service twice:

var container = new DependencyContainerBuilder()
    .WithDuplicatePolicy(DuplicateRegistrationPolicy.LastWins)
    .AddRegistry<LibraryGeneratedRegistry>()  // Registers ILogger
    .AddRegistry<GeneratedRegistry>()         // Also registers ILogger
    .Build();

Options:

  • Throw (default) - Throws an exception on duplicate
  • FirstWins - Keeps the first registration
  • LastWins - Overwrites with the last registration

Customizing Generated Class Names

By default the generated class is named GeneratedRegistry in your assembly's root namespace. You can override this with [assembly: DependencyContainerOptions(...)]:

[assembly: DependencyContainerOptions(
    Namespace = "MyApp.DI",   // Default: assembly root namespace
    Prefix    = "Hub"         // Default: no prefix
)]

With Prefix = "Hub", the generated classes become HubGeneratedRegistry, HubWindowGeneratedRegistry, etc. With Namespace = "MyApp.DI", they are placed in that namespace instead.

Analyzer Diagnostics

The following diagnostics are produced by the included Roslyn analyzer:

ID Severity Description
DI001 Error A class has both [Singleton] and [Instance] attributes — pick one lifetime.
DI002 Error The service type in [Singleton<T>] is not assignable from the implementation.
DI003 Warning [Singleton<MyClass>] on MyClass is redundant — use plain [Singleton].
DI004 Error Container = "..." value is not a valid C# identifier.
DI005 Error Prefix = "..." in [DependencyContainerOptions] is not a valid C# identifier.
DI006 Error Namespace = "..." in [DependencyContainerOptions] is not a valid C# namespace.

License

MIT License

Copyright (c) 2026 The SourceGeneratedDI Authors

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.2 93 3/15/2026
2.0.2-pre.1 68 3/3/2026
2.0.1-pre.1 53 2/10/2026
2.0.0-pre.4 49 2/10/2026
2.0.0-pre.3 51 2/10/2026
2.0.0-pre.2 57 2/10/2026
2.0.0-pre.1 46 2/10/2026
1.0.0 159 2/6/2026
1.0.0-beta.1 54 2/6/2026

v2.0.2
  - Public release
 
v2.0.2-pre.1
  - Named container support: `Container = "Window"` on attributes routes services to `WindowGeneratedRegistry`
  - Breaking: Default registry class renamed to `GeneratedRegistry` in assembly root namespace (no more mangled assembly name prefix)
  - Custom namespace/prefix configurable via `[DependencyContainerOptions(Namespace = "...", Prefix = "...")]`

v2.0.1-pre.1
  - Removed generated `<AssemblyName>GeneratedContainerFactory` class
  - Added comprehensive XML documentation to generated code
  - Version synced with Abstractions package (2.0.1) for clarity

v2.0.0-pre.4
  - Note: Both packages must be referenced explicitly (Abstractions dependency cannot auto-flow with source generators)

v2.0.0-pre.3
  - Another attempt at fixing the Abstractions package flow as an automatic dependency

v2.0.0-pre.2
  - Reverted automatic dependency - both packages must be referenced (standard for source generators)

v2.0.0-pre.1
  - Breaking: switched from generated per-assembly container bodies to generated registration sources.
  - Breaking: source-generated registrations now compose through `IRegistrationSource` and `DependencyContainerBuilder`.
  - Breaking: runtime attributes/contracts moved to `Mal.SourceGeneratedDI.Abstractions`.
  - Added generated `<AssemblyName>GeneratedRegistry`.
  - Added generated partial hook for advanced assembly-level manual factories.
  - Improved cross-assembly composition support by default.