1010using System . Collections . Immutable ;
1111using System . Collections . ObjectModel ;
1212using System . Diagnostics ;
13+ using System . Diagnostics . CodeAnalysis ;
1314using System . Linq ;
14- using System . Runtime . InteropServices ;
1515using System . Threading ;
1616using System . Threading . Tasks ;
1717using Microsoft . CodeAnalysis ;
@@ -331,9 +331,66 @@ public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)
331331 return source . Where ( ( Func < T ? , bool > ) s_notNullTest ) ! ;
332332 }
333333
334+ /// <summary>
335+ /// Uses a builder to store results computed from <paramref name="source"/>. If <paramref name="source"/> is
336+ /// null, or definitely empty, this returns false, allowing the caller to immediately return <c>[]</c> without
337+ /// further allocations. If this returns true, then <paramref name="builder"/> will be set to an appropriate
338+ /// builder to receive temporary values.
339+ /// </summary>
340+ /// <param name="useCountForBuilder">If the count of <paramref name="source"/> can be determined (and is non-zero),
341+ /// if <paramref name="builder"/> should be initialized to that same count. This should be passed true
342+ /// for 'SelectAsArray' methods, and false for 'WhereAsArray' or 'SelectManyAsArray' methods (as the latter two
343+ /// do not know how many items will be added to the builder).</param>
344+ private static bool TryGetBuilder < TSource , TResult > (
345+ [ NotNullWhen ( true ) ] IEnumerable < TSource > ? source ,
346+ bool useCountForBuilder ,
347+ [ NotNullWhen ( true ) ] out ArrayBuilder < TResult > ? builder )
348+ {
349+ if ( source is null )
350+ {
351+ builder = null ;
352+ return false ;
353+ }
354+
355+ #if NET
356+ if ( source . TryGetNonEnumeratedCount ( out var count ) )
357+ {
358+ if ( count == 0 )
359+ {
360+ builder = null ;
361+ return false ;
362+ }
363+
364+ if ( useCountForBuilder )
365+ {
366+ builder = ArrayBuilder < TResult > . GetInstance ( count ) ;
367+ return true ;
368+ }
369+ }
370+ #endif
371+
372+ builder = ArrayBuilder < TResult > . GetInstance ( ) ;
373+ return true ;
374+ }
375+
376+ public static ImmutableArray < T > WhereAsArray < T > ( this IEnumerable < T > values , Func < T , bool > predicate )
377+ {
378+ if ( ! TryGetBuilder < T , T > ( values , useCountForBuilder : false , out var builder ) )
379+ return [ ] ;
380+
381+ foreach ( var value in values )
382+ {
383+ if ( predicate ( value ) )
384+ builder . Add ( value ) ;
385+ }
386+
387+ return builder . ToImmutableAndFree ( ) ;
388+ }
389+
334390 public static ImmutableArray < T > WhereAsArray < T , TArg > ( this IEnumerable < T > values , Func < T , TArg , bool > predicate , TArg arg )
335391 {
336- var result = ArrayBuilder < T > . GetInstance ( ) ;
392+ if ( ! TryGetBuilder < T , T > ( values , useCountForBuilder : false , out var result ) )
393+ return [ ] ;
337394
338395 foreach ( var value in values )
339396 {
@@ -349,25 +406,34 @@ public static T[] AsArray<T>(this IEnumerable<T> source)
349406
350407 public static ImmutableArray < TResult > SelectAsArray < TSource , TResult > ( this IEnumerable < TSource > ? source , Func < TSource , TResult > selector )
351408 {
352- if ( source == null )
353- {
354- return ImmutableArray < TResult > . Empty ;
355- }
409+ if ( ! TryGetBuilder < TSource , TResult > ( source , useCountForBuilder : true , out var builder ) )
410+ return [ ] ;
356411
357- var builder = ArrayBuilder < TResult > . GetInstance ( ) ;
358412 builder . AddRange ( source . Select ( selector ) ) ;
413+ return builder . ToImmutableAndFree ( ) ;
414+
415+ }
416+
417+ public static ImmutableArray < TResult > SelectAsArray < TItem , TResult > ( this IEnumerable < TItem > ? source , Func < TItem , bool > predicate , Func < TItem , TResult > selector )
418+ {
419+ if ( ! TryGetBuilder < TItem , TResult > ( source , useCountForBuilder : false , out var builder ) )
420+ return [ ] ;
421+
422+ foreach ( var item in source )
423+ {
424+ if ( predicate ( item ) )
425+ builder . Add ( selector ( item ) ) ;
426+ }
359427
360428 return builder . ToImmutableAndFree ( ) ;
361429 }
362430
363431 public static ImmutableArray < TResult > SelectAsArray < TSource , TResult > ( this IEnumerable < TSource > ? source , Func < TSource , int , TResult > selector )
364432 {
365- if ( source == null )
366- return ImmutableArray < TResult > . Empty ;
433+ if ( ! TryGetBuilder < TSource , TResult > ( source , useCountForBuilder : true , out var builder ) )
434+ return [ ] ;
367435
368- var builder = ArrayBuilder < TResult > . GetInstance ( ) ;
369-
370- int index = 0 ;
436+ var index = 0 ;
371437 foreach ( var element in source )
372438 {
373439 builder . Add ( selector ( element , index ) ) ;
@@ -379,42 +445,33 @@ public static ImmutableArray<TResult> SelectAsArray<TSource, TResult>(this IEnum
379445
380446 public static ImmutableArray < TResult > SelectAsArray < TSource , TResult > ( this IReadOnlyCollection < TSource > ? source , Func < TSource , TResult > selector )
381447 {
382- if ( source == null )
383- return ImmutableArray < TResult > . Empty ;
448+ if ( source is null or { Count : 0 } )
449+ return [ ] ;
384450
385- var builder = new TResult [ source . Count ] ;
386- var index = 0 ;
451+ var builder = new FixedSizeArrayBuilder < TResult > ( source . Count ) ;
387452 foreach ( var item in source )
388- {
389- builder [ index ] = selector ( item ) ;
390- index ++ ;
391- }
453+ builder . Add ( selector ( item ) ) ;
392454
393- return ImmutableCollectionsMarshal . AsImmutableArray ( builder ) ;
455+ return builder . MoveToImmutable ( ) ;
394456 }
395457
396458 public static ImmutableArray < TResult > SelectAsArray < TSource , TResult , TArg > ( this IReadOnlyCollection < TSource > ? source , Func < TSource , TArg , TResult > selector , TArg arg )
397459 {
398- if ( source == null )
399- return ImmutableArray < TResult > . Empty ;
460+ if ( source is null or { Count : 0 } )
461+ return [ ] ;
400462
401- var builder = new TResult [ source . Count ] ;
402- var index = 0 ;
463+ var builder = new FixedSizeArrayBuilder < TResult > ( source . Count ) ;
403464 foreach ( var item in source )
404- {
405- builder [ index ] = selector ( item , arg ) ;
406- index ++ ;
407- }
465+ builder . Add ( selector ( item , arg ) ) ;
408466
409- return ImmutableCollectionsMarshal . AsImmutableArray ( builder ) ;
467+ return builder . MoveToImmutable ( ) ;
410468 }
411469
412470 public static ImmutableArray < TResult > SelectManyAsArray < TSource , TResult > ( this IEnumerable < TSource > ? source , Func < TSource , IEnumerable < TResult > > selector )
413471 {
414- if ( source == null )
415- return ImmutableArray < TResult > . Empty ;
472+ if ( ! TryGetBuilder < TSource , TResult > ( source , useCountForBuilder : false , out var builder ) )
473+ return [ ] ;
416474
417- var builder = ArrayBuilder < TResult > . GetInstance ( ) ;
418475 foreach ( var item in source )
419476 builder . AddRange ( selector ( item ) ) ;
420477
@@ -423,10 +480,9 @@ public static ImmutableArray<TResult> SelectManyAsArray<TSource, TResult>(this I
423480
424481 public static ImmutableArray < TResult > SelectManyAsArray < TItem , TArg , TResult > ( this IEnumerable < TItem > ? source , Func < TItem , TArg , IEnumerable < TResult > > selector , TArg arg )
425482 {
426- if ( source == null )
427- return ImmutableArray < TResult > . Empty ;
483+ if ( ! TryGetBuilder < TItem , TResult > ( source , useCountForBuilder : false , out var builder ) )
484+ return [ ] ;
428485
429- var builder = ArrayBuilder < TResult > . GetInstance ( ) ;
430486 foreach ( var item in source )
431487 builder . AddRange ( selector ( item , arg ) ) ;
432488
@@ -435,8 +491,8 @@ public static ImmutableArray<TResult> SelectManyAsArray<TItem, TArg, TResult>(th
435491
436492 public static ImmutableArray < TResult > SelectManyAsArray < TItem , TResult > ( this IReadOnlyCollection < TItem > ? source , Func < TItem , IEnumerable < TResult > > selector )
437493 {
438- if ( source == null )
439- return ImmutableArray < TResult > . Empty ;
494+ if ( source is null or { Count : 0 } )
495+ return [ ] ;
440496
441497 // Basic heuristic. Assume each element in the source adds one item to the result.
442498 var builder = ArrayBuilder < TResult > . GetInstance ( source . Count ) ;
@@ -448,8 +504,8 @@ public static ImmutableArray<TResult> SelectManyAsArray<TItem, TResult>(this IRe
448504
449505 public static ImmutableArray < TResult > SelectManyAsArray < TItem , TArg , TResult > ( this IReadOnlyCollection < TItem > ? source , Func < TItem , TArg , IEnumerable < TResult > > selector , TArg arg )
450506 {
451- if ( source == null )
452- return ImmutableArray < TResult > . Empty ;
507+ if ( source is null or { Count : 0 } )
508+ return [ ] ;
453509
454510 // Basic heuristic. Assume each element in the source adds one item to the result.
455511 var builder = ArrayBuilder < TResult > . GetInstance ( source . Count ) ;
@@ -461,10 +517,9 @@ public static ImmutableArray<TResult> SelectManyAsArray<TItem, TArg, TResult>(th
461517
462518 public static ImmutableArray < TResult > SelectManyAsArray < TSource , TResult > ( this IEnumerable < TSource > ? source , Func < TSource , OneOrMany < TResult > > selector )
463519 {
464- if ( source == null )
465- return ImmutableArray < TResult > . Empty ;
520+ if ( ! TryGetBuilder < TSource , TResult > ( source , useCountForBuilder : false , out var builder ) )
521+ return [ ] ;
466522
467- var builder = ArrayBuilder < TResult > . GetInstance ( ) ;
468523 foreach ( var item in source )
469524 selector ( item ) . AddRangeTo ( builder ) ;
470525
@@ -476,12 +531,11 @@ public static ImmutableArray<TResult> SelectManyAsArray<TSource, TResult>(this I
476531 /// </summary>
477532 public static async ValueTask < ImmutableArray < TResult > > SelectAsArrayAsync < TItem , TResult > ( this IEnumerable < TItem > source , Func < TItem , ValueTask < TResult > > selector )
478533 {
479- var builder = ArrayBuilder < TResult > . GetInstance ( ) ;
534+ if ( ! TryGetBuilder < TItem , TResult > ( source , useCountForBuilder : true , out var builder ) )
535+ return [ ] ;
480536
481537 foreach ( var item in source )
482- {
483538 builder . Add ( await selector ( item ) . ConfigureAwait ( false ) ) ;
484- }
485539
486540 return builder . ToImmutableAndFree ( ) ;
487541 }
@@ -491,12 +545,11 @@ public static async ValueTask<ImmutableArray<TResult>> SelectAsArrayAsync<TItem,
491545 /// </summary>
492546 public static async ValueTask < ImmutableArray < TResult > > SelectAsArrayAsync < TItem , TResult > ( this IEnumerable < TItem > source , Func < TItem , CancellationToken , ValueTask < TResult > > selector , CancellationToken cancellationToken )
493547 {
494- var builder = ArrayBuilder < TResult > . GetInstance ( ) ;
548+ if ( ! TryGetBuilder < TItem , TResult > ( source , useCountForBuilder : true , out var builder ) )
549+ return [ ] ;
495550
496551 foreach ( var item in source )
497- {
498552 builder . Add ( await selector ( item , cancellationToken ) . ConfigureAwait ( false ) ) ;
499- }
500553
501554 return builder . ToImmutableAndFree ( ) ;
502555 }
@@ -506,24 +559,22 @@ public static async ValueTask<ImmutableArray<TResult>> SelectAsArrayAsync<TItem,
506559 /// </summary>
507560 public static async ValueTask < ImmutableArray < TResult > > SelectAsArrayAsync < TItem , TArg , TResult > ( this IEnumerable < TItem > source , Func < TItem , TArg , CancellationToken , ValueTask < TResult > > selector , TArg arg , CancellationToken cancellationToken )
508561 {
509- var builder = ArrayBuilder < TResult > . GetInstance ( ) ;
562+ if ( ! TryGetBuilder < TItem , TResult > ( source , useCountForBuilder : true , out var builder ) )
563+ return [ ] ;
510564
511565 foreach ( var item in source )
512- {
513566 builder . Add ( await selector ( item , arg , cancellationToken ) . ConfigureAwait ( false ) ) ;
514- }
515567
516568 return builder . ToImmutableAndFree ( ) ;
517569 }
518570
519571 public static async ValueTask < ImmutableArray < TResult > > SelectManyAsArrayAsync < TItem , TArg , TResult > ( this IEnumerable < TItem > source , Func < TItem , TArg , CancellationToken , ValueTask < IEnumerable < TResult > > > selector , TArg arg , CancellationToken cancellationToken )
520572 {
521- var builder = ArrayBuilder < TResult > . GetInstance ( ) ;
573+ if ( ! TryGetBuilder < TItem , TResult > ( source , useCountForBuilder : false , out var builder ) )
574+ return [ ] ;
522575
523576 foreach ( var item in source )
524- {
525577 builder . AddRange ( await selector ( item , arg , cancellationToken ) . ConfigureAwait ( false ) ) ;
526- }
527578
528579 return builder . ToImmutableAndFree ( ) ;
529580 }
0 commit comments