Saturday, 25 June 2022

Generic Singleton that allows initing a type T once

I have looked into a simple design pattern today and decided to have a go with a generic implementation of this. The class is sealed with a private constructor. It will for a generic pattern of type T instantiate a 'Singleton', i.e. same objects will be returned via the static Instance property for a given type T. It is also possible to 'Init' to a custom object in case you want to set the Singleton object to a custom inited object of type T. If you skip initing call - this will just use the parameterless
constructor. But you can only do a call to Init once (i.e. call the Init method) , so do this initing only in the application startup code for example. Of course, we could instead create a more distilled version, disallowing 'Initing' singleton objects not allowing sending in an adjusted instance of type T as the inited singleton - and also Singleton is considered a pattern which should be implemented per class and not in a generic manner like this. Also, it violates the Single responsibility principle
as the object of type T has not control of how in instantiates itself. But Singleton pattern is an accepted creational pattern.
 

 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()
        {   
        }
    }
 
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 - as mentioned it will call the default public constructor of type T and set an this 'default' instance of T as the singleton for type T. Possible you could have a way of setting a custom constructor here instead of passing an object as a sort of improved 'factory pattern'. We could for example in the Init method specify which constructor method
to call in the initing and pass in parameters for example.
 
var aeroplaneDefaultInstantiated = Singleton<Aeroplane>.Instance; 
 
Note : Default instantiation - calls the parameterless public constructor of type T. So you must do the initialization inside the public parameterless constructor if you skip the Init method. We can also allow sending in a custom constructor to the Singleton class by offering another init method. Here, we can send in parameters of given types and therefore identify the constructor to call. This of course demands that such a constructor exists. It offers another way of setting a singleton object. We now can either set the singleton object for type T via :
  • An instance that calls the default parameterless constructor and sets this object as the singleton for type T
  • A customized instance in case you want to have more fine tuned initalized object to be set as the singleton for type T
  • An instance that calls a specified constructor of the type T and sets the created instance as the singleton for type T
Sample I tested this out with (dump method calls below are done inside Linqpad 7 - so paste this code into there) :
 
void Main()
{
	Singleton<Aeroplane>.Init(new object[] { "Nieuport IV", 1911 });
	//Singleton<Aeroplane>.Init(new object[] { "Nieuport V", 1914 });

	var aeroplaneTwo = Singleton<Aeroplane>.Instance;
	var aeroplaneThree = Singleton<Aeroplane>.Instance;
	Object.ReferenceEquals(aeroplaneTwo, aeroplaneThree).Dump();
	aeroplaneTwo.Dump();
	aeroplaneThree.Dump();
}

public class Aeroplane
{
	public string? Model { get; set; }
	public string? Manufacturer { get; set; }
	public int YearBuilt { get; set; }
	public int PassengerCount { get; set; }
	
	public Aeroplane()
	{
			
	}
	
	public Aeroplane(string model, int yearBuilt)
	{
		Model = model; 
		YearBuilt = yearBuilt; 
		if (YearBuilt < 1913) {
			PassengerCount = 1; 
		}
	}
}


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 void Init(object[] constructorParams)
	{
		if (_instanceObj?.IsValueCreated == true)
		{
			throw new ArgumentException($"A Singleton for the type <{typeof(T).Name}> is already set");
		}
		var constructor = typeof(T).GetConstructor(constructorParams.Select(p => p.GetType()).ToArray());
		if (constructor == null)
		{
			string typenamesParams = string.Join(",", constructorParams.Select(p => p.GetType()));
			throw new ArgumentException($"Could not find a constructor of type {typeof(T).Name} with the parameter types {typenamesParams}"); 
		}
		var instanceObj = constructor.Invoke(constructorParams); 
		_instanceObj = new Lazy<T>(instanceObj as T); 

	}


	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()
	{
	}
}

 
Once more, we disallow calling the Init method many times, here we call a specific constructor to init as the Singleton object.

1 comment:

  1. Paste the code into Linqpad 7 using 'C# program' and hit F5 to run. It will first create an aeroplane object via calling the Init method accepting constructor parameters, which will via reflection identity which constructor to invoke and create an intance of type T to set as the Singleton. As you can see, I test for object reference equality here (which returns 'true' = success) and if you uncomment my line trying to call Init twice, you will get an argument exception stating that Init has already been called.

    I am not sure if you want to do this though in C# code, as there are several good alternatives in for example .NET - registering singletons in the startup code for example via dependency container. But you might want to customize a Singleton constructor in your code and the code I have shown is thread safe as it uses the Lazy actively throughout the code.

    ReplyDelete