@@ -12,7 +12,7 @@ namespace Soenneker.Utils.AsyncSingleton;
12
12
public class AsyncSingleton : IAsyncSingleton
13
13
{
14
14
private object ? _instance ;
15
- private readonly AsyncLock _lock ;
15
+ private readonly AsyncLock _lock = new ( ) ;
16
16
17
17
private Func < ValueTask < object > > ? _asyncFunc ;
18
18
private Func < object > ? _func ;
@@ -64,7 +64,6 @@ public AsyncSingleton(Func<object> func) : this()
64
64
65
65
public AsyncSingleton ( )
66
66
{
67
- _lock = new AsyncLock ( ) ;
68
67
}
69
68
70
69
public ValueTask Init ( params object [ ] objects )
@@ -77,12 +76,12 @@ public async ValueTask Init(CancellationToken cancellationToken, params object[]
77
76
if ( _disposed )
78
77
throw new ObjectDisposedException ( nameof ( AsyncSingleton ) ) ;
79
78
80
- if ( _instance != null )
79
+ if ( _instance is not null )
81
80
return ;
82
81
83
82
using ( await _lock . LockAsync ( cancellationToken ) . ConfigureAwait ( false ) )
84
83
{
85
- if ( _instance != null )
84
+ if ( _instance is not null )
86
85
return ;
87
86
88
87
_instance = await InitInternal ( cancellationToken , objects ) . NoSync ( ) ;
@@ -95,32 +94,32 @@ private async ValueTask<object> InitInternal(CancellationToken cancellationToken
95
94
{
96
95
case InitializationType . AsyncObject :
97
96
if ( _asyncObjectFunc == null )
98
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
97
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
99
98
100
99
return await _asyncObjectFunc ( objects ) . NoSync ( ) ;
101
100
case InitializationType . AsyncObjectToken :
102
101
if ( _asyncObjectTokenFunc == null )
103
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
102
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
104
103
105
104
return await _asyncObjectTokenFunc ( cancellationToken , objects ) . NoSync ( ) ;
106
105
case InitializationType . Async :
107
106
if ( _asyncFunc == null )
108
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
107
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
109
108
110
109
return await _asyncFunc ( ) . NoSync ( ) ;
111
110
case InitializationType . SyncObject :
112
111
if ( _objectFunc == null )
113
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
112
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
114
113
115
114
return _objectFunc ( objects ) ;
116
115
case InitializationType . SyncObjectToken :
117
116
if ( _objectTokenFunc == null )
118
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
117
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
119
118
120
119
return _objectTokenFunc ( cancellationToken , objects ) ;
121
120
case InitializationType . Sync :
122
121
if ( _func == null )
123
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
122
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
124
123
125
124
return _func ( ) ;
126
125
default :
@@ -138,12 +137,12 @@ public void InitSync(CancellationToken cancellationToken, params object[] object
138
137
if ( _disposed )
139
138
throw new ObjectDisposedException ( nameof ( AsyncSingleton ) ) ;
140
139
141
- if ( _instance != null )
140
+ if ( _instance is not null )
142
141
return ;
143
142
144
143
using ( _lock . Lock ( cancellationToken ) )
145
144
{
146
- if ( _instance != null )
145
+ if ( _instance is not null )
147
146
return ;
148
147
149
148
_instance = InitSyncInternal ( cancellationToken , objects ) ;
@@ -156,32 +155,32 @@ private object InitSyncInternal(CancellationToken cancellationToken, params obje
156
155
{
157
156
case InitializationType . AsyncObject :
158
157
if ( _asyncObjectFunc == null )
159
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
158
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
160
159
161
160
return _asyncObjectFunc ( objects ) . NoSync ( ) . GetAwaiter ( ) . GetResult ( ) ;
162
161
case InitializationType . AsyncObjectToken :
163
162
if ( _asyncObjectTokenFunc == null )
164
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
163
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
165
164
166
165
return _asyncObjectTokenFunc ( cancellationToken , objects ) . NoSync ( ) . GetAwaiter ( ) . GetResult ( ) ;
167
166
case InitializationType . Async :
168
167
if ( _asyncFunc == null )
169
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
168
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
170
169
171
170
return _asyncFunc ( ) . NoSync ( ) . GetAwaiter ( ) . GetResult ( ) ;
172
171
case InitializationType . SyncObject :
173
172
if ( _objectFunc == null )
174
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
173
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
175
174
176
175
return _objectFunc ( objects ) ;
177
176
case InitializationType . SyncObjectToken :
178
177
if ( _objectTokenFunc == null )
179
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
178
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
180
179
181
180
return _objectTokenFunc ( cancellationToken , objects ) ;
182
181
case InitializationType . Sync :
183
182
if ( _func == null )
184
- throw new NullReferenceException ( "Initialization func for AsyncSingleton cannot be null" ) ;
183
+ throw new NullReferenceException ( Constants . InitializationFuncError ) ;
185
184
186
185
return _func ( ) ;
187
186
default :
@@ -190,8 +189,8 @@ private object InitSyncInternal(CancellationToken cancellationToken, params obje
190
189
}
191
190
192
191
public void SetInitialization ( Func < object [ ] , ValueTask < object > > func )
193
- {
194
- if ( _instance != null )
192
+ {
193
+ if ( _instance is not null )
195
194
throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
196
195
197
196
_initializationType = InitializationType . AsyncObject ;
@@ -200,7 +199,7 @@ public void SetInitialization(Func<object[], ValueTask<object>> func)
200
199
201
200
public void SetInitialization ( Func < CancellationToken , object [ ] , ValueTask < object > > func )
202
201
{
203
- if ( _instance != null )
202
+ if ( _instance is not null )
204
203
throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
205
204
206
205
_initializationType = InitializationType . AsyncObjectToken ;
@@ -209,7 +208,7 @@ public void SetInitialization(Func<CancellationToken, object[], ValueTask<object
209
208
210
209
public void SetInitialization ( Func < ValueTask < object > > func )
211
210
{
212
- if ( _instance != null )
211
+ if ( _instance is not null )
213
212
throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
214
213
215
214
_initializationType = InitializationType . Async ;
@@ -218,7 +217,7 @@ public void SetInitialization(Func<ValueTask<object>> func)
218
217
219
218
public void SetInitialization ( Func < object [ ] , object > func )
220
219
{
221
- if ( _instance != null )
220
+ if ( _instance is not null )
222
221
throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
223
222
224
223
_initializationType = InitializationType . SyncObject ;
@@ -227,7 +226,7 @@ public void SetInitialization(Func<object[], object> func)
227
226
228
227
public void SetInitialization ( Func < CancellationToken , object [ ] , object > func )
229
228
{
230
- if ( _instance != null )
229
+ if ( _instance is not null )
231
230
throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
232
231
233
232
_initializationType = InitializationType . SyncObjectToken ;
@@ -236,7 +235,7 @@ public void SetInitialization(Func<CancellationToken, object[], object> func)
236
235
237
236
public void SetInitialization ( Func < object > func )
238
237
{
239
- if ( _instance != null )
238
+ if ( _instance is not null )
240
239
throw new Exception ( "Setting the initialization of an AsyncSingleton after it's already has been set is not allowed" ) ;
241
240
242
241
_initializationType = InitializationType . Sync ;
@@ -250,20 +249,26 @@ public void Dispose()
250
249
251
250
_disposed = true ;
252
251
253
- if ( _instance == null )
254
- return ;
252
+ // Capture the instance into a local variable to avoid multiple volatile field accesses.
253
+ object ? localInstance = _instance ;
255
254
256
- switch ( _instance )
255
+ if ( localInstance != null )
257
256
{
258
- case IDisposable disposable :
257
+ // Explicit conditional checks for better predictability in JIT compilation.
258
+ if ( localInstance is IDisposable disposable )
259
+ {
259
260
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.
262
265
asyncDisposable . DisposeAsync ( ) . NoSync ( ) . GetAwaiter ( ) . GetResult ( ) ;
263
- break ;
266
+ }
267
+
268
+ // Clear the instance explicitly to allow for garbage collection.
269
+ _instance = null ;
264
270
}
265
271
266
- _instance = null ;
267
272
GC . SuppressFinalize ( this ) ;
268
273
}
269
274
@@ -274,20 +279,28 @@ public async ValueTask DisposeAsync()
274
279
275
280
_disposed = true ;
276
281
277
- if ( _instance == null )
282
+ object ? localInstance = _instance ;
283
+
284
+ if ( localInstance == null )
285
+ {
286
+ GC . SuppressFinalize ( this ) ;
278
287
return ;
288
+ }
279
289
280
- switch ( _instance )
290
+ // Check for IAsyncDisposable first to avoid unnecessary interface casting
291
+ if ( localInstance is IAsyncDisposable asyncDisposable )
281
292
{
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 ( ) ;
288
299
}
289
300
301
+ // Clear the instance reference explicitly to assist GC
290
302
_instance = null ;
303
+
291
304
GC . SuppressFinalize ( this ) ;
292
305
}
293
306
}
0 commit comments