@@ -142,12 +142,14 @@ internal sealed class CachingContext
142142 {
143143 private readonly ConcurrentDictionary < Type , JsonTypeInfo ? > _jsonTypeInfoCache = new ( ) ;
144144
145- public CachingContext ( JsonSerializerOptions options )
145+ public CachingContext ( JsonSerializerOptions options , int hashCode )
146146 {
147147 Options = options ;
148+ HashCode = hashCode ;
148149 }
149150
150151 public JsonSerializerOptions Options { get ; }
152+ public int HashCode { get ; }
151153 // Property only accessed by reflection in testing -- do not remove.
152154 // If changing please ensure that src/ILLink.Descriptors.LibraryBuild.xml is up-to-date.
153155 public int Count => _jsonTypeInfoCache . Count ;
@@ -164,37 +166,39 @@ public void Clear()
164166 /// <summary>
165167 /// Defines a cache of CachingContexts; instead of using a ConditionalWeakTable which can be slow to traverse
166168 /// this approach uses a fixed-size array of weak references of <see cref="CachingContext"/> that can be looked up lock-free.
167- /// Relevant caching contexts are looked up by linear traversal using the equality comparison defined by
168- /// <see cref="AreEquivalentOptions(JsonSerializerOptions, JsonSerializerOptions)"/>.
169+ /// Relevant caching contexts are looked up by linear traversal using the equality comparison defined by <see cref="EqualityComparer"/>.
169170 /// </summary>
170171 internal static class TrackedCachingContexts
171172 {
172173 private const int MaxTrackedContexts = 64 ;
173174 private static readonly WeakReference < CachingContext > ? [ ] s_trackedContexts = new WeakReference < CachingContext > [ MaxTrackedContexts ] ;
175+ private static readonly EqualityComparer s_optionsComparer = new ( ) ;
174176
175177 public static CachingContext GetOrCreate ( JsonSerializerOptions options )
176178 {
177179 Debug . Assert ( options . IsReadOnly , "Cannot create caching contexts for mutable JsonSerializerOptions instances" ) ;
178180 Debug . Assert ( options . _typeInfoResolver != null ) ;
179181
180- if ( TryGetContext ( options , out int firstUnpopulatedIndex , out CachingContext ? result ) )
182+ int hashCode = s_optionsComparer . GetHashCode ( options ) ;
183+
184+ if ( TryGetContext ( options , hashCode , out int firstUnpopulatedIndex , out CachingContext ? result ) )
181185 {
182186 return result ;
183187 }
184188 else if ( firstUnpopulatedIndex < 0 )
185189 {
186190 // Cache is full; return a fresh instance.
187- return new CachingContext ( options ) ;
191+ return new CachingContext ( options , hashCode ) ;
188192 }
189193
190194 lock ( s_trackedContexts )
191195 {
192- if ( TryGetContext ( options , out firstUnpopulatedIndex , out result ) )
196+ if ( TryGetContext ( options , hashCode , out firstUnpopulatedIndex , out result ) )
193197 {
194198 return result ;
195199 }
196200
197- var ctx = new CachingContext ( options ) ;
201+ var ctx = new CachingContext ( options , hashCode ) ;
198202
199203 if ( firstUnpopulatedIndex >= 0 )
200204 {
@@ -218,6 +222,7 @@ public static CachingContext GetOrCreate(JsonSerializerOptions options)
218222
219223 private static bool TryGetContext (
220224 JsonSerializerOptions options ,
225+ int hashCode ,
221226 out int firstUnpopulatedIndex ,
222227 [ NotNullWhen ( true ) ] out CachingContext ? result )
223228 {
@@ -235,7 +240,7 @@ private static bool TryGetContext(
235240 firstUnpopulatedIndex = i ;
236241 }
237242 }
238- else if ( AreEquivalentOptions ( options , ctx . Options ) )
243+ else if ( hashCode == ctx . HashCode && s_optionsComparer . Equals ( options , ctx . Options ) )
239244 {
240245 result = ctx ;
241246 return true ;
@@ -252,52 +257,114 @@ private static bool TryGetContext(
252257 /// If two instances are equivalent, they should generate identical metadata caches;
253258 /// the converse however does not necessarily hold.
254259 /// </summary>
255- private static bool AreEquivalentOptions ( JsonSerializerOptions left , JsonSerializerOptions right )
260+ private sealed class EqualityComparer : IEqualityComparer < JsonSerializerOptions >
256261 {
257- Debug . Assert ( left != null && right != null ) ;
258-
259- return
260- left . _dictionaryKeyPolicy == right . _dictionaryKeyPolicy &&
261- left . _jsonPropertyNamingPolicy == right . _jsonPropertyNamingPolicy &&
262- left . _readCommentHandling == right . _readCommentHandling &&
263- left . _referenceHandler == right . _referenceHandler &&
264- left . _encoder == right . _encoder &&
265- left . _defaultIgnoreCondition == right . _defaultIgnoreCondition &&
266- left . _numberHandling == right . _numberHandling &&
267- left . _unknownTypeHandling == right . _unknownTypeHandling &&
268- left . _defaultBufferSize == right . _defaultBufferSize &&
269- left . _maxDepth == right . _maxDepth &&
270- left . _allowTrailingCommas == right . _allowTrailingCommas &&
271- left . _ignoreNullValues == right . _ignoreNullValues &&
272- left . _ignoreReadOnlyProperties == right . _ignoreReadOnlyProperties &&
273- left . _ignoreReadonlyFields == right . _ignoreReadonlyFields &&
274- left . _includeFields == right . _includeFields &&
275- left . _propertyNameCaseInsensitive == right . _propertyNameCaseInsensitive &&
276- left . _writeIndented == right . _writeIndented &&
277- left . _typeInfoResolver == right . _typeInfoResolver &&
278- CompareLists ( left . _converters , right . _converters ) ;
279-
280- static bool CompareLists < TValue > ( ConfigurationList < TValue > left , ConfigurationList < TValue > right )
262+ public bool Equals ( JsonSerializerOptions ? left , JsonSerializerOptions ? right )
263+ {
264+ Debug . Assert ( left != null && right != null ) ;
265+
266+ return
267+ left . _dictionaryKeyPolicy == right . _dictionaryKeyPolicy &&
268+ left . _jsonPropertyNamingPolicy == right . _jsonPropertyNamingPolicy &&
269+ left . _readCommentHandling == right . _readCommentHandling &&
270+ left . _referenceHandler == right . _referenceHandler &&
271+ left . _encoder == right . _encoder &&
272+ left . _defaultIgnoreCondition == right . _defaultIgnoreCondition &&
273+ left . _numberHandling == right . _numberHandling &&
274+ left . _unknownTypeHandling == right . _unknownTypeHandling &&
275+ left . _defaultBufferSize == right . _defaultBufferSize &&
276+ left . _maxDepth == right . _maxDepth &&
277+ left . _allowTrailingCommas == right . _allowTrailingCommas &&
278+ left . _ignoreNullValues == right . _ignoreNullValues &&
279+ left . _ignoreReadOnlyProperties == right . _ignoreReadOnlyProperties &&
280+ left . _ignoreReadonlyFields == right . _ignoreReadonlyFields &&
281+ left . _includeFields == right . _includeFields &&
282+ left . _propertyNameCaseInsensitive == right . _propertyNameCaseInsensitive &&
283+ left . _writeIndented == right . _writeIndented &&
284+ left . _typeInfoResolver == right . _typeInfoResolver &&
285+ CompareLists ( left . _converters , right . _converters ) ;
286+
287+ static bool CompareLists < TValue > ( ConfigurationList < TValue > left , ConfigurationList < TValue > right )
288+ where TValue : class ?
289+ {
290+ int n ;
291+ if ( ( n = left . Count ) != right . Count )
292+ {
293+ return false ;
294+ }
295+
296+ for ( int i = 0 ; i < n ; i ++ )
297+ {
298+ if ( left [ i ] != right [ i ] )
299+ {
300+ return false ;
301+ }
302+ }
303+
304+ return true ;
305+ }
306+ }
307+
308+ public int GetHashCode ( JsonSerializerOptions options )
281309 {
282- int n ;
283- if ( ( n = left . Count ) != right . Count )
310+ HashCode hc = default ;
311+
312+ AddHashCode ( ref hc , options . _dictionaryKeyPolicy ) ;
313+ AddHashCode ( ref hc , options . _jsonPropertyNamingPolicy ) ;
314+ AddHashCode ( ref hc , options . _readCommentHandling ) ;
315+ AddHashCode ( ref hc , options . _referenceHandler ) ;
316+ AddHashCode ( ref hc , options . _encoder ) ;
317+ AddHashCode ( ref hc , options . _defaultIgnoreCondition ) ;
318+ AddHashCode ( ref hc , options . _numberHandling ) ;
319+ AddHashCode ( ref hc , options . _unknownTypeHandling ) ;
320+ AddHashCode ( ref hc , options . _defaultBufferSize ) ;
321+ AddHashCode ( ref hc , options . _maxDepth ) ;
322+ AddHashCode ( ref hc , options . _allowTrailingCommas ) ;
323+ AddHashCode ( ref hc , options . _ignoreNullValues ) ;
324+ AddHashCode ( ref hc , options . _ignoreReadOnlyProperties ) ;
325+ AddHashCode ( ref hc , options . _ignoreReadonlyFields ) ;
326+ AddHashCode ( ref hc , options . _includeFields ) ;
327+ AddHashCode ( ref hc , options . _propertyNameCaseInsensitive ) ;
328+ AddHashCode ( ref hc , options . _writeIndented ) ;
329+ AddHashCode ( ref hc , options . _typeInfoResolver ) ;
330+ AddListHashCode ( ref hc , options . _converters ) ;
331+
332+ return hc . ToHashCode ( ) ;
333+
334+ static void AddListHashCode < TValue > ( ref HashCode hc , ConfigurationList < TValue > list )
284335 {
285- return false ;
336+ int n = list . Count ;
337+ for ( int i = 0 ; i < n ; i ++ )
338+ {
339+ AddHashCode ( ref hc , list [ i ] ) ;
340+ }
286341 }
287342
288- for ( int i = 0 ; i < n ; i ++ )
343+ static void AddHashCode < TValue > ( ref HashCode hc , TValue ? value )
289344 {
290- TValue ? leftElem = left [ i ] ;
291- TValue ? rightElem = right [ i ] ;
292- bool areEqual = leftElem is null ? rightElem is null : leftElem . Equals ( rightElem ) ;
293- if ( ! areEqual )
345+ if ( typeof ( TValue ) . IsValueType )
294346 {
295- return false ;
347+ hc . Add ( value ) ;
348+ }
349+ else
350+ {
351+ Debug . Assert ( ! typeof ( TValue ) . IsSealed , "Sealed reference types like string should not use this method." ) ;
352+ hc . Add ( RuntimeHelpers . GetHashCode ( value ) ) ;
296353 }
297354 }
355+ }
298356
299- return true ;
357+ #if ! NETCOREAPP
358+ /// <summary>
359+ /// Polyfill for System.HashCode.
360+ /// </summary>
361+ private struct HashCode
362+ {
363+ private int _hashCode ;
364+ public void Add < T > ( T ? value ) => _hashCode = ( _hashCode , value ) . GetHashCode ( ) ;
365+ public int ToHashCode ( ) => _hashCode ;
300366 }
367+ #endif
301368 }
302369 }
303370}
0 commit comments