@@ -12,7 +12,7 @@ namespace Soenneker.Utils.AsyncSingleton;
1212public class AsyncSingleton : IAsyncSingleton
1313{
1414 private object ? _instance ;
15- private readonly AsyncLock _lock ;
15+ private readonly AsyncLock _lock = new ( ) ;
1616
1717 private Func < ValueTask < object > > ? _asyncFunc ;
1818 private Func < object > ? _func ;
@@ -64,7 +64,6 @@ public AsyncSingleton(Func<object> func) : this()
6464
6565 public AsyncSingleton ( )
6666 {
67- _lock = new AsyncLock ( ) ;
6867 }
6968
7069 public ValueTask Init ( params object [ ] objects )
@@ -77,12 +76,12 @@ public async ValueTask Init(CancellationToken cancellationToken, params object[]
7776 if ( _disposed )
7877 throw new ObjectDisposedException ( nameof ( AsyncSingleton ) ) ;
7978
80- if ( _instance != null )
79+ if ( _instance is not null )
8180 return ;
8281
8382 using ( await _lock . LockAsync ( cancellationToken ) . ConfigureAwait ( false ) )
8483 {
85- if ( _instance != null )
84+ if ( _instance is not null )
8685 return ;
8786
8887 _instance = await InitInternal ( cancellationToken , objects ) . NoSync ( ) ;
@@ -95,32 +94,32 @@ private async ValueTask<object> InitInternal(CancellationToken cancellationToken
9594 {
9695 case InitializationType . AsyncObject :
9796 if ( _asyncObjectFunc == null )
98- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
97+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
9998
10099 return await _asyncObjectFunc ( objects ) . NoSync ( ) ;
101100 case InitializationType . AsyncObjectToken :
102101 if ( _asyncObjectTokenFunc == null )
103- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
102+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
104103
105104 return await _asyncObjectTokenFunc ( cancellationToken , objects ) . NoSync ( ) ;
106105 case InitializationType . Async :
107106 if ( _asyncFunc == null )
108- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
107+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
109108
110109 return await _asyncFunc ( ) . NoSync ( ) ;
111110 case InitializationType . SyncObject :
112111 if ( _objectFunc == null )
113- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
112+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
114113
115114 return _objectFunc ( objects ) ;
116115 case InitializationType . SyncObjectToken :
117116 if ( _objectTokenFunc == null )
118- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
117+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
119118
120119 return _objectTokenFunc ( cancellationToken , objects ) ;
121120 case InitializationType . Sync :
122121 if ( _func == null )
123- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
122+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
124123
125124 return _func ( ) ;
126125 default :
@@ -138,12 +137,12 @@ public void InitSync(CancellationToken cancellationToken, params object[] object
138137 if ( _disposed )
139138 throw new ObjectDisposedException ( nameof ( AsyncSingleton ) ) ;
140139
141- if ( _instance != null )
140+ if ( _instance is not null )
142141 return ;
143142
144143 using ( _lock . Lock ( cancellationToken ) )
145144 {
146- if ( _instance != null )
145+ if ( _instance is not null )
147146 return ;
148147
149148 _instance = InitSyncInternal ( cancellationToken , objects ) ;
@@ -156,32 +155,32 @@ private object InitSyncInternal(CancellationToken cancellationToken, params obje
156155 {
157156 case InitializationType . AsyncObject :
158157 if ( _asyncObjectFunc == null )
159- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
158+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
160159
161160 return _asyncObjectFunc ( objects ) . NoSync ( ) . GetAwaiter ( ) . GetResult ( ) ;
162161 case InitializationType . AsyncObjectToken :
163162 if ( _asyncObjectTokenFunc == null )
164- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
163+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
165164
166165 return _asyncObjectTokenFunc ( cancellationToken , objects ) . NoSync ( ) . GetAwaiter ( ) . GetResult ( ) ;
167166 case InitializationType . Async :
168167 if ( _asyncFunc == null )
169- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
168+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
170169
171170 return _asyncFunc ( ) . NoSync ( ) . GetAwaiter ( ) . GetResult ( ) ;
172171 case InitializationType . SyncObject :
173172 if ( _objectFunc == null )
174- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
173+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
175174
176175 return _objectFunc ( objects ) ;
177176 case InitializationType . SyncObjectToken :
178177 if ( _objectTokenFunc == null )
179- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
178+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
180179
181180 return _objectTokenFunc ( cancellationToken , objects ) ;
182181 case InitializationType . Sync :
183182 if ( _func == null )
184- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
183+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
185184
186185 return _func ( ) ;
187186 default :
@@ -190,8 +189,8 @@ private object InitSyncInternal(CancellationToken cancellationToken, params obje
190189 }
191190
192191 public void SetInitialization ( Func < object [ ] , ValueTask < object > > func )
193- {
194- if ( _instance != null )
192+ {
193+ if ( _instance is not null )
195194 throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
196195
197196 _initializationType = InitializationType . AsyncObject ;
@@ -200,7 +199,7 @@ public void SetInitialization(Func<object[], ValueTask<object>> func)
200199
201200 public void SetInitialization ( Func < CancellationToken , object [ ] , ValueTask < object > > func )
202201 {
203- if ( _instance != null )
202+ if ( _instance is not null )
204203 throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
205204
206205 _initializationType = InitializationType . AsyncObjectToken ;
@@ -209,7 +208,7 @@ public void SetInitialization(Func<CancellationToken, object[], ValueTask<object
209208
210209 public void SetInitialization ( Func < ValueTask < object > > func )
211210 {
212- if ( _instance != null )
211+ if ( _instance is not null )
213212 throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
214213
215214 _initializationType = InitializationType . Async ;
@@ -218,7 +217,7 @@ public void SetInitialization(Func<ValueTask<object>> func)
218217
219218 public void SetInitialization ( Func < object [ ] , object > func )
220219 {
221- if ( _instance != null )
220+ if ( _instance is not null )
222221 throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
223222
224223 _initializationType = InitializationType . SyncObject ;
@@ -227,7 +226,7 @@ public void SetInitialization(Func<object[], object> func)
227226
228227 public void SetInitialization ( Func < CancellationToken , object [ ] , object > func )
229228 {
230- if ( _instance != null )
229+ if ( _instance is not null )
231230 throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
232231
233232 _initializationType = InitializationType . SyncObjectToken ;
@@ -236,7 +235,7 @@ public void SetInitialization(Func<CancellationToken, object[], object> func)
236235
237236 public void SetInitialization ( Func < object > func )
238237 {
239- if ( _instance != null )
238+ if ( _instance is not null )
240239 throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
241240
242241 _initializationType = InitializationType . Sync ;
@@ -250,20 +249,26 @@ public void Dispose()
250249
251250 _disposed = true ;
252251
253- if ( _instance == null )
254- return ;
252+ // Capture the instance into a local variable to avoid multiple volatile field accesses.
253+ object ? localInstance = _instance ;
255254
256- switch ( _instance )
255+ if ( localInstance != null )
257256 {
258- case IDisposable disposable :
257+ // Explicit conditional checks for better predictability in JIT compilation.
258+ if ( localInstance is IDisposable disposable )
259+ {
259260 disposable . Dispose ( ) ;
260- break ;
261- case IAsyncDisposable asyncDisposable :
261+ }
262+ else if ( localInstance is IAsyncDisposable asyncDisposable )
263+ {
264+ // Handle async disposal in a synchronous context.
262265 asyncDisposable . DisposeAsync ( ) . NoSync ( ) . GetAwaiter ( ) . GetResult ( ) ;
263- break ;
266+ }
267+
268+ // Clear the instance explicitly to allow for garbage collection.
269+ _instance = null ;
264270 }
265271
266- _instance = null ;
267272 GC . SuppressFinalize ( this ) ;
268273 }
269274
@@ -274,20 +279,28 @@ public async ValueTask DisposeAsync()
274279
275280 _disposed = true ;
276281
277- if ( _instance == null )
282+ object ? localInstance = _instance ;
283+
284+ if ( localInstance == null )
285+ {
286+ GC . SuppressFinalize ( this ) ;
278287 return ;
288+ }
279289
280- switch ( _instance )
290+ // Check for IAsyncDisposable first to avoid unnecessary interface casting
291+ if ( localInstance is IAsyncDisposable asyncDisposable )
281292 {
282- case IAsyncDisposable asyncDisposable :
283- await asyncDisposable . DisposeAsync ( ) . NoSync ( ) ;
284- break ;
285- case IDisposable disposable :
286- disposable . Dispose ( ) ;
287- break ;
293+ // Await using ConfigureAwait(false) to minimize synchronization context capture
294+ await asyncDisposable . DisposeAsync ( ) . NoSync ( ) ;
295+ }
296+ else if ( localInstance is IDisposable disposable )
297+ {
298+ disposable . Dispose ( ) ;
288299 }
289300
301+ // Clear the instance reference explicitly to assist GC
290302 _instance = null ;
303+
291304 GC . SuppressFinalize ( this ) ;
292305 }
293306}
0 commit comments