@@ -275,18 +275,34 @@ public bool ContainsKey(TKey key)
275275
276276 public bool ContainsValue ( TValue value )
277277 {
278+ Entry [ ] entries = _entries ;
278279 if ( value == null )
279280 {
280281 for ( int i = 0 ; i < _count ; i ++ )
281282 {
282- if ( _entries [ i ] . hashCode >= 0 && _entries [ i ] . value == null ) return true ;
283+ if ( entries [ i ] . hashCode >= 0 && entries [ i ] . value == null ) return true ;
283284 }
284285 }
285286 else
286287 {
287- for ( int i = 0 ; i < _count ; i ++ )
288+ if ( default ( TValue ) != null )
288289 {
289- if ( _entries [ i ] . hashCode >= 0 && EqualityComparer < TValue > . Default . Equals ( _entries [ i ] . value , value ) ) return true ;
290+ // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
291+ for ( int i = 0 ; i < _count ; i ++ )
292+ {
293+ if ( entries [ i ] . hashCode >= 0 && EqualityComparer < TValue > . Default . Equals ( entries [ i ] . value , value ) ) return true ;
294+ }
295+ }
296+ else
297+ {
298+ // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
299+ // https://github.com/dotnet/coreclr/issues/17273
300+ // So cache in a local rather than get EqualityComparer per loop iteration
301+ EqualityComparer < TValue > defaultComparer = EqualityComparer < TValue > . Default ;
302+ for ( int i = 0 ; i < _count ; i ++ )
303+ {
304+ if ( entries [ i ] . hashCode >= 0 && defaultComparer . Equals ( entries [ i ] . value , value ) ) return true ;
305+ }
290306 }
291307 }
292308 return false ;
@@ -364,24 +380,53 @@ private int FindEntry(TKey key)
364380 int hashCode = key . GetHashCode ( ) & 0x7FFFFFFF ;
365381 // Value in _buckets is 1-based
366382 i = buckets [ hashCode % buckets . Length ] - 1 ;
367- do
383+ if ( default ( TKey ) != null )
368384 {
369- // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
370- // Test in if to drop range check for following array access
371- if ( ( uint ) i >= ( uint ) entries . Length || ( entries [ i ] . hashCode == hashCode && EqualityComparer < TKey > . Default . Equals ( entries [ i ] . key , key ) ) )
385+ // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
386+ do
372387 {
373- break ;
374- }
375-
376- i = entries [ i ] . next ;
377- if ( collisionCount >= entries . Length )
388+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
389+ // Test in if to drop range check for following array access
390+ if ( ( uint ) i >= ( uint ) entries . Length || ( entries [ i ] . hashCode == hashCode && EqualityComparer < TKey > . Default . Equals ( entries [ i ] . key , key ) ) )
391+ {
392+ break ;
393+ }
394+
395+ i = entries [ i ] . next ;
396+ if ( collisionCount >= entries . Length )
397+ {
398+ // The chain of entries forms a loop; which means a concurrent update has happened.
399+ // Break out of the loop and throw, rather than looping forever.
400+ ThrowHelper . ThrowInvalidOperationException_ConcurrentOperationsNotSupported ( ) ;
401+ }
402+ collisionCount ++ ;
403+ } while ( true ) ;
404+ }
405+ else
406+ {
407+ // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
408+ // https://github.com/dotnet/coreclr/issues/17273
409+ // So cache in a local rather than get EqualityComparer per loop iteration
410+ EqualityComparer < TKey > defaultComparer = EqualityComparer < TKey > . Default ;
411+ do
378412 {
379- // The chain of entries forms a loop; which means a concurrent update has happened.
380- // Break out of the loop and throw, rather than looping forever.
381- ThrowHelper . ThrowInvalidOperationException_ConcurrentOperationsNotSupported ( ) ;
382- }
383- collisionCount ++ ;
384- } while ( true ) ;
413+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
414+ // Test in if to drop range check for following array access
415+ if ( ( uint ) i >= ( uint ) entries . Length || ( entries [ i ] . hashCode == hashCode && defaultComparer . Equals ( entries [ i ] . key , key ) ) )
416+ {
417+ break ;
418+ }
419+
420+ i = entries [ i ] . next ;
421+ if ( collisionCount >= entries . Length )
422+ {
423+ // The chain of entries forms a loop; which means a concurrent update has happened.
424+ // Break out of the loop and throw, rather than looping forever.
425+ ThrowHelper . ThrowInvalidOperationException_ConcurrentOperationsNotSupported ( ) ;
426+ }
427+ collisionCount ++ ;
428+ } while ( true ) ;
429+ }
385430 }
386431 else
387432 {
@@ -449,40 +494,85 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
449494
450495 if ( comparer == null )
451496 {
452- do
497+ if ( default ( TKey ) != null )
453498 {
454- // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
455- // Test uint in if rather than loop condition to drop range check for following array access
456- if ( ( uint ) i >= ( uint ) entries . Length )
499+ // ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
500+ do
457501 {
458- break ;
459- }
502+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
503+ // Test uint in if rather than loop condition to drop range check for following array access
504+ if ( ( uint ) i >= ( uint ) entries . Length )
505+ {
506+ break ;
507+ }
460508
461- if ( entries [ i ] . hashCode == hashCode && EqualityComparer < TKey > . Default . Equals ( entries [ i ] . key , key ) )
462- {
463- if ( behavior == InsertionBehavior . OverwriteExisting )
509+ if ( entries [ i ] . hashCode == hashCode && EqualityComparer < TKey > . Default . Equals ( entries [ i ] . key , key ) )
464510 {
465- entries [ i ] . value = value ;
466- return true ;
511+ if ( behavior == InsertionBehavior . OverwriteExisting )
512+ {
513+ entries [ i ] . value = value ;
514+ return true ;
515+ }
516+
517+ if ( behavior == InsertionBehavior . ThrowOnExisting )
518+ {
519+ ThrowHelper . ThrowAddingDuplicateWithKeyArgumentException ( key ) ;
520+ }
521+
522+ return false ;
467523 }
468524
469- if ( behavior == InsertionBehavior . ThrowOnExisting )
525+ i = entries [ i ] . next ;
526+ if ( collisionCount >= entries . Length )
470527 {
471- ThrowHelper . ThrowAddingDuplicateWithKeyArgumentException ( key ) ;
528+ // The chain of entries forms a loop; which means a concurrent update has happened.
529+ // Break out of the loop and throw, rather than looping forever.
530+ ThrowHelper . ThrowInvalidOperationException_ConcurrentOperationsNotSupported ( ) ;
531+ }
532+ collisionCount ++ ;
533+ } while ( true ) ;
534+ }
535+ else
536+ {
537+ // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
538+ // https://github.com/dotnet/coreclr/issues/17273
539+ // So cache in a local rather than get EqualityComparer per loop iteration
540+ EqualityComparer < TKey > defaultComparer = EqualityComparer < TKey > . Default ;
541+ do
542+ {
543+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
544+ // Test uint in if rather than loop condition to drop range check for following array access
545+ if ( ( uint ) i >= ( uint ) entries . Length )
546+ {
547+ break ;
472548 }
473549
474- return false ;
475- }
550+ if ( entries [ i ] . hashCode == hashCode && defaultComparer . Equals ( entries [ i ] . key , key ) )
551+ {
552+ if ( behavior == InsertionBehavior . OverwriteExisting )
553+ {
554+ entries [ i ] . value = value ;
555+ return true ;
556+ }
557+
558+ if ( behavior == InsertionBehavior . ThrowOnExisting )
559+ {
560+ ThrowHelper . ThrowAddingDuplicateWithKeyArgumentException ( key ) ;
561+ }
562+
563+ return false ;
564+ }
476565
477- i = entries [ i ] . next ;
478- if ( collisionCount >= entries . Length )
479- {
480- // The chain of entries forms a loop; which means a concurrent update has happened.
481- // Break out of the loop and throw, rather than looping forever.
482- ThrowHelper . ThrowInvalidOperationException_ConcurrentOperationsNotSupported ( ) ;
483- }
484- collisionCount ++ ;
485- } while ( true ) ;
566+ i = entries [ i ] . next ;
567+ if ( collisionCount >= entries . Length )
568+ {
569+ // The chain of entries forms a loop; which means a concurrent update has happened.
570+ // Break out of the loop and throw, rather than looping forever.
571+ ThrowHelper . ThrowInvalidOperationException_ConcurrentOperationsNotSupported ( ) ;
572+ }
573+ collisionCount ++ ;
574+ } while ( true ) ;
575+ }
486576 }
487577 else
488578 {
0 commit comments