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
                    
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="Splat.Builder" Version="21.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Splat.Builder" Version="21.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Splat.Builder" />
                    
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 Splat.Builder --version 21.0.0
                    
#r "nuget: Splat.Builder, 21.0.0"
                    
#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 Splat.Builder@21.0.0
                    
#: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=Splat.Builder&version=21.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Splat.Builder&version=21.0.0
                    
Install as a Cake Tool

NuGet Stats Build Code Coverage <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

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)

For application authors

  • Use GlobalGenericFirstDependencyResolver if:
    • You have a single application-wide container