@@ -275,18 +275,34 @@ public bool ContainsKey(TKey key)
275
275
276
276
public bool ContainsValue ( TValue value )
277
277
{
278
+ Entry [ ] entries = _entries ;
278
279
if ( value == null )
279
280
{
280
281
for ( int i = 0 ; i < _count ; i ++ )
281
282
{
282
- if ( _entries [ i ] . hashCode >= 0 && _entries [ i ] . value == null ) return true ;
283
+ if ( entries [ i ] . hashCode >= 0 && entries [ i ] . value == null ) return true ;
283
284
}
284
285
}
285
286
else
286
287
{
287
- for ( int i = 0 ; i < _count ; i ++ )
288
+ if ( default ( TValue ) != null )
288
289
{
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
+ }
290
306
}
291
307
}
292
308
return false ;
@@ -364,24 +380,53 @@ private int FindEntry(TKey key)
364
380
int hashCode = key . GetHashCode ( ) & 0x7FFFFFFF ;
365
381
// Value in _buckets is 1-based
366
382
i = buckets [ hashCode % buckets . Length ] - 1 ;
367
- do
383
+ if ( default ( TKey ) != null )
368
384
{
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
372
387
{
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
378
412
{
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
+ }
385
430
}
386
431
else
387
432
{
@@ -449,40 +494,85 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
449
494
450
495
if ( comparer == null )
451
496
{
452
- do
497
+ if ( default ( TKey ) != null )
453
498
{
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
457
501
{
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
+ }
460
508
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 ) )
464
510
{
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 ;
467
523
}
468
524
469
- if ( behavior == InsertionBehavior . ThrowOnExisting )
525
+ i = entries [ i ] . next ;
526
+ if ( collisionCount >= entries . Length )
470
527
{
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 ;
472
548
}
473
549
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
+ }
476
565
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
+ }
486
576
}
487
577
else
488
578
{
0 commit comments