An externally initializing singleton dictionary that uses double-check asynchronous locking, with optional async and sync disposal
$ dotnet add package Soenneker.Utils.SingletonDictionary
Soenneker.Utils.SingletonDictionaryAsyncLock from Nito.AsyncExdotnet add package Soenneker.Utils.SingletonDictionary
Here’s an example using SingletonDictionary to manage singleton HttpClient instances keyed by configuration (e.g. timeout):
public class HttpRequester : IDisposable, IAsyncDisposable
{
private readonly SingletonDictionary<HttpClient> _clients;
public HttpRequester()
{
_clients = new SingletonDictionary<HttpClient>((args) =>
{
var socketsHandler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
MaxConnectionsPerServer = 10
};
var client = new HttpClient(socketsHandler)
{
Timeout = TimeSpan.FromSeconds((int)args[0])
};
return client;
});
}
public async ValueTask Get()
{
var client = await _clients.Get("100", 100);
await client.GetAsync("https://google.com");
}
public async ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
await _clients.DisposeAsync();
}
public void Dispose()
{
GC.SuppressFinalize(this);
_clients.Dispose();
}
}
SingletonDictionary<T> is backed by a ConcurrentDictionary<string, T> and supports:
SetInitialization before first useExample constructor overloads include:
new SingletonDictionary<T>(Func<string, object[], ValueTask<T>> factory);
new SingletonDictionary<T>(Func<object[], T> factory);
new SingletonDictionary<T>(Func<string, CancellationToken, object[], ValueTask<T>> factory);
// And more...
You can also initialize manually:
var dict = new SingletonDictionary<MyService>();
dict.SetInitialization((args) => new MyService(args));
This library uses AsyncLock for safe concurrent access in async contexts, and synchronously via Lock() for blocking methods. This avoids race conditions and guarantees safe singleton creation.