@@ -329,7 +329,8 @@ public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
329329 where TMessage : class
330330 where TToken : IEquatable < TToken >
331331 {
332- Object2 [ ] pairs ;
332+ object [ ] rentedArray ;
333+ Span < object > pairs ;
333334 int i = 0 ;
334335
335336 lock ( this . recipientsMap )
@@ -355,7 +356,9 @@ public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
355356 return message ;
356357 }
357358
358- pairs = ArrayPool < Object2 > . Shared . Rent ( totalHandlersCount ) ;
359+ // Rent the array and also assign it to a span, which will be used to access values.
360+ // We're doing this to avoid the array covariance checks slowdown in the loops below.
361+ pairs = rentedArray = ArrayPool < object > . Shared . Rent ( 2 * totalHandlersCount ) ;
359362
360363 // Copy the handlers to the local collection.
361364 // The array is oversized at this point, since it also includes
@@ -373,39 +376,37 @@ public TMessage Send<TMessage, TToken>(TMessage message, TToken token)
373376 // Pick the target handler, if the token is a match for the recipient
374377 if ( mappingEnumerator . Value . TryGetValue ( token , out object ? handler ) )
375378 {
376- // This array access should always guaranteed to be valid due to the size of the
379+ // This span access should always guaranteed to be valid due to the size of the
377380 // array being set according to the current total number of registered handlers,
378381 // which will always be greater or equal than the ones matching the previous test.
379- // We're still using a checked array access here though to make sure an out of
382+ // We're still using a checked span accesses here though to make sure an out of
380383 // bounds write can never happen even if an error was present in the logic above.
381- pairs [ i ++ ] = new Object2 ( handler ! , recipient ) ;
384+ pairs [ 2 * i ] = handler ! ;
385+ pairs [ ( 2 * i ) + 1 ] = recipient ;
386+ i ++ ;
382387 }
383388 }
384389 }
385390
386- // The rented array is often larger than the number of matching pairs
387- // to process, so we first slice the span with just the items we need.
388- Span < Object2 > pendingPairs = pairs . AsSpan ( 0 , i ) ;
389-
390391 try
391392 {
392393 // Invoke all the necessary handlers on the local copy of entries
393- foreach ( ref Object2 pair in pendingPairs )
394+ for ( int j = 0 ; j < i ; j ++ )
394395 {
395396 // Here we perform an unsafe cast to enable covariance for delegate types.
396397 // We know that the input recipient will always respect the type constraints
397398 // of each original input delegate, and doing so allows us to still invoke
398399 // them all from here without worrying about specific generic type arguments.
399- Unsafe . As < MessageHandler < object , TMessage > > ( pair . Handler ) ( pair . Recipient , message ) ;
400+ Unsafe . As < MessageHandler < object , TMessage > > ( pairs [ 2 * j ] ) ( pairs [ ( 2 * j ) + 1 ] , message ) ;
400401 }
401402 }
402403 finally
403404 {
404405 // As before, we also need to clear it first to avoid having potentially long
405406 // lasting memory leaks due to leftover references being stored in the pool.
406- pendingPairs . Clear ( ) ;
407+ Array . Clear ( rentedArray , 0 , 2 * i ) ;
407408
408- ArrayPool < Object2 > . Shared . Return ( pairs ) ;
409+ ArrayPool < object > . Shared . Return ( rentedArray ) ;
409410 }
410411
411412 return message ;
0 commit comments