Mal.SourceGeneratedDI.Abstractions
2.0.2
dotnet add package Mal.SourceGeneratedDI.Abstractions --version 2.0.2
NuGet\Install-Package Mal.SourceGeneratedDI.Abstractions -Version 2.0.2
<PackageReference Include="Mal.SourceGeneratedDI.Abstractions" Version="2.0.2" />
<PackageVersion Include="Mal.SourceGeneratedDI.Abstractions" Version="2.0.2" />
<PackageReference Include="Mal.SourceGeneratedDI.Abstractions" />
paket add Mal.SourceGeneratedDI.Abstractions --version 2.0.2
#r "nuget: Mal.SourceGeneratedDI.Abstractions, 2.0.2"
#:package Mal.SourceGeneratedDI.Abstractions@2.0.2
#addin nuget:?package=Mal.SourceGeneratedDI.Abstractions&version=2.0.2
#tool nuget:?package=Mal.SourceGeneratedDI.Abstractions&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.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- 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 | 88 | 3/15/2026 |
| 2.0.2-pre.1 | 70 | 3/3/2026 |
| 2.0.1 | 102 | 2/10/2026 |
| 2.0.0 | 105 | 2/10/2026 |
v2.0.2
- Public release
v2.0.2-pre.1
- Added `Container` property to `[Singleton]` and `[Instance]` attributes for named container routing
- Added `Namespace` and `Prefix` properties to `[DependencyContainerOptions]` for controlling generated type names
- Breaking: Generated registry class renamed from `<Assembly>GeneratedRegistry` to `GeneratedRegistry` (in assembly root namespace)
- Named containers generate additional registry classes (e.g. `Container = "Window"` produces `WindowGeneratedRegistry`)
- Analyzer validates container names, prefix, and namespace are valid C# identifiers (DI004, DI005, DI006)
v2.0.1
- Breaking: Removed `DependencyContainer.Create()` static method and direct constructors - use `DependencyContainerBuilder` only
- Breaking: Renamed `Register<T>()` methods to `RegisterSingleton<T>()` and added `RegisterInstance<T>()` for explicit lifetime control
- Added generic `AddRegistry<T>()` overload for cleaner syntax
- Added `WithDuplicatePolicy()` fluent method to replace property setter
- Added comprehensive XML documentation to all public members
v2.0.0
- Breaking: introduces required shared runtime contract package for v2 architecture.
- Added DI attributes shared across projects (`Singleton`, `Instance`, assembly-level variants).
- Added `IServiceRegistry` and `IRegistrationSource` for generated registry composition.
- Added runtime `DependencyContainerBuilder` with duplicate policy and optional fallback `IServiceProvider`.
- Added runtime `DependencyContainer` used to resolve merged registrations.