Singleton
Concept
Generally, Singleton
is the only instance exists during a AppDomain
or a Thread
.
Basic Implementation
class Singleton
{
private static readonly Singleton _instance = new();
public static Singleton Instance => _instance.Value;
private Singleton() { }
// ...elided
}
2
3
4
5
6
7
8
Lazy Initialization
Lazy initialization ensures the instance won't be created until it were accessed, and Lazy<T>
also ensures thread safety. Pass a delegate to Lazy<T>.Lazy(Func<T>)
to generate its value.
class Singleton
{
private static readonly Lazy<Singleton> _instance = new(() => new());
public static Singleton Instance => _instance.Value;
private Singleton() { }
// ...elided
}
2
3
4
5
6
7
8
Singleton
is a BAD idea?
Testablity issue
Singleton is hard to test, using Singleton of a type means hard coding it in your code base, not flexible enough.
- Use
interface
to abstract singleton usage to enhance your code.
Dependency Injection
The best practice is using Dependency Injection to generate an singleton in runtime instead of coding on your own. Each call to container.Resolve<T>()
will return a shared instance of the specified type in the same thread. With this approach, we don't need to code our singleton on our own, but constructor of target type shall be public
, or the IOC framework can not access it during the runtime.
- Following example uses
Autofac
to perform dependency injection.
class OrdinaryClass
{
public OrdinaryClass() { }
// ...elided
}
public class DependencyInjectionTest
{
[Fact]
public void DependencyInjection()
{
ContainerBuilder builder = new();
builder.RegisterType<OrdinaryClass>().SingleInstance();
using var container = builder.Build();
Assert.True(container.Resolve<OrdinaryClass>() is not null);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Mono-state Singleton
Mono-state Singleton allows you to access its constructor, but all state of this type are static, all instances shares the same states. Not a good approach though.
class MonoStateSingleton
{
private static int _state01 = 0x0;
private static int _state02 = 0x64;
public int State01 { get => _state01; set => _state01 = value; }
public int State02 { get => _state02; set => _state02 = value; }
// ...elided
}
2
3
4
5
6
7
8
9
10
Thread Singleton
Use ThreadLocal<T>
to make object thread safe and static for each thread. Each thread has a different singleton
class ThreadSingleton
{
private static readonly ThreadLocal<ThreadSingleton> _instance = new(() => new());
private int _threadAccessedCount;
public static ThreadSingleton Instance => _instance.Value!;
public int ThreadAccessedCount => ++_threadAccessedCount;
private ThreadSingleton() { }
//...elided
}
2
3
4
5
6
7
8
9
10
Ambient Context
Ambient Context is similar to singleton pattern, one of its advantage is, it allows you to change the static value/state of the type during the procedure. And in some cases we don't have to preserve a parameter position for a method that access the AmbientContext type. When IDisposable
is implemented by AmbientContext
, using
statement will auto invoke Dispose()
, this can be used to restore the state depending on how you implement the Dispose()
method.
class AmbientContext : IDisposable
{
private static int _currentState;
public static int CurrentState { get => _currentState; private set => _currentState = value; }
private static readonly ThreadLocal<AmbientContext> _context = new(() => new());
public static AmbientContext CurrentContext { get => _context.Value!; set => _context.Value = value; }
public AmbientContext(int state = 250) => _currentState = state;
public void Dispose() => _currentState = 250;
//...elided
}
public class AmbientContextTest
{
[Fact]
public void Test()
{
AmbientContext.CurrentContext = new AmbientContext();
DoSomethingUsingAmbientContext();
using (var context = new AmbientContext(500))
{
Assert.Equal(500, AmbientContext.CurrentState);
}
Assert.Equal(250, AmbientContext.CurrentState);
static void DoSomethingUsingAmbientContext() =>
Console.WriteLine($"state is {AmbientContext.CurrentState}");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30