Mal.SourceGeneratedDI
2.0.2
dotnet add package Mal.SourceGeneratedDI --version 2.0.2
NuGet\Install-Package Mal.SourceGeneratedDI -Version 2.0.2
<PackageReference Include="Mal.SourceGeneratedDI" Version="2.0.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Mal.SourceGeneratedDI" Version="2.0.2" />
<PackageReference Include="Mal.SourceGeneratedDI"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Mal.SourceGeneratedDI --version 2.0.2
#r "nuget: Mal.SourceGeneratedDI, 2.0.2"
#:package Mal.SourceGeneratedDI@2.0.2
#addin nuget:?package=Mal.SourceGeneratedDI&version=2.0.2
#tool nuget:?package=Mal.SourceGeneratedDI&version=2.0.2
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 analyzersMal.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
DependencyContainerBuilderwith 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 duplicateFirstWins- Keeps the first registrationLastWins- 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.
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.