Splat.Builder
21.0.0
Prefix Reserved
dotnet add package Splat.Builder --version 21.0.0
NuGet\Install-Package Splat.Builder -Version 21.0.0
<PackageReference Include="Splat.Builder" Version="21.0.0" />
<PackageVersion Include="Splat.Builder" Version="21.0.0" />
<PackageReference Include="Splat.Builder" />
paket add Splat.Builder --version 21.0.0
#r "nuget: Splat.Builder, 21.0.0"
#:package Splat.Builder@21.0.0
#addin nuget:?package=Splat.Builder&version=21.0.0
#tool nuget:?package=Splat.Builder&version=21.0.0
<br>
<a href="https://www.nuget.org/packages/splat">
<img src="https://img.shields.io/nuget/dt/splat.svg">
</a>
<a href="https://reactiveui.net/slack">
<img src="https://img.shields.io/badge/chat-slack-blue.svg">
</a>
<img src="https://github.com/reactiveui/styleguide/blob/master/logo_splat/logo.png?raw=true" width="200" />
Splat
Table of Contents
- What does it do?
- How do I install?
- Detecting whether you're in a unit test runner
- Service Location
- Logging
- Cross platform drawing
- Cross-platform Image Loading
- Detecting if you're in design mode
- Application Performance Monitoring
- Dependency Resolver Performance Benchmarks
- Contribute
Certain types of things are basically impossible to do in cross-platform
mobile code today, yet there's no reason why. Writing a ViewModel that handles
loading a gallery of pictures from disk will be completely riddled with
#ifdefs and basically unreadable.
Splat aims to fix that, by providing a usable leaky abstraction above platform
code. It is leaky, because it always provides an extension method ToNative()
and FromNative(), which converts the abstraction to the platform-specific
version. Load the image in the cross-platform code, then call ToNative() in
your view to actually display it.
What does it do?
Splat currently supports:
- Cross-platform image loading/saving
- A port of System.Drawing.Color for portable libraries
- Cross-platform geometry primitives (PointF, SizeF, RectangleF), as well as a bunch of additional extension methods to make using them easier.
- A way to detect whether you're in a Unit Test runner / Design Mode
- A cross-platform logging framework
- Simple yet flexible Service Location
Core Team
<table> <tbody> <tr> <td align="center" valign="top"> <img width="100" height="100" src="https://github.com/glennawatson.png?s=150"> <br> <a href="https://github.com/glennawatson">Glenn Watson</a> <p>Melbourne, Australia</p> </td> <td align="center" valign="top"> <img width="100" height="100" src="https://github.com/rlittlesii.png?s=150"> <br> <a href="https://github.com/rlittlesii">Rodney Littles II</a> <p>Texas, USA</p> </td> </tr> <tr> <td align="center" valign="top"> <img width="100" height="100" src="https://github.com/dpvreony.png?s=150"> <br> <a href="https://github.com/dpvreony">David Vreony</a> <p>UK</p> </td> <td align="center" valign="top"> <img width="100" height="100" src="https://github.com/chrispulman.png?s=150"> <br> <a href="https://github.com/chrispulman">Chris Pulman</a> <p>UK</p> </td> </tr> </tbody> </table>
How do I install?
Always Be NuGetting. Package contains binaries for:
- .NET Framework 4.6.2, .NET Framework 4.7.2, .NET Standard 2.0, .NET 6.0, and .NET 8.0
- Works with:
- WPF
- Windows Forms
- WinUI 3
- Maui (WinUI, Android, iOS and Mac)
- Avalonia
Detecting whether you're in a unit test runner
// If true, we are running unit tests
ModeDetector.InUnitTestRunner();
Service Location
Splat provides a simple service location implementation that is optimized for Desktop and Mobile applications, while still remaining reasonably flexible.
There are 2 parts to the locator design:
- AppLocator.Current The property to use to retrieve services. AppLocator.Current is a static variable that can be set on startup, to adapt Splat to other DI/IoC frameworks. We're currently working from v7 onward to make it easier to use your DI/IoC framework of choice. (see below)
- AppLocator.CurrentMutable The property to use to register services
To get a service:
// To get a single service registration
var toaster = AppLocator.Current.GetService<IToaster>();
// To get all service registrations
var allToasterImpls = AppLocator.Current.GetServices<IToaster>();
Locator.Current is a static variable that can be set on startup, to adapt Splat to other DI/IoC frameworks. We're currently working from v7 onward to make it easier to use your DI/IoC framework of choice.
The default implementation of Service Location also allows new types to be registered at runtime.
// Create a new Toaster any time someone asks
Locator.CurrentMutable.Register(() => new Toaster(), typeof(IToaster));
// Register a singleton instance
Locator.CurrentMutable.RegisterConstant(new ExtraGoodToaster(), typeof(IToaster));
// Register a singleton which won't get created until the first user accesses it
Locator.CurrentMutable.RegisterLazySingleton(() => new LazyToaster(), typeof(IToaster));
The Default Dependency Resolver (v19+)
Starting with v19, Splat provides two high-performance resolver implementations optimized for AOT compilation: GlobalGenericFirstDependencyResolver and InstanceGenericFirstDependencyResolver. Both deliver significantly better performance than the legacy ModernDependencyResolver while supporting different isolation requirements. By default InstanceGenericFirstDependencyResolver is now the default container.
Note:
ModernDependencyResolver remains supported for backward compatibility, but new applications should strongly prefer one of the GenericFirst resolvers.
| Feature | GlobalGenericFirstDependencyResolver | InstanceGenericFirstDependencyResolver |
|---|---|---|
| Container Storage | Process-wide static containers | Per-resolver instance containers (ConditionalWeakTable) |
| Isolation | Shared across all resolver instances | Isolated per resolver instance |
| Performance | Fastest - direct static access | Very fast - one additional CWT lookup |
| Memory | Minimal - static fields only | Low - weak references, GC-friendly |
| Use Case | Single global service locator | Multiple independent resolvers, testing scenarios |
| Thread Safety | Lock-free reads, thread-safe writes | Lock-free reads, thread-safe writes |
| AOT Compatible | Yes | Yes |
Quick Start for New Users
The service locator is simple to use - register services at startup, then resolve them when needed:
// Register services at application startup
Locator.CurrentMutable.Register<IToaster>(() => new Toaster());
Locator.CurrentMutable.RegisterConstant<IConfiguration>(myConfig);
Locator.CurrentMutable.RegisterLazySingleton<ILogger>(() => new FileLogger());
// Resolve services anywhere in your application
var toaster = Locator.Current.GetService<IToaster>();
var config = Locator.Current.GetService<IConfiguration>();
Key Concepts:
- Locator.CurrentMutable - Use this to register services during initialization
- Locator.Current - Use this to retrieve services during runtime
- Contracts - Optional named registrations when you need multiple implementations:
Register<IToaster>(() => new FastToaster(), "Fast") - Lazy Singletons - Services created once on first access:
RegisterLazySingleton<T> - Constants - Pre-created singleton instances:
RegisterConstant<T>
Choosing Between Global and Instance Resolvers
Splat provides two resolver implementations with identical APIs but different tradeoffs around performance and state isolation.
Short version
- GlobalGenericFirstDependencyResolver is the fastest option.
- InstanceGenericFirstDependencyResolver is easier to reason about and safer for libraries and tests.
Performance vs Isolation Tradeoff
The Global resolver achieves its speed by sharing static generic containers across the process. This is intentional and safe, but it means registrations are visible globally.
If global container state feels confusing or risky for your use case, choose the Instance resolver. The performance difference is measurable but rarely user-visible outside of very hot paths.
Performance Characteristics
Based on internal benchmarks using realistic application workloads:
- Global is approximately 25–30% faster than Instance in mixed read/write scenarios.
- Instance adds a small but measurable overhead due to per-resolver isolation.
- Both resolvers are orders of magnitude faster than the legacy
ModernDependencyResolver.
For most applications, both are “fast enough”. The deciding factor is how much shared global state you are willing to expose to users.
The table below summarizes the practical performance and behavioral differences between Splat’s dependency resolvers, based on internal benchmarks using .NET 10 and realistic application workloads.
| Operation / Concern | GlobalGenericFirst | InstanceGenericFirst | ModernDependencyResolver |
|---|---|---|---|
| Service resolution (hit) | Fastest | Very fast (slightly slower than Global) | Slow |
| Service resolution (miss) | Fastest | Very fast (slightly slower than Global) | Very slow |
| Mixed workload (real application usage) | Fastest | ~25–30% slower than Global | ~3–4× slower |
| Bulk registration (startup) | Fastest | ~25–30% slower | Very slow (O(n²)) |
| Memory allocations | Lowest | Low (slightly higher than Global) | Very high |
| Scales to hundreds of services | Yes | Yes | Poor |
| Container state | Process-wide shared | Isolated per resolver instance | Isolated per resolver instance |
| Test isolation | Requires manual cleanup | Automatic | Automatic |
| AOT / NativeAOT support | Full | Full | Poor |
| Recommended for new code | Yes | Yes | No |
Behavioral Differences
| Aspect | Global | Instance |
|---|---|---|
| Container state | Shared process-wide | Isolated per resolver |
| Surprise factor | Higher | Lower |
| Test isolation | Requires manual cleanup | Automatic |
| Performance | Fastest | Slightly slower |
| Debuggability | Harder (implicit coupling) | Easier (explicit ownership) |
Recommended Defaults
For application authors
- Use GlobalGenericFirstDependencyResolver if:
- You have a single application-wide container