@@ -16,11 +16,16 @@ 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. 
2223        /// </summary> 
23-         public  void  Clear ( )  =>  _cache . Clear ( ) ; 
24+         public  void  Clear ( ) 
25+         { 
26+             _defaultOptions  =  null ; 
27+             _cache . Clear ( ) ; 
28+         } 
2429
2530        /// <summary> 
2631        /// Gets a named options instance, or adds a new instance created with <paramref name="createOptions"/>. 
@@ -35,6 +40,21 @@ public virtual TOptions GetOrAdd(string? name, Func<TOptions> createOptions)
3540            name  ??=  Options . DefaultName ; 
3641            Lazy < TOptions >  value ; 
3742
43+             if  ( name  ==  Options . DefaultName ) 
44+             { 
45+                 if  ( _defaultOptions  is  null ) 
46+                 { 
47+                     // We need a reference to the new instance to be able to return it. Usage of `return _defaultOptions.Value` 
48+                     // could technically save us some allocations but it would have a risk of sneaky race condition of .Clear 
49+                     // being called between the Interlocked.CompareExchange call assigning new value and the return, leading to NRE. 
50+                     var  newDefaultOptions  =  new  Lazy < TOptions > ( createOptions ) ; 
51+                     var  result  =  Interlocked . CompareExchange ( ref  _defaultOptions ,  newDefaultOptions ,  null ) ; 
52+ 
53+                     return  result  is  not null  ?  result . Value  :  newDefaultOptions . Value ; 
54+                 } 
55+                 return  _defaultOptions . Value ; 
56+             } 
57+ 
3858#if NET  ||  NETSTANDARD2_1 
3959            value  =  _cache . GetOrAdd ( name ,  static  ( name ,  createOptions )  =>  new  Lazy < TOptions > ( createOptions ) ,  createOptions ) ; 
4060#else
@@ -51,6 +71,22 @@ internal TOptions GetOrAdd<TArg>(string? name, Func<string, TArg, TOptions> crea
5171        { 
5272            // For compatibility, fall back to public GetOrAdd() if we're in a derived class. 
5373            // For simplicity, we do the same for older frameworks that don't support the factoryArgument overload of GetOrAdd(). 
74+             name  ??=  Options . DefaultName ; 
75+             if  ( name  ==  Options . DefaultName ) 
76+             { 
77+                 if  ( _defaultOptions  is  null ) 
78+                 { 
79+                     // We need a reference to the new instance to be able to return it. Usage of `return _defaultOptions.Value` 
80+                     // could technically save us some allocations but it would have a risk of sneaky race condition of .Clear 
81+                     // being called between the Interlocked.CompareExchange call assigning new value and the return, leading to NRE. 
82+                     var  newDefaultOptions  =  new  Lazy < TOptions > ( ( )  =>  createOptions ( Options . DefaultName ,  factoryArgument ) ) ; 
83+                     var  result  =  Interlocked . CompareExchange ( ref  _defaultOptions ,  newDefaultOptions ,  null ) ; 
84+ 
85+                     return  result  is  not null  ?  result . Value  :  newDefaultOptions . Value ; 
86+                 } 
87+                 return  _defaultOptions . Value ; 
88+             } 
89+ 
5490#if NET  ||  NETSTANDARD2_1 
5591            if  ( GetType ( )  !=  typeof ( OptionsCache < TOptions > ) ) 
5692#endif
@@ -59,7 +95,7 @@ internal TOptions GetOrAdd<TArg>(string? name, Func<string, TArg, TOptions> crea
5995                string ?  localName  =  name ; 
6096                Func < string ,  TArg ,  TOptions >  localCreateOptions  =  createOptions ; 
6197                TArg  localFactoryArgument  =  factoryArgument ; 
62-                 return  GetOrAdd ( name ,  ( )  =>  localCreateOptions ( localName   ??   Options . DefaultName ,  localFactoryArgument ) ) ; 
98+                 return  GetOrAdd ( name ,  ( )  =>  localCreateOptions ( localName ,  localFactoryArgument ) ) ; 
6399            } 
64100
65101#if NET  ||  NETSTANDARD2_1 
@@ -77,7 +113,19 @@ internal TOptions GetOrAdd<TArg>(string? name, Func<string, TArg, TOptions> crea
77113        /// <returns><see langword="true"/> if the options were retrieved; otherwise, <see langword="false"/>.</returns> 
78114        internal  bool  TryGetValue ( string ?  name ,  [ MaybeNullWhen ( false ) ]  out  TOptions  options ) 
79115        { 
80-             if  ( _cache . TryGetValue ( name  ??  Options . DefaultName ,  out  Lazy < TOptions > ?  lazy ) ) 
116+             name  ??=  Options . DefaultName ; 
117+             if  ( name  ==  Options . DefaultName ) 
118+             { 
119+                 if  ( _defaultOptions  is  {  }  defaultOptions ) 
120+                 { 
121+                     options  =  defaultOptions . Value ; 
122+                     return  true ; 
123+                 } 
124+                 options  =  default ; 
125+                 return  false ; 
126+             } 
127+ 
128+             if  ( _cache . TryGetValue ( name ,  out  Lazy < TOptions > ?  lazy ) ) 
81129            { 
82130                options  =  lazy . Value ; 
83131                return  true ; 
@@ -97,7 +145,18 @@ public virtual bool TryAdd(string? name, TOptions options)
97145        { 
98146            ArgumentNullException . ThrowIfNull ( options ) ; 
99147
100-             return  _cache . TryAdd ( name  ??  Options . DefaultName ,  new  Lazy < TOptions > ( 
148+             name  ??=  Options . DefaultName ; 
149+             if  ( name  ==  Options . DefaultName ) 
150+             { 
151+                 if  ( _defaultOptions  is  not null ) 
152+                 { 
153+                     return  false ;  // Default options already exist 
154+                 } 
155+                 var  result  =  Interlocked . CompareExchange ( ref  _defaultOptions ,  new  Lazy < TOptions > ( ( )  =>  options ) ,  null ) ; 
156+                 return  result  is  null ; 
157+             } 
158+ 
159+             return  _cache . TryAdd ( name ,  new  Lazy < TOptions > ( 
101160#if ! ( NET  ||  NETSTANDARD2_1 ) 
102161                ( )  => 
103162#endif
@@ -109,7 +168,20 @@ public virtual bool TryAdd(string? name, TOptions options)
109168        /// </summary> 
110169        /// <param name="name">The name of the options instance.</param> 
111170        /// <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  _ ) ; 
171+         public  virtual  bool  TryRemove ( string ?  name ) 
172+         { 
173+             name  ??=  Options . DefaultName ; 
174+             if  ( name  ==  Options . DefaultName ) 
175+             { 
176+                 if  ( _defaultOptions  is  not null ) 
177+                 { 
178+                     var  result  =  Interlocked . Exchange ( ref  _defaultOptions ,  null ) ; 
179+                     return  result  is  not null ; 
180+                 } 
181+                 return  false ; 
182+             } 
183+ 
184+             return  _cache . TryRemove ( name ,  out  _ ) ; 
185+         } 
114186    } 
115187} 
0 commit comments