-
Couldn't load subscription status.
- Fork 1.4k
Description
This issue relates to the Singleton<T> type defined in Microsoft.Toolkit.Helpers.Singleton.cs.
I'm wondering whether having this type in the toolkit at all is a good idea, considering it can be fully replaced by only using built-in C# language features, which also result in a faster execution.
The current implementation of the Singleton<T> class is as follows:
public static class Singleton<T>
where T : new()
{
// Use ConcurrentDictionary for thread safety.
private static ConcurrentDictionary<Type, T> _instances = new ConcurrentDictionary<Type, T>();
/// <summary>
/// Gets the instance of the Singleton class.
/// </summary>
public static T Instance
{
get
{
// Safely creates the first instance or retrieves the existing instance across threads.
return _instances.GetOrAdd(typeof(T), (t) => new T());
}
}
}This works, but there are a few issues with this implementation:
- That
_instancesfield is within a generic type! This means that for eachSingleton<T>type, that field will be different, and an entirely newConcurrentDictionary<Type, T>instance will be built. Furthermore, since eachTinstance is only created once, it means that that dictionary will never really be used at all. When theInstanceproperty is accessed,_instanceswill always either be empty, or with a single object of typeT, since that's the one being looked for in the currentSingleton<T>type. Might as well just use a statically initialized field. - The thread-safety here is guaranteed by that
_instancebeing statically initialized, since the static class constructor is run under a lock (see here and here). But this means that the same thread-safe behavior could be achieved without the need of aSingleton<T>class in the first place.
Consider this snippet:
public class MyClass
{
public static MyClass Instance { get; } = new MyClass();
}This is translated to (see here):
public class MyClass
{
[CompilerGenerated]
private static readonly MyClass <Instance>k__BackingField = new MyClass();
public static MyClass Instance
{
[CompilerGenerated]
get
{
return <Instance>k__BackingField;
}
}
}Which again, causes the C# compiler to emit this code in the static constructor for MyClass:
.method private hidebysig specialname rtspecialname static
void .cctor () cil managed
{
newobj instance void MyClass::.ctor()
stsfld class MyClass MyClass::'<Instance>k__BackingField'
ret
}And this static constructor (see linked issues above) is run under a lock in a given app-domain and only executed once, This is all that's necessary to ensure that a given T instance is only created once when the static T Instance property is accessed the first time (since doing so triggers the static constructor).
Suggestions:
- Remove the
Singleton<T>class entirely from the toolkit. - Provide docs to inform developers of this built-in technique to implement a
T Instancefield for the singleton pattern, without the need of additional APIs at all.