@@ -16,6 +16,7 @@ public class OptionsCache<[DynamicallyAccessedMembers(Options.DynamicallyAccesse
1616 where TOptions : class
1717 {
1818 private readonly ConcurrentDictionary < string , Lazy < TOptions > > _cache = new ConcurrentDictionary < string , Lazy < TOptions > > ( concurrencyLevel : 1 , capacity : 31 , StringComparer . Ordinal ) ; // 31 == default capacity
19+ private Lazy < TOptions > ? _defaultOptions = null ;
1920
2021 /// <summary>
2122 /// Clears all options instances from the cache.
@@ -35,6 +36,21 @@ public virtual TOptions GetOrAdd(string? name, Func<TOptions> createOptions)
3536 name ??= Options . DefaultName ;
3637 Lazy < TOptions > value ;
3738
39+ if ( name == Options . DefaultName )
40+ {
41+ if ( _defaultOptions is null )
42+ {
43+ // We need a reference to the new instance to be able to return it. Usage of `return _defaultOptions.Value`
44+ // could technically save us some allocations but it would have a risk of sneaky race condition of .Clear
45+ // being called between the Interlocked.CompareExchange call assigning new value and the return, leading to NRE.
46+ var newDefaultOptions = new Lazy < TOptions > ( createOptions ) ;
47+ var result = Interlocked . CompareExchange ( ref _defaultOptions , newDefaultOptions , null ) ;
48+
49+ return result is not null ? result . Value : newDefaultOptions . Value ;
50+ }
51+ return _defaultOptions . Value ;
52+ }
53+
3854#if NET || NETSTANDARD2_1
3955 value = _cache . GetOrAdd ( name , static ( name , createOptions ) => new Lazy < TOptions > ( createOptions ) , createOptions ) ;
4056#else
@@ -51,6 +67,22 @@ internal TOptions GetOrAdd<TArg>(string? name, Func<string, TArg, TOptions> crea
5167 {
5268 // For compatibility, fall back to public GetOrAdd() if we're in a derived class.
5369 // For simplicity, we do the same for older frameworks that don't support the factoryArgument overload of GetOrAdd().
70+
71+ if ( name == Options . DefaultName )
72+ {
73+ if ( _defaultOptions is null )
74+ {
75+ // We need a reference to the new instance to be able to return it. Usage of `return _defaultOptions.Value`
76+ // could technically save us some allocations but it would have a risk of sneaky race condition of .Clear
77+ // being called between the Interlocked.CompareExchange call assigning new value and the return, leading to NRE.
78+ var newDefaultOptions = new Lazy < TOptions > ( ( ) => createOptions ( Options . DefaultName , factoryArgument ) ) ;
79+ var result = Interlocked . CompareExchange ( ref _defaultOptions , newDefaultOptions , null ) ;
80+
81+ return result is not null ? result . Value : newDefaultOptions . Value ;
82+ }
83+ return _defaultOptions . Value ;
84+ }
85+
5486#if NET || NETSTANDARD2_1
5587 if ( GetType ( ) != typeof ( OptionsCache < TOptions > ) )
5688#endif
@@ -77,6 +109,17 @@ internal TOptions GetOrAdd<TArg>(string? name, Func<string, TArg, TOptions> crea
77109 /// <returns><see langword="true"/> if the options were retrieved; otherwise, <see langword="false"/>.</returns>
78110 internal bool TryGetValue ( string ? name , [ MaybeNullWhen ( false ) ] out TOptions options )
79111 {
112+ if ( name == Options . DefaultName )
113+ {
114+ if ( _defaultOptions is { } defaultOptions )
115+ {
116+ options = defaultOptions . Value ;
117+ return true ;
118+ }
119+ options = default ;
120+ return false ;
121+ }
122+
80123 if ( _cache . TryGetValue ( name ?? Options . DefaultName , out Lazy < TOptions > ? lazy ) )
81124 {
82125 options = lazy . Value ;
@@ -97,6 +140,16 @@ public virtual bool TryAdd(string? name, TOptions options)
97140 {
98141 ArgumentNullException . ThrowIfNull ( options ) ;
99142
143+ if ( name == Options . DefaultName )
144+ {
145+ if ( _defaultOptions is not null )
146+ {
147+ return false ; // Default options already exist
148+ }
149+ var result = Interlocked . CompareExchange ( ref _defaultOptions , new Lazy < TOptions > ( ( ) => options ) , null ) ;
150+ return result is null ;
151+ }
152+
100153 return _cache . TryAdd ( name ?? Options . DefaultName , new Lazy < TOptions > (
101154#if ! ( NET || NETSTANDARD2_1 )
102155 ( ) =>
@@ -109,7 +162,19 @@ public virtual bool TryAdd(string? name, TOptions options)
109162 /// </summary>
110163 /// <param name="name">The name of the options instance.</param>
111164 /// <returns><see langword="true"/> if anything was removed; otherwise, <see langword="false"/>.</returns>
112- public virtual bool TryRemove ( string ? name ) =>
113- _cache . TryRemove ( name ?? Options . DefaultName , out _ ) ;
165+ public virtual bool TryRemove ( string ? name )
166+ {
167+ if ( name == Options . DefaultName )
168+ {
169+ if ( _defaultOptions is not null )
170+ {
171+ var result = Interlocked . Exchange ( ref _defaultOptions , null ) ;
172+ return result is not null ;
173+ }
174+ return false ;
175+ }
176+
177+ return _cache . TryRemove ( name ?? Options . DefaultName , out _ ) ;
178+ }
114179 }
115180}
0 commit comments