What’s the singleton pattern?
A singleton pattern is an object creation pattern where only one instance of the object can ever be created in the lifetime of the application. In C#, this would be akin to having a static class as that would ensure there is only one instnace of the class for the application’s lifetime.
public sealed class MessageService {
public static MessageService SingletonMessageService { get; } = new MessageService(); // Auto implemented properties allow us to declare the singleton service in this manner
private MessageService() { }
}
With the above example, there would always be only one instance of MessageService
and the only way to use it is by calling it through MessageService.SingletonMessageService
. The constructor for MessageService
has been intentionally made private such that new instances of the class cannot be created. Without the private constructor, we can always createa a new instance of MessageService
by doing var messageService = new MessageService()
.
Why use the singleton pattern?
The singleton pattern can be useful to use if we know the creation of the instance will take a lot of resources. An example of this in .NET would be the HttpClient
class. It’s recommended to have a single instance of the HttpClient
class for the lifetime of the application. Having too many HttpClient
instances can issues with as every time it is creeated, it’ll be attached to a socket. Instead of creating HttpClient
every time, we can use the singleton pattern for example to have a static instance of the HttpClient
.
public sealed class MessageService {
public static MessageService SingletonMessageService { get; } = new MessageService();
private HttpClient _client = new HttpClient() { /* With some configuration*/};
private MessageService() { }
public Task<string> GetMessageFrom(string url) {
await client.GetStringAsync(url);
}
}
Are singletons thread safe?
Without a few tweaks, the singleton pattern might not be thread safe. In the above example, if multiple threads call SingletonMessageService.GetMessageFrom(url)
, this might cause us issues if multiple threads are trying to use different URLs. One way we can resovle this is by placing a lock on the method that ensures only one thread can ever use the client at one ago.
public sealed class MessageService {
public static MessageService SingletonMessageService { get; } = new MessageService();
private HttpClient _client = new HttpClient() { /* With some configuration*/};
private object _lock = new object();
private MessageService() { }
public Task<string> GetMessageFrom(string url) {
lock(_lock) {
// The lock is useful to ensure that only one thread can ever use the client.GetStringAsync();
await client.GetStringAsync(url);
}
}
}
Final thoughts
Singleton patterns can be used to ensure only one instance of an object is created during the application’s lifetime. Locks can then be placed on methods to ensure multiple threads can safely call the methods.