In many solutions today, people use service lifetime of singleton with dependency injection, as .NET offers this out of the box. If you still want to create a generic singleton pattern in your code where you might also consider initializing the type T to a initialized singleton object, 'settable once' and thread safe, here is a possible way to do it.
public sealed class Singleton<T> where T : class, new()
{
private static Lazy<T> InstanceProxy
{
get
{
if (_instanceObj?.IsValueCreated != true)
{
_instanceObj = new Lazy<T>(() => new T());
}
return _instanceObj;
}
}
private static Lazy<T>? _instanceObj;
public static T Instance { get { return InstanceProxy.Value; } }
public static void Init(Lazy<T> instance)
{
if (_instanceObj?.IsValueCreated == true)
{
throw new ArgumentException($"A Singleton for the type <T> is already set");
}
_instanceObj = instance ?? throw new ArgumentNullException(nameof(instance));
}
private Singleton()
{
}
}
The class is sealed and with a private constructor, it accepts types which are classes and must offer a public parameterless constructor 'new'. It uses the Lazy to achieve built in thread safety. You can init also the type T Singleton object for convenience. It is only allowed if a Singleton is not first set. Obviously, you should only init a Singleton of type T early on in your program, such as when the application or service / API starts up. The code will throw an ArgumentException if the Init method is called twice or more times for the type T.
You can use it like this :
Some model class :
public class Aeroplane
{
public string? Model { get; set; }
public string? Manufacturer { get; set; }
public int YearBuilt { get; set; }
public int PassengerCount { get; set; }
}
Usage sample :
var aeroplane = new Aeroplane
{
Manufacturer = "Boeing",
Model = "747",
PassengerCount = 350,
YearBuilt = 2005
};
var aeroPlane3 = Singleton<Aeroplane>.Instance;
var aeroPlane4 = Singleton<Aeroplane>.Instance;
Console.WriteLine($"Aeroplane3 and aeroplane4 is same object? {Object.ReferenceEquals(aeroPlane3, aeroPlane4)}");
Outputs 'true'.
Trying to re-init type T Singleton to another object fails :
var aeroplane2 = new Aeroplane
{
Manufacturer = "Sopwith Aviation Company",
Model = "Sophwith Camel",
PassengerCount = 1,
YearBuilt = 1917
};
Singleton<Aeroplane>.Init(new Lazy<Aeroplane>(aeroplane2));
You can of course just access the Singleton with initing it - it will call the default public constructor. Possible you could have a way of setting a custom constructor here instead of passing an object as a sort of 'factory pattern'.
var aeroplaneDefaultInstantiated = Singleton<Aeroplane>.Instance;
Default instantiation - calls the parameterless public constructor of type T.