Singleton Pattern
Overview
Several parts of a program use the same read-only catalog. Instead of constructing it repeatedly, the program can restrict construction and expose one shared object.
Singleton provides a single instance and an access point to it. Here the constructor is private, and callers obtain the object through Instance.
UML
Callers use AppCatalog.Instance. Its constructor is private, and a static Lazy<AppCatalog> creates the object on first access.
History
GoF cataloged Singleton as a creational pattern in Design Patterns in 1994. It restricts a class to one instance and provides access to it.1
Why use it?
One creation point can suit a shared read-only catalog. Global access also hides a dependency; explicitly creating and injecting an object is easier when tests or callers need different instances.
The scope here is the static field in the same runtime load context. Multiple server processes each have their own instance. This is not a distributed mechanism for preventing duplicate work.
Example
Check whether two accesses return the same object reference.
using System;
using System.Collections.Generic;
using System.Linq;
Console.WriteLine(ReferenceEquals(AppCatalog.Instance, AppCatalog.Instance));
sealed class AppCatalog
{
private static readonly Lazy<AppCatalog> shared = new(() => new AppCatalog());
private AppCatalog() { }
public static AppCatalog Instance => shared.Value;
public string Version => "v1";
}
TrueLazy<T> synchronizes initialization. That does not automatically make mutable operations added to the object thread-safe.
An extended example
Have several tasks access the instance concurrently. The example catalog remains read-only.
using System;
using System.Collections.Generic;
using System.Linq;
var tasks = Enumerable.Range(0, 8)
.Select(_ => System.Threading.Tasks.Task.Run(() => AppCatalog.Instance)).ToArray();
var values = await System.Threading.Tasks.Task.WhenAll(tasks);
Console.WriteLine(values.All(value => ReferenceEquals(value, values[0])));
Console.WriteLine(values[0].Version);
sealed class AppCatalog
{
private static readonly Lazy<AppCatalog> shared = new(() => new AppCatalog());
private AppCatalog() { }
public static AppCatalog Instance => shared.Value;
public string Version => "v1";
}
True
v1These calls share one static field and receive one instance. If configuration must vary by user, first check whether that sharing scope fits the application.
Related reading
Each example is a separate .NET 10 console program.
Footnotes
- Gamma, Helm, Johnson, Vlissides. Design Patterns (1994). Publisher record. Creational patterns, Singleton. β©