A high-performance C# source generator that produces compile-time dependency injection registrations for Splat. Eliminates runtime reflection, provides full native AOT support, and includes intelligent analyzers with automatic code fixes.
This source generator produces dependency injection registrations for Splat at compile-time based on your constructor and property injection requirements. It uses an incremental source generator to provide fast builds with zero runtime reflection overhead.
Key features:
- Zero reflection - All registrations generated at compile-time
- Native AOT compatible - Works with trimming and AOT compilation
- Built-in analyzers - Real-time diagnostics and automatic code fixes
- Constructor injection - Automatic dependency resolution
- Property injection - Attribute-based property initialization
- Lazy singletons - Thread-safe lazy initialization with configurable modes
- Contract support - Named registrations for multiple implementations
- Incremental compilation - Fast builds that only regenerate when needed
Always Be NuGetting. Package contains:
| Package | NuGet |
|---|---|
| Splat.DependencyInjection.SourceGenerator |
- .NET SDK: Any version supporting C# 7.3 or later
- Target Frameworks: .NET Framework 4.6.2+, .NET 8+, .NET 9+, .NET 10+
- Splat: Version 19.1.1 or later (supports modern generic-first resolvers)
Add the package to your project:
<PackageReference Include="Splat.DependencyInjection.SourceGenerator" Version="{latest version}" PrivateAssets="all" />Note: The PrivateAssets="all" attribute prevents the source generator from being transitively referenced by projects that depend on yours. This is the recommended configuration for source generators.
Use the SplatRegistrations static class to register services. The source generator will detect these calls and generate the implementation at compile-time.
Transient Registration (New Instance Each Time)
using static Splat.SplatRegistrations;
// Register with interface and implementation
Register<IToaster, Toaster>();
Register<IMessageService, MessageService>();
// Register concrete type only (when no interface)
Register<DatabaseContext>();Lazy Singleton Registration (Single Lazy Instance)
// Basic lazy singleton
RegisterLazySingleton<IDatabase, SqliteDatabase>();
// With thread safety mode
RegisterLazySingleton<ICache, MemoryCache>(LazyThreadSafetyMode.PublicationOnly);Thread safety modes:
LazyThreadSafetyMode.ExecutionAndPublication(default) - Full thread safety with locksLazyThreadSafetyMode.PublicationOnly- Multiple threads may initialize, first winsLazyThreadSafetyMode.None- No thread safety (single-threaded scenarios only)
Constant Registration (Pre-Created Instance)
// Register an existing instance
var config = new Configuration { ApiUrl = "https://api.example.com" };
RegisterConstant<IConfiguration>(config);Named Contracts (Multiple Implementations)
// Register multiple implementations with different contracts
Register<ILogger, FileLogger>("file");
Register<ILogger, ConsoleLogger>("console");
Register<ILogger, CloudLogger>("cloud");
// Retrieve by contract
var fileLogger = resolver.GetService<ILogger>("file");Call SetupIOC() once during application startup in each assembly that uses SplatRegistrations:
using Splat;
using static Splat.SplatRegistrations;
// In your application entry point
public class App
{
public void ConfigureServices()
{
// Register all dependencies
Register<IUserService, UserService>();
Register<IAuthService, AuthService>();
RegisterLazySingleton<IDatabase, AppDatabase>();
// Initialize the container (generates and executes registrations)
SetupIOC();
}
}For unit tests, pass a custom resolver:
[Test]
public void TestDependencies()
{
var resolver = new ModernDependencyResolver();
SetupIOC(resolver); // Use test-specific resolver
var service = resolver.GetService<IUserService>();
Assert.NotNull(service);
}The source generator automatically resolves constructor parameters.
Single Constructor
public class UserService : IUserService
{
private readonly IDatabase _database;
private readonly ILogger _logger;
// Automatically detected - no attribute needed
public UserService(IDatabase database, ILogger logger)
{
_database = database;
_logger = logger;
}
}Multiple Constructors
Use [DependencyInjectionConstructor] to specify which constructor to use:
using static Splat.SplatRegistrations;
public class AuthService : IAuthService
{
private readonly IDatabase _database;
private readonly ILogger _logger;
// Empty constructor for testing
public AuthService()
{
_database = new InMemoryDatabase();
_logger = new NullLogger();
}
// Production constructor - marked for DI
[DependencyInjectionConstructor]
public AuthService(IDatabase database, ILogger logger)
{
_database = database;
_logger = logger;
}
}If you forget the attribute with multiple constructors, the analyzer will warn you and offer a code fix to add it automatically.
Lazy Dependencies
Inject Lazy<T> for on-demand initialization:
public class ExpensiveService
{
private readonly Lazy<IDatabase> _database;
public ExpensiveService(Lazy<IDatabase> database)
{
_database = database; // Not initialized yet
}
public void DoWork()
{
// Database initialized only when first accessed
_database.Value.ExecuteQuery("...");
}
}
// Register the dependency as a lazy singleton
RegisterLazySingleton<IDatabase, AppDatabase>();
Register<IExpensiveService, ExpensiveService>();Mark properties with [DependencyInjectionProperty] for initialization after construction.
using static Splat.SplatRegistrations;
public class ViewModelBase
{
// Property injection - must have public or internal setter
[DependencyInjectionProperty]
public INavigationService Navigation { get; set; }
[DependencyInjectionProperty]
public ILogger Logger { get; internal set; } // Internal setters supported
}The analyzer will:
- Warn if property doesn't have a public/internal setter
- Offer code fix to change
private settopublic setorinternal set - Offer code fix to add missing setter to read-only properties
using Splat;
using static Splat.SplatRegistrations;
// Models
public interface IDatabase { }
public interface ILogger { }
public interface IUserService { }
public class SqliteDatabase : IDatabase { }
public class FileLogger : ILogger { }
public class UserService : IUserService
{
private readonly IDatabase _database;
// Constructor injection
public UserService(IDatabase database)
{
_database = database;
}
// Property injection
[DependencyInjectionProperty]
public ILogger Logger { get; set; }
}
// Application startup
public class Program
{
public static void Main()
{
// Register dependencies
RegisterLazySingleton<IDatabase, SqliteDatabase>();
Register<ILogger, FileLogger>();
Register<IUserService, UserService>();
// Initialize container
SetupIOC();
// Resolve services
var userService = Locator.Current.GetService<IUserService>();
}
}The package includes intelligent analyzers that provide real-time feedback:
| Diagnostic ID | Severity | Description | Code Fix |
|---|---|---|---|
| SPLATDI001 | Warning | Multiple constructors without [DependencyInjectionConstructor] attribute |
Adds attribute to selected constructor |
| SPLATDI002 | Error | Property with [DependencyInjectionProperty] lacks accessible setter |
Changes setter to public or internal |
| SPLATDI003 | Error | Multiple constructors marked with [DependencyInjectionConstructor] |
Manual fix required |
| SPLATDI004 | Error | Constructor marked with [DependencyInjectionConstructor] is not accessible |
Changes to public or internal |
The analyzer detects issues in real-time and offers automatic fixes via Quick Actions (Ctrl+. or Cmd+.).
The source generator follows a four-step process:
- Compile-Time Detection - Scans for
SplatRegistrations.Register()calls during compilation - Metadata Extraction - Analyzes constructor parameters and property injection requirements
- Code Generation - Generates optimized registration code with no reflection
- Incremental Builds - Only regenerates when relevant code changes
Generated code example:
// Generated by Splat.DependencyInjection.SourceGenerator
static partial void SetupIOCInternal(IDependencyResolver resolver)
{
// Transient registration
resolver.Register<IUserService>(() => new UserService(
(IDatabase)resolver.GetService(typeof(IDatabase)),
(ILogger)resolver.GetService(typeof(ILogger))
) {
Navigation = (INavigationService)resolver.GetService(typeof(INavigationService))
});
// Lazy singleton registration
{
var lazy = new Lazy<IDatabase>(() => new SqliteDatabase(),
LazyThreadSafetyMode.ExecutionAndPublication);
resolver.Register<Lazy<IDatabase>>(() => lazy);
resolver.Register<IDatabase>(() => lazy.Value);
}
}Compared to reflection-based DI:
- Approximately 100x faster registration execution (no runtime reflection)
- Approximately 10-100x faster incremental builds (only processes changed files)
- Full AOT support (works with Native AOT and trimming)
- Zero runtime overhead (all work done at compile-time)
Generator doesn't seem to run?
Ensure you called SetupIOC() in your startup code. The generator only produces code for assemblies that use SplatRegistrations.
"Multiple constructors" warning?
Add [DependencyInjectionConstructor] to the constructor you want used. Use the Quick Fix to add automatically.
Property injection not working?
Ensure the property has [DependencyInjectionProperty] and a public or internal setter. The analyzer will warn if the setter is missing or inaccessible.
Lazy dependencies not resolving?
Make sure you registered the dependency with RegisterLazySingleton, not Register. Only lazy singletons can be injected as Lazy<T>.
Version 2.1.1 includes breaking changes:
- Requires Splat 19.1.1 or later for generic-first resolver support
- Migrated from legacy
ISourceGeneratorto modernIIncrementalGenerator - Updated to Roslyn 4.14.0
- Removed support for .NET Standard 2.0 and .NET 6
- Minimum supported frameworks: .NET Framework 4.6.2+, .NET 8+, .NET 9+, .NET 10+
New features in 2.1.1:
- 10-100x faster incremental builds (only processes changed files)
- Cache-friendly pipeline eliminates unnecessary recompilation
- Built-in analyzers with real-time diagnostics and automatic code fixes
- Full Native AOT and trimming support with generic-first API
If you have questions or need help:
- Check existing GitHub Issues
- Ask on Stack Overflow with the
splattag - Join our Slack community
Please do not open GitHub issues for general support questions.
We welcome contributions! Here's how you can help:
- Report Issues: Found a bug? Open an issue
- Submit PRs: Improvements are always welcome
- Documentation: Help improve our examples and docs
- Testing: Add test cases for edge scenarios
See our contribution guidelines for details.
The core team members and contributors work on this project in their free time. If Splat.DI.SourceGenerator increases your productivity, please consider supporting the project:
This project is licensed under the MIT License - see the LICENSE file for details.