diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/AggregateBy.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/AggregateBy.cs index 6456462d950637..4f3e6efb081c76 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/AggregateBy.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/AggregateBy.cs @@ -42,7 +42,9 @@ public static IAsyncEnumerable> AggregateBy>() : + Impl(source, keySelector, seed, func, keyComparer, default); static async IAsyncEnumerable> Impl( IAsyncEnumerable source, @@ -117,7 +119,9 @@ public static IAsyncEnumerable> AggregateBy>() : + Impl(source, keySelector, seed, func, keyComparer, default); static async IAsyncEnumerable> Impl( IAsyncEnumerable source, @@ -188,7 +192,9 @@ public static IAsyncEnumerable> AggregateBy>() : + Impl(source, keySelector, seedSelector, func, keyComparer, default); static async IAsyncEnumerable> Impl( IAsyncEnumerable source, @@ -264,7 +270,9 @@ public static IAsyncEnumerable> AggregateBy>() : + Impl(source, keySelector, seedSelector, func, keyComparer, default); static async IAsyncEnumerable> Impl( IAsyncEnumerable source, @@ -277,28 +285,26 @@ static async IAsyncEnumerable> Impl( IAsyncEnumerator enumerator = source.GetAsyncEnumerator(cancellationToken); try { - if (!await enumerator.MoveNextAsync().ConfigureAwait(false)) + if (await enumerator.MoveNextAsync().ConfigureAwait(false)) { - yield break; - } + Dictionary dict = new(keyComparer); - Dictionary dict = new(keyComparer); - - do - { - TSource value = enumerator.Current; - TKey key = await keySelector(value, cancellationToken).ConfigureAwait(false); + do + { + TSource value = enumerator.Current; + TKey key = await keySelector(value, cancellationToken).ConfigureAwait(false); - dict[key] = await func( - dict.TryGetValue(key, out TAccumulate? acc) ? acc : await seedSelector(key, cancellationToken).ConfigureAwait(false), - value, - cancellationToken).ConfigureAwait(false); - } - while (await enumerator.MoveNextAsync().ConfigureAwait(false)); + dict[key] = await func( + dict.TryGetValue(key, out TAccumulate? acc) ? acc : await seedSelector(key, cancellationToken).ConfigureAwait(false), + value, + cancellationToken).ConfigureAwait(false); + } + while (await enumerator.MoveNextAsync().ConfigureAwait(false)); - foreach (KeyValuePair countBy in dict) - { - yield return countBy; + foreach (KeyValuePair countBy in dict) + { + yield return countBy; + } } } finally diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Cast.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Cast.cs index 37121ea5f3977e..f065dacbd148d0 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Cast.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Cast.cs @@ -26,8 +26,9 @@ public static IAsyncEnumerable Cast( // satisfies the C# query { ThrowHelper.ThrowIfNull(source); - return source is IAsyncEnumerable result ? - result : + return + source.IsKnownEmpty() ? Empty() : + source as IAsyncEnumerable ?? Impl(source, default); static async IAsyncEnumerable Impl( diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Chunk.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Chunk.cs index 5392d953e1d960..f653d23970d9f4 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Chunk.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Chunk.cs @@ -30,7 +30,9 @@ public static IAsyncEnumerable Chunk( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNegativeOrZero(size); - return Chunk(source, size, default); + return + source.IsKnownEmpty() ? Empty() : + Chunk(source, size, default); async static IAsyncEnumerable Chunk( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Concat.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Concat.cs index e29d2f45384907..21d3f82988bdff 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Concat.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Concat.cs @@ -23,7 +23,10 @@ public static IAsyncEnumerable Concat( ThrowHelper.ThrowIfNull(first); ThrowHelper.ThrowIfNull(second); - return Impl(first, second, default); + return + first.IsKnownEmpty() ? second : + second.IsKnownEmpty() ? first : + Impl(first, second, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/CountBy.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/CountBy.cs index ba26521df5ca0a..612c21558ab115 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/CountBy.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/CountBy.cs @@ -28,7 +28,9 @@ public static IAsyncEnumerable> CountBy( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(keySelector); - return Impl(source, keySelector, keyComparer, default); + return + source.IsKnownEmpty() ? Empty>() : + Impl(source, keySelector, keyComparer, default); static async IAsyncEnumerable> Impl( IAsyncEnumerable source, Func keySelector, IEqualityComparer? keyComparer, [EnumeratorCancellation] CancellationToken cancellationToken) @@ -83,7 +85,9 @@ public static IAsyncEnumerable> CountBy( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(keySelector); - return Impl(source, keySelector, keyComparer, default); + return + source.IsKnownEmpty() ? Empty>() : + Impl(source, keySelector, keyComparer, default); static async IAsyncEnumerable> Impl( IAsyncEnumerable source, Func> keySelector, IEqualityComparer? keyComparer, [EnumeratorCancellation] CancellationToken cancellationToken) diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Distinct.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Distinct.cs index ea3d75e87bed25..eca8261d3a0996 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Distinct.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Distinct.cs @@ -21,7 +21,9 @@ public static IAsyncEnumerable Distinct( { ThrowHelper.ThrowIfNull(source); - return Impl(source, comparer, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/DistinctBy.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/DistinctBy.cs index 8c71a327abc514..18a228f7eb98b0 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/DistinctBy.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/DistinctBy.cs @@ -32,7 +32,9 @@ public static IAsyncEnumerable DistinctBy( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(keySelector); - return Impl(source, keySelector, comparer, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, keySelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -86,7 +88,9 @@ public static IAsyncEnumerable DistinctBy( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(keySelector); - return Impl(source, keySelector, comparer, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, keySelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Empty.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Empty.cs index fc1fb0dc7e9d78..2c39758ce16c28 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Empty.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Empty.cs @@ -16,9 +16,14 @@ public static partial class AsyncEnumerable /// An empty whose type argument is . public static IAsyncEnumerable Empty() => EmptyAsyncEnumerable.Instance; - private sealed class EmptyAsyncEnumerable : IAsyncEnumerable, IAsyncEnumerator + /// Determines whether is known to be an always-empty enumerable. + private static bool IsKnownEmpty(this IAsyncEnumerable source) => + ReferenceEquals(source, EmptyAsyncEnumerable.Instance); + + private sealed class EmptyAsyncEnumerable : + IAsyncEnumerable, IAsyncEnumerator, IOrderedAsyncEnumerable { - public static EmptyAsyncEnumerable Instance { get; } = new EmptyAsyncEnumerable(); + public static readonly EmptyAsyncEnumerable Instance = new(); public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => this; @@ -27,6 +32,18 @@ private sealed class EmptyAsyncEnumerable : IAsyncEnumerable, public TResult Current => default!; public ValueTask DisposeAsync() => default; + + public IOrderedAsyncEnumerable CreateOrderedAsyncEnumerable(Func keySelector, IComparer? comparer, bool descending) + { + ThrowHelper.ThrowIfNull(keySelector); + return this; + } + + public IOrderedAsyncEnumerable CreateOrderedAsyncEnumerable(Func> keySelector, IComparer? comparer, bool descending) + { + ThrowHelper.ThrowIfNull(keySelector); + return this; + } } } } diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Except.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Except.cs index a1946b1c0b04fa..e7a278c597abe6 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Except.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Except.cs @@ -26,7 +26,9 @@ public static IAsyncEnumerable Except( ThrowHelper.ThrowIfNull(first); ThrowHelper.ThrowIfNull(second); - return Impl(first, second, comparer, default); + return + first.IsKnownEmpty() ? Empty() : + Impl(first, second, comparer, default); async static IAsyncEnumerable Impl( IAsyncEnumerable first, @@ -34,19 +36,34 @@ async static IAsyncEnumerable Impl( IEqualityComparer? comparer, [EnumeratorCancellation] CancellationToken cancellationToken) { - HashSet set = new(comparer); - - await foreach (TSource element in second.WithCancellation(cancellationToken).ConfigureAwait(false)) + IAsyncEnumerator firstEnumerator = first.GetAsyncEnumerator(cancellationToken); + try { - set.Add(element); - } + if (!await firstEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield break; + } - await foreach (TSource element in first.WithCancellation(cancellationToken).ConfigureAwait(false)) - { - if (set.Add(element)) + HashSet set = new(comparer); + + await foreach (TSource element in second.WithCancellation(cancellationToken).ConfigureAwait(false)) { - yield return element; + set.Add(element); } + + do + { + TSource firstElement = firstEnumerator.Current; + if (set.Add(firstElement)) + { + yield return firstElement; + } + } + while (await firstEnumerator.MoveNextAsync().ConfigureAwait(false)); + } + finally + { + await firstEnumerator.DisposeAsync().ConfigureAwait(false); } } } diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ExceptBy.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ExceptBy.cs index cabc34ceeaaa7a..1d813d8923d21d 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ExceptBy.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ExceptBy.cs @@ -33,7 +33,9 @@ public static IAsyncEnumerable ExceptBy( ThrowHelper.ThrowIfNull(second); ThrowHelper.ThrowIfNull(keySelector); - return Impl(first, second, keySelector, comparer, default); + return + first.IsKnownEmpty() ? Empty() : + Impl(first, second, keySelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, @@ -42,19 +44,34 @@ static async IAsyncEnumerable Impl( IEqualityComparer? comparer, [EnumeratorCancellation] CancellationToken cancellationToken) { - HashSet set = new(comparer); - - await foreach (TKey key in second.WithCancellation(cancellationToken).ConfigureAwait(false)) + IAsyncEnumerator firstEnumerator = first.GetAsyncEnumerator(cancellationToken); + try { - set.Add(key); - } + if (!await firstEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield break; + } - await foreach (TSource element in first.WithCancellation(cancellationToken).ConfigureAwait(false)) - { - if (set.Add(keySelector(element))) + HashSet set = new(comparer); + + await foreach (TKey key in second.WithCancellation(cancellationToken).ConfigureAwait(false)) { - yield return element; + set.Add(key); } + + do + { + TSource firstElement = firstEnumerator.Current; + if (set.Add(keySelector(firstElement))) + { + yield return firstElement; + } + } + while (await firstEnumerator.MoveNextAsync().ConfigureAwait(false)); + } + finally + { + await firstEnumerator.DisposeAsync().ConfigureAwait(false); } } } @@ -82,7 +99,9 @@ public static IAsyncEnumerable ExceptBy( ThrowHelper.ThrowIfNull(second); ThrowHelper.ThrowIfNull(keySelector); - return Impl(first, second, keySelector, comparer, default); + return + first.IsKnownEmpty() ? Empty() : + Impl(first, second, keySelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, @@ -91,19 +110,34 @@ static async IAsyncEnumerable Impl( IEqualityComparer? comparer, [EnumeratorCancellation] CancellationToken cancellationToken) { - HashSet set = new(comparer); - - await foreach (TKey key in second.WithCancellation(cancellationToken).ConfigureAwait(false)) + IAsyncEnumerator firstEnumerator = first.GetAsyncEnumerator(cancellationToken); + try { - set.Add(key); - } + if (!await firstEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + yield break; + } - await foreach (TSource element in first.WithCancellation(cancellationToken).ConfigureAwait(false)) - { - if (set.Add(await keySelector(element, cancellationToken).ConfigureAwait(false))) + HashSet set = new(comparer); + + await foreach (TKey key in second.WithCancellation(cancellationToken).ConfigureAwait(false)) { - yield return element; + set.Add(key); } + + do + { + TSource firstElement = firstEnumerator.Current; + if (set.Add(await keySelector(firstElement, cancellationToken).ConfigureAwait(false))) + { + yield return firstElement; + } + } + while (await firstEnumerator.MoveNextAsync().ConfigureAwait(false)); + } + finally + { + await firstEnumerator.DisposeAsync().ConfigureAwait(false); } } } diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/GroupBy.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/GroupBy.cs index 8eac9561756b5e..9fddf1cbd17c86 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/GroupBy.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/GroupBy.cs @@ -32,7 +32,9 @@ public static IAsyncEnumerable> GroupBy( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(keySelector); - return Impl(source, keySelector, comparer, default); + return + source.IsKnownEmpty() ? Empty>() : + Impl(source, keySelector, comparer, default); static async IAsyncEnumerable> Impl( IAsyncEnumerable source, @@ -67,7 +69,9 @@ public static IAsyncEnumerable> GroupBy( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(keySelector); - return Impl(source, keySelector, comparer, default); + return + source.IsKnownEmpty() ? Empty>() : + Impl(source, keySelector, comparer, default); static async IAsyncEnumerable> Impl( IAsyncEnumerable source, @@ -111,7 +115,9 @@ public static IAsyncEnumerable> GroupBy>() : + Impl(source, keySelector, elementSelector, comparer, default); static async IAsyncEnumerable> Impl( IAsyncEnumerable source, @@ -156,7 +162,9 @@ public static IAsyncEnumerable> GroupBy>() : + Impl(source, keySelector, elementSelector, comparer, default); static async IAsyncEnumerable> Impl( IAsyncEnumerable source, @@ -200,7 +208,9 @@ public static IAsyncEnumerable GroupBy( ThrowHelper.ThrowIfNull(keySelector); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(source, keySelector, resultSelector, comparer, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, keySelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -209,10 +219,12 @@ static async IAsyncEnumerable Impl( IEqualityComparer? comparer, [EnumeratorCancellation] CancellationToken cancellationToken) { - var lookup = (AsyncLookup)await ToLookupAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false); - foreach (TResult item in lookup.ApplyResultSelector(resultSelector)) + if (await ToLookupAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false) is AsyncLookup lookup) { - yield return item; + foreach (TResult item in lookup.ApplyResultSelector(resultSelector)) + { + yield return item; + } } } } @@ -245,7 +257,9 @@ public static IAsyncEnumerable GroupBy( ThrowHelper.ThrowIfNull(keySelector); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(source, keySelector, resultSelector, comparer, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, keySelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -254,10 +268,12 @@ static async IAsyncEnumerable Impl( IEqualityComparer? comparer, [EnumeratorCancellation] CancellationToken cancellationToken) { - var lookup = (AsyncLookup)await ToLookupAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false); - await foreach (TResult item in lookup.ApplyResultSelector(resultSelector, cancellationToken).ConfigureAwait(false)) + if (await ToLookupAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false) is AsyncLookup lookup) { - yield return item; + await foreach (TResult item in lookup.ApplyResultSelector(resultSelector, cancellationToken).ConfigureAwait(false)) + { + yield return item; + } } } } @@ -294,7 +310,9 @@ public static IAsyncEnumerable GroupBy() : + Impl(source, keySelector, elementSelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -304,10 +322,12 @@ static async IAsyncEnumerable Impl( IEqualityComparer? comparer, [EnumeratorCancellation] CancellationToken cancellationToken) { - var lookup = (AsyncLookup)await ToLookupAsync(source, keySelector, elementSelector, comparer, cancellationToken).ConfigureAwait(false); - foreach (TResult item in lookup.ApplyResultSelector(resultSelector)) + if (await ToLookupAsync(source, keySelector, elementSelector, comparer, cancellationToken).ConfigureAwait(false) is AsyncLookup lookup) { - yield return item; + foreach (TResult item in lookup.ApplyResultSelector(resultSelector)) + { + yield return item; + } } } } @@ -344,7 +364,9 @@ public static IAsyncEnumerable GroupBy() : + Impl(source, keySelector, elementSelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -354,10 +376,12 @@ static async IAsyncEnumerable Impl( IEqualityComparer? comparer, [EnumeratorCancellation] CancellationToken cancellationToken) { - var lookup = (AsyncLookup)await ToLookupAsync(source, keySelector, elementSelector, comparer, cancellationToken).ConfigureAwait(false); - await foreach (TResult item in lookup.ApplyResultSelector(resultSelector, cancellationToken).ConfigureAwait(false)) + if (await ToLookupAsync(source, keySelector, elementSelector, comparer, cancellationToken).ConfigureAwait(false) is AsyncLookup lookup) { - yield return item; + await foreach (TResult item in lookup.ApplyResultSelector(resultSelector, cancellationToken).ConfigureAwait(false)) + { + yield return item; + } } } } diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/GroupJoin.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/GroupJoin.cs index 3b2d56147bb31d..769ccb65b14284 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/GroupJoin.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/GroupJoin.cs @@ -47,7 +47,9 @@ public static IAsyncEnumerable GroupJoin ThrowHelper.ThrowIfNull(innerKeySelector); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); + return + outer.IsKnownEmpty() ? Empty() : + Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable outer, @@ -116,7 +118,9 @@ public static IAsyncEnumerable GroupJoin ThrowHelper.ThrowIfNull(innerKeySelector); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); + return + outer.IsKnownEmpty() ? Empty() : + Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable outer, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Index.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Index.cs index 758ca28b6b5d72..efbdb9aa203caa 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Index.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Index.cs @@ -20,7 +20,9 @@ public static partial class AsyncEnumerable { ThrowHelper.ThrowIfNull(source); - return Impl(source, default); + return + source.IsKnownEmpty() ? Empty<(int Index, TSource Item)>() : + Impl(source, default); static async IAsyncEnumerable<(int Index, TSource Item)> Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Intersect.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Intersect.cs index 97382080923145..22e1eab34c34a4 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Intersect.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Intersect.cs @@ -26,7 +26,9 @@ public static IAsyncEnumerable Intersect( ThrowHelper.ThrowIfNull(first); ThrowHelper.ThrowIfNull(second); - return Impl(first, second, comparer, default); + return + first.IsKnownEmpty() || second.IsKnownEmpty() ? Empty() : + Impl(first, second, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/IntersectBy.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/IntersectBy.cs index 2960209f2f43c5..18c4a52f3cc13f 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/IntersectBy.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/IntersectBy.cs @@ -38,7 +38,9 @@ public static IAsyncEnumerable IntersectBy( ThrowHelper.ThrowIfNull(second); ThrowHelper.ThrowIfNull(keySelector); - return Impl(first, second, keySelector, comparer, default); + return + first.IsKnownEmpty() || second.IsKnownEmpty() ? Empty() : + Impl(first, second, keySelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, @@ -106,7 +108,9 @@ public static IAsyncEnumerable IntersectBy( ThrowHelper.ThrowIfNull(second); ThrowHelper.ThrowIfNull(keySelector); - return Impl(first, second, keySelector, comparer, default); + return + first.IsKnownEmpty() || second.IsKnownEmpty() ? Empty() : + Impl(first, second, keySelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Join.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Join.cs index d6acc4a3012d35..cac87ca1f4f724 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Join.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Join.cs @@ -44,7 +44,9 @@ public static IAsyncEnumerable Join( // ThrowHelper.ThrowIfNull(innerKeySelector); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); + return + outer.IsKnownEmpty() || inner.IsKnownEmpty() ? Empty() : + Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable outer, IAsyncEnumerable inner, @@ -121,7 +123,9 @@ public static IAsyncEnumerable Join( ThrowHelper.ThrowIfNull(innerKeySelector); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); + return + outer.IsKnownEmpty() || inner.IsKnownEmpty() ? Empty() : + Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable outer, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/LeftJoin.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/LeftJoin.cs index acce0f765694ad..38eebe64e5251c 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/LeftJoin.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/LeftJoin.cs @@ -41,7 +41,9 @@ public static IAsyncEnumerable LeftJoin( ThrowHelper.ThrowIfNull(innerKeySelector); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); + return + outer.IsKnownEmpty() ? Empty() : + Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable outer, IAsyncEnumerable inner, @@ -116,7 +118,9 @@ public static IAsyncEnumerable LeftJoin( ThrowHelper.ThrowIfNull(innerKeySelector); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); + return + outer.IsKnownEmpty() ? Empty() : + Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable outer, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/OfType.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/OfType.cs index 2aa02ebb54858b..0abad165b4dd7b 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/OfType.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/OfType.cs @@ -26,7 +26,9 @@ public static IAsyncEnumerable OfType( { ThrowHelper.ThrowIfNull(source); - return Impl(source, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/OrderBy.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/OrderBy.cs index 64414b2e11bc7b..a4ee8f30d65c16 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/OrderBy.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/OrderBy.cs @@ -33,8 +33,15 @@ public static IOrderedAsyncEnumerable Order( public static IOrderedAsyncEnumerable OrderBy( // satisfies the C# query-expression pattern this IAsyncEnumerable source, Func keySelector, - IComparer? comparer = null) => - new OrderedIterator(source, keySelector, comparer, false, null); + IComparer? comparer = null) + { + ThrowHelper.ThrowIfNull(source); + ThrowHelper.ThrowIfNull(keySelector); + + return + source.IsKnownEmpty() ? EmptyAsyncEnumerable.Instance : + new OrderedIterator(source, keySelector, comparer, false, null); + } /// Sorts the elements of a sequence in ascending order. /// The type of the elements of . @@ -48,8 +55,15 @@ public static IOrderedAsyncEnumerable OrderBy( // satisf public static IOrderedAsyncEnumerable OrderBy( this IAsyncEnumerable source, Func> keySelector, - IComparer? comparer = null) => - new OrderedIterator(source, keySelector, comparer, false, null); + IComparer? comparer = null) + { + ThrowHelper.ThrowIfNull(source); + ThrowHelper.ThrowIfNull(keySelector); + + return + source.IsKnownEmpty() ? EmptyAsyncEnumerable.Instance : + new OrderedIterator(source, keySelector, comparer, false, null); + } /// Sorts the elements of a sequence in descending order. /// The type of the elements of . @@ -74,8 +88,15 @@ public static IOrderedAsyncEnumerable OrderDescending( public static IOrderedAsyncEnumerable OrderByDescending( // satisfies the C# query-expression pattern this IAsyncEnumerable source, Func keySelector, - IComparer? comparer = null) => - new OrderedIterator(source, keySelector, comparer, true, null); + IComparer? comparer = null) + { + ThrowHelper.ThrowIfNull(source); + ThrowHelper.ThrowIfNull(keySelector); + + return + source.IsKnownEmpty() ? EmptyAsyncEnumerable.Instance : + new OrderedIterator(source, keySelector, comparer, true, null); + } /// Sorts the elements of a sequence in descending order. /// The type of the elements of . @@ -89,8 +110,15 @@ public static IOrderedAsyncEnumerable OrderByDescending( public static IOrderedAsyncEnumerable OrderByDescending( this IAsyncEnumerable source, Func> keySelector, - IComparer? comparer = null) => - new OrderedIterator(source, keySelector, comparer, true, null); + IComparer? comparer = null) + { + ThrowHelper.ThrowIfNull(source); + ThrowHelper.ThrowIfNull(keySelector); + + return + source.IsKnownEmpty() ? EmptyAsyncEnumerable.Instance : + new OrderedIterator(source, keySelector, comparer, true, null); + } /// Performs a subsequent ordering of the elements in a sequence in ascending order. /// The type of the elements of . @@ -198,9 +226,6 @@ private sealed partial class OrderedIterator : OrderedIterator source, object keySelector, IComparer? comparer, bool descending, OrderedIterator? parent) : base(source) { - ThrowHelper.ThrowIfNull(source); - ThrowHelper.ThrowIfNull(keySelector); - Debug.Assert(keySelector is Func or Func>); _parent = parent; diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Reverse.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Reverse.cs index 4ecc083ca54a13..424d6d78144135 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Reverse.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Reverse.cs @@ -19,7 +19,9 @@ public static IAsyncEnumerable Reverse( { ThrowHelper.ThrowIfNull(source); - return Impl(source, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/RightJoin.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/RightJoin.cs index 810496d53ea793..bec5def85f942b 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/RightJoin.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/RightJoin.cs @@ -41,7 +41,9 @@ public static IAsyncEnumerable RightJoin ThrowHelper.ThrowIfNull(innerKeySelector); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); + return + inner.IsKnownEmpty() ? Empty() : + Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable outer, @@ -117,7 +119,9 @@ public static IAsyncEnumerable RightJoin ThrowHelper.ThrowIfNull(innerKeySelector); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); + return + inner.IsKnownEmpty() ? Empty() : + Impl(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable outer, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Select.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Select.cs index a366e55111b158..448f77f09ab3f9 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Select.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Select.cs @@ -28,7 +28,9 @@ public static IAsyncEnumerable Select( // satisfies t ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(selector); - return Impl(source, selector, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, selector, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -60,7 +62,9 @@ public static IAsyncEnumerable Select( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(selector); - return Impl(source, selector, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, selector, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -95,7 +99,9 @@ public static IAsyncEnumerable Select( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(selector); - return Impl(source, selector, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, selector, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -131,7 +137,9 @@ public static IAsyncEnumerable Select( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(selector); - return Impl(source, selector, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, selector, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SelectMany.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SelectMany.cs index 3789754f2a994e..770eb9f70a7b07 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SelectMany.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SelectMany.cs @@ -31,7 +31,9 @@ public static IAsyncEnumerable SelectMany( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(selector); - return Impl(source, selector, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, selector, default); async static IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -69,7 +71,9 @@ public static IAsyncEnumerable SelectMany( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(selector); - return Impl(source, selector, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, selector, default); async static IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -107,7 +111,9 @@ public static IAsyncEnumerable SelectMany( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(selector); - return Impl(source, selector, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, selector, default); async static IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -146,7 +152,9 @@ public static IAsyncEnumerable SelectMany( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(selector); - return Impl(source, selector, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, selector, default); async static IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -186,7 +194,9 @@ public static IAsyncEnumerable SelectMany( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(selector); - return Impl(source, selector, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, selector, default); async static IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -226,7 +236,9 @@ public static IAsyncEnumerable SelectMany( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(selector); - return Impl(source, selector, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, selector, default); async static IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -274,7 +286,9 @@ public static IAsyncEnumerable SelectMany() : + Impl(source, collectionSelector, resultSelector, default); async static IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -322,7 +336,9 @@ public static IAsyncEnumerable SelectMany() : + Impl(source, collectionSelector, resultSelector, default); async static IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -370,7 +386,9 @@ public static IAsyncEnumerable SelectMany() : + Impl(source, collectionSelector, resultSelector, default); async static IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -418,7 +436,9 @@ public static IAsyncEnumerable SelectMany() : + Impl(source, collectionSelector, resultSelector, default); async static IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -465,7 +485,9 @@ public static IAsyncEnumerable SelectMany() : + Impl(source, collectionSelector, resultSelector, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -513,7 +535,9 @@ public static IAsyncEnumerable SelectMany() : + Impl(source, collectionSelector, resultSelector, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -561,7 +585,9 @@ public static IAsyncEnumerable SelectMany() : + Impl(source, collectionSelector, resultSelector, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Shuffle.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Shuffle.cs index a798976a5aef84..e2d5d89a59eb98 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Shuffle.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Shuffle.cs @@ -24,7 +24,9 @@ public static IAsyncEnumerable Shuffle( { ThrowHelper.ThrowIfNull(source); - return Impl(source, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Skip.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Skip.cs index 764f9511c4e644..c6e9cb13e89230 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Skip.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Skip.cs @@ -21,8 +21,9 @@ public static IAsyncEnumerable Skip( { ThrowHelper.ThrowIfNull(source); - return count <= 0 ? - source : + return + source.IsKnownEmpty() ? Empty() : + count <= 0 ? source : Impl(source, count, default); static async IAsyncEnumerable Impl( diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SkipLast.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SkipLast.cs index 9856d9d7f56a8b..e6f6966e09f465 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SkipLast.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SkipLast.cs @@ -26,6 +26,7 @@ public static IAsyncEnumerable SkipLast( ThrowHelper.ThrowIfNull(source); return + source.IsKnownEmpty() ? Empty() : count <= 0 ? source : TakeRangeFromEndIterator(source, isStartIndexFromEnd: false, startIndex: 0, isEndIndexFromEnd: true, endIndex: count, default); } diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SkipWhile.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SkipWhile.cs index 976508a944ff05..7738765e360f45 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SkipWhile.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/SkipWhile.cs @@ -31,7 +31,9 @@ public static IAsyncEnumerable SkipWhile( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -84,7 +86,9 @@ public static IAsyncEnumerable SkipWhile( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -141,7 +145,9 @@ public static IAsyncEnumerable SkipWhile( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -199,7 +205,9 @@ public static IAsyncEnumerable SkipWhile( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Take.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Take.cs index 003df03d59177f..0e1cd012395200 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Take.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Take.cs @@ -26,8 +26,8 @@ public static IAsyncEnumerable Take( { ThrowHelper.ThrowIfNull(source); - return count <= 0 ? - Empty() : + return + source.IsKnownEmpty() || count <= 0 ? Empty() : Impl(source, count, default); static async IAsyncEnumerable Impl( @@ -63,6 +63,11 @@ public static IAsyncEnumerable Take( { ThrowHelper.ThrowIfNull(source); + if (source.IsKnownEmpty()) + { + return Empty(); + } + Index start = range.Start, end = range.End; bool isStartIndexFromEnd = start.IsFromEnd, isEndIndexFromEnd = end.IsFromEnd; int startIndex = start.Value, endIndex = end.Value; @@ -78,8 +83,8 @@ public static IAsyncEnumerable Take( } else if (!isEndIndexFromEnd) { - return startIndex >= endIndex ? - Empty() : + return + startIndex >= endIndex ? Empty() : Impl(source, startIndex, endIndex, default); } diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/TakeLast.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/TakeLast.cs index b3fd0df73684cc..dff268880d09a4 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/TakeLast.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/TakeLast.cs @@ -18,8 +18,8 @@ public static IAsyncEnumerable TakeLast( { ThrowHelper.ThrowIfNull(source); - return count <= 0 ? - Empty() : + return + source.IsKnownEmpty() || count <= 0 ? Empty() : TakeRangeFromEndIterator(source, isStartIndexFromEnd: true, startIndex: count, isEndIndexFromEnd: true, endIndex: 0, default); } } diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/TakeWhile.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/TakeWhile.cs index cf9e95b5047a74..e1fac70ccf9cb2 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/TakeWhile.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/TakeWhile.cs @@ -27,7 +27,9 @@ public static IAsyncEnumerable TakeWhile( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, Func predicate, @@ -62,7 +64,9 @@ public static IAsyncEnumerable TakeWhile( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -101,7 +105,9 @@ public static IAsyncEnumerable TakeWhile( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -141,7 +147,9 @@ public static IAsyncEnumerable TakeWhile( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToArrayAsync.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToArrayAsync.cs index 6e5dd1e16d1188..beefd39d3c1340 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToArrayAsync.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToArrayAsync.cs @@ -27,13 +27,27 @@ public static ValueTask ToArrayAsync( static async ValueTask Impl( ConfiguredCancelableAsyncEnumerable source) { - List list = []; - await foreach (TSource element in source) + ConfiguredCancelableAsyncEnumerable.Enumerator e = source.GetAsyncEnumerator(); + try { - list.Add(element); - } + if (await e.MoveNextAsync()) + { + List list = []; + do + { + list.Add(e.Current); + } + while (await e.MoveNextAsync()); + + return list.ToArray(); + } - return list.ToArray(); + return []; + } + finally + { + await e.DisposeAsync(); + } } } } diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToAsyncEnumerable.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToAsyncEnumerable.cs index 7f867a5aa51fa8..ff8b8c880a0f09 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToAsyncEnumerable.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToAsyncEnumerable.cs @@ -22,9 +22,10 @@ public static IAsyncEnumerable ToAsyncEnumerable( return source switch { - TSource[] array => FromArray(array), + TSource[] array => array.Length == 0 ? Empty() : FromArray(array), List list => FromList(list), IList list => FromIList(list), + _ when source == Enumerable.Empty() => Empty(), _ => FromIterator(source), }; diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToLookupAsync.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToLookupAsync.cs index 17cd7f1475bbca..dda7798c5f4826 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToLookupAsync.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/ToLookupAsync.cs @@ -41,13 +41,28 @@ static async ValueTask> Impl( Func keySelector, IEqualityComparer? comparer) { - var lookup = new AsyncLookup(comparer); - await foreach (TSource item in source) + ConfiguredCancelableAsyncEnumerable.Enumerator e = source.GetAsyncEnumerator(); + try { - lookup.GetGrouping(keySelector(item), create: true)!.Add(item); - } + if (!await e.MoveNextAsync()) + { + return EmptyLookup.Instance; + } - return lookup; + AsyncLookup lookup = new(comparer); + do + { + TSource item = e.Current; + lookup.GetGrouping(keySelector(item), create: true)!.Add(item); + } + while (await e.MoveNextAsync()); + + return lookup; + } + finally + { + await e.DisposeAsync(); + } } } @@ -81,13 +96,28 @@ static async ValueTask> Impl( IEqualityComparer? comparer, CancellationToken cancellationToken) { - var lookup = new AsyncLookup(comparer); - await foreach (TSource item in source.WithCancellation(cancellationToken).ConfigureAwait(false)) + IAsyncEnumerator e = source.GetAsyncEnumerator(cancellationToken); + try { - lookup.GetGrouping(await keySelector(item, cancellationToken).ConfigureAwait(false), create: true)!.Add(item); - } + if (!await e.MoveNextAsync().ConfigureAwait(false)) + { + return EmptyLookup.Instance; + } - return lookup; + AsyncLookup lookup = new(comparer); + do + { + TSource item = e.Current; + lookup.GetGrouping(await keySelector(item, cancellationToken).ConfigureAwait(false), create: true)!.Add(item); + } + while (await e.MoveNextAsync().ConfigureAwait(false)); + + return lookup; + } + finally + { + await e.DisposeAsync().ConfigureAwait(false); + } } } @@ -125,13 +155,28 @@ static async ValueTask> Impl( Func elementSelector, IEqualityComparer? comparer) { - var lookup = new AsyncLookup(comparer); - await foreach (TSource item in source) + ConfiguredCancelableAsyncEnumerable.Enumerator e = source.GetAsyncEnumerator(); + try { - lookup.GetGrouping(keySelector(item), create: true)!.Add(elementSelector(item)); - } + if (!await e.MoveNextAsync()) + { + return EmptyLookup.Instance; + } - return lookup; + AsyncLookup lookup = new(comparer); + do + { + TSource item = e.Current; + lookup.GetGrouping(keySelector(item), create: true)!.Add(elementSelector(item)); + } + while (await e.MoveNextAsync()); + + return lookup; + } + finally + { + await e.DisposeAsync(); + } } } @@ -170,15 +215,76 @@ static async ValueTask> Impl( IEqualityComparer? comparer, CancellationToken cancellationToken) { - var lookup = new AsyncLookup(comparer); - await foreach (TSource item in source.WithCancellation(cancellationToken).ConfigureAwait(false)) + IAsyncEnumerator e = source.GetAsyncEnumerator(cancellationToken); + try { - lookup.GetGrouping(await keySelector(item, cancellationToken).ConfigureAwait(false), create: true)! - .Add(await elementSelector(item, cancellationToken).ConfigureAwait(false)); + if (!await e.MoveNextAsync().ConfigureAwait(false)) + { + return EmptyLookup.Instance; + } + + AsyncLookup lookup = new(comparer); + do + { + TSource item = e.Current; + lookup.GetGrouping(await keySelector(item, cancellationToken).ConfigureAwait(false), create: true)!.Add(await elementSelector(item, cancellationToken).ConfigureAwait(false)); + } + while (await e.MoveNextAsync().ConfigureAwait(false)); + + return lookup; + } + finally + { + await e.DisposeAsync().ConfigureAwait(false); } + } + } - return lookup; + [DebuggerDisplay("Count = 0")] + private sealed class EmptyLookup : ILookup, IList>, IReadOnlyCollection> + { + public static readonly EmptyLookup Instance = new(); + + public bool IsReadOnly => true; + + public int Count => 0; + + public IEnumerable this[TKey key] => []; + + public IEnumerator> GetEnumerator() => Enumerable.Empty>().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public bool Contains(TKey key) => false; + + public bool Contains(IGrouping item) => false; + + public void CopyTo(IGrouping[] array, int arrayIndex) + { + ThrowHelper.ThrowIfNull(array); + if ((uint)arrayIndex > (uint)array.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(arrayIndex)); + } + } + + public int IndexOf(IGrouping item) => -1; + + public void Add(IGrouping item) => throw new NotSupportedException(); + + public void Clear() => throw new NotSupportedException(); + + public IGrouping this[int index] + { + get => throw new ArgumentOutOfRangeException(nameof(index)); + set => throw new NotSupportedException(); } + + public void Insert(int index, IGrouping item) => throw new NotSupportedException(); + + public bool Remove(IGrouping item) => throw new NotSupportedException(); + + public void RemoveAt(int index) => throw new NotSupportedException(); } [DebuggerDisplay("Count = {Count}")] diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Union.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Union.cs index 17b9e857f3599d..0d0ebe01d94543 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Union.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Union.cs @@ -26,7 +26,9 @@ public static IAsyncEnumerable Union( ThrowHelper.ThrowIfNull(first); ThrowHelper.ThrowIfNull(second); - return Impl(first, second, comparer, default); + return + first.IsKnownEmpty() && second.IsKnownEmpty() ? Empty() : + Impl(first, second, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/UnionBy.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/UnionBy.cs index ccf80ed292c923..94500949c2172a 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/UnionBy.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/UnionBy.cs @@ -30,7 +30,9 @@ public static IAsyncEnumerable UnionBy( ThrowHelper.ThrowIfNull(second); ThrowHelper.ThrowIfNull(keySelector); - return Impl(first, second, keySelector, comparer, default); + return + first.IsKnownEmpty() && second.IsKnownEmpty() ? Empty() : + Impl(first, second, keySelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, @@ -79,7 +81,9 @@ public static IAsyncEnumerable UnionBy( ThrowHelper.ThrowIfNull(second); ThrowHelper.ThrowIfNull(keySelector); - return Impl(first, second, keySelector, comparer, default); + return + first.IsKnownEmpty() && second.IsKnownEmpty() ? Empty() : + Impl(first, second, keySelector, comparer, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Where.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Where.cs index e915df69ac4166..768d1b75dfd603 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Where.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Where.cs @@ -24,7 +24,9 @@ public static IAsyncEnumerable Where( // satisfies the C# quer ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -55,7 +57,9 @@ public static IAsyncEnumerable Where( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -92,7 +96,9 @@ public static IAsyncEnumerable Where( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, @@ -130,7 +136,9 @@ public static IAsyncEnumerable Where( ThrowHelper.ThrowIfNull(source); ThrowHelper.ThrowIfNull(predicate); - return Impl(source, predicate, default); + return + source.IsKnownEmpty() ? Empty() : + Impl(source, predicate, default); static async IAsyncEnumerable Impl( IAsyncEnumerable source, diff --git a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Zip.cs b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Zip.cs index d4e7e6b2e6c0e4..37ce9d33609948 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Zip.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/src/System/Linq/Zip.cs @@ -33,7 +33,9 @@ public static IAsyncEnumerable Zip( ThrowHelper.ThrowIfNull(second); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(first, second, resultSelector, default); + return + first.IsKnownEmpty() || second.IsKnownEmpty() ? Empty() : + Impl(first, second, resultSelector, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, @@ -88,7 +90,9 @@ public static IAsyncEnumerable Zip( ThrowHelper.ThrowIfNull(second); ThrowHelper.ThrowIfNull(resultSelector); - return Impl(first, second, resultSelector, default); + return + first.IsKnownEmpty() || second.IsKnownEmpty() ? Empty() : + Impl(first, second, resultSelector, default); static async IAsyncEnumerable Impl( IAsyncEnumerable first, @@ -135,7 +139,9 @@ await e2.MoveNextAsync().ConfigureAwait(false)) ThrowHelper.ThrowIfNull(first); ThrowHelper.ThrowIfNull(second); - return Impl(first, second, default); + return + first.IsKnownEmpty() || second.IsKnownEmpty() ? Empty<(TFirst, TSecond)>() : + Impl(first, second, default); static async IAsyncEnumerable<(TFirst First, TSecond Second)> Impl( IAsyncEnumerable first, @@ -186,7 +192,9 @@ await e2.MoveNextAsync().ConfigureAwait(false)) ThrowHelper.ThrowIfNull(second); ThrowHelper.ThrowIfNull(third); - return Impl(first, second, third, default); + return + first.IsKnownEmpty() || second.IsKnownEmpty() || third.IsKnownEmpty() ? Empty<(TFirst, TSecond, TThird)>() : + Impl(first, second, third, default); static async IAsyncEnumerable<(TFirst First, TSecond Second, TThird)> Impl( IAsyncEnumerable first, IAsyncEnumerable second, IAsyncEnumerable third, [EnumeratorCancellation] CancellationToken cancellationToken) diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/AggregateByTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/AggregateByTests.cs index 3db27d3f02219a..09e94193c658bf 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/AggregateByTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/AggregateByTests.cs @@ -32,6 +32,15 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("func", () => AsyncEnumerable.AggregateBy(AsyncEnumerable.Empty(), async (x, ct) => x, async (x, ct) => x, (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty>(), AsyncEnumerable.Empty().AggregateBy(x => x, x => x, (x, y) => x + y)); + Assert.Same(AsyncEnumerable.Empty>(), AsyncEnumerable.Empty().AggregateBy(x => x, 42, (x, y) => x + y)); + Assert.Same(AsyncEnumerable.Empty>(), AsyncEnumerable.Empty().AggregateBy(async (x, ct) => x, async (x, ct) => x, async (x, y, ct) => x + y)); + Assert.Same(AsyncEnumerable.Empty>(), AsyncEnumerable.Empty().AggregateBy(async (x, ct) => x, 42, async (x, y, ct) => x + y)); + } + public static IEnumerable VariousValues_MatchesEnumerable_String_MemberData() { yield return new object[] { new string[0] }; diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/AsyncEnumerableTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/AsyncEnumerableTests.cs index f1000bb272420a..ccbf00771f68ae 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/AsyncEnumerableTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/AsyncEnumerableTests.cs @@ -16,6 +16,12 @@ protected static IAsyncEnumerable CreateSource(params T[] items) => protected static IEnumerable> CreateSources(params T[] items) { + if (items.Length == 0) + { + yield return Enumerable.Empty().ToAsyncEnumerable(); + yield return AsyncEnumerable.Empty(); + } + yield return items.ToAsyncEnumerable(); yield return items.ToAsyncEnumerable().Yield(); } diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/CastTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/CastTests.cs index a7a88b8644d234..9f4b4d90a4267e 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/CastTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/CastTests.cs @@ -17,10 +17,10 @@ public void InvalidInputs_Throws() } [Fact] - public async Task Empty_ProducesEmpty() + public void Empty_ProducesEmpty() // validating an optimization / implementation detail { - await AssertEqual(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Cast()); - await AssertEqual(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Cast()); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Cast()); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Cast()); } [Fact] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/ChunkTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/ChunkTests.cs index aa900c15e633f4..3af01028ec5a00 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/ChunkTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/ChunkTests.cs @@ -20,6 +20,12 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("size", () => AsyncEnumerable.Chunk(AsyncEnumerable.Empty(), -1)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Chunk(1)); + } + #if NET [Fact] public async Task VariousValues_MatchesEnumerable() diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/ConcatTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/ConcatTests.cs index b6d649300af7ae..35110b12aae896 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/ConcatTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/ConcatTests.cs @@ -17,6 +17,17 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("second", () => AsyncEnumerable.Concat(AsyncEnumerable.Empty(), null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + IAsyncEnumerable empty = AsyncEnumerable.Empty(); + IAsyncEnumerable nonEmpty = CreateSource(1, 3, 5); + + Assert.Same(empty, empty.Concat(empty)); + Assert.Same(nonEmpty, nonEmpty.Concat(empty)); + Assert.Same(nonEmpty, empty.Concat(nonEmpty)); + } + [Theory] [InlineData(new int[0], new int[0])] [InlineData(new int[0], new int[] { 42 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/CountByTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/CountByTests.cs index 4ba4aff94fb9de..b877602ea5088f 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/CountByTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/CountByTests.cs @@ -18,6 +18,13 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("keySelector", () => AsyncEnumerable.CountBy(AsyncEnumerable.Empty(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty>(), AsyncEnumerable.Empty().CountBy(i => i)); + Assert.Same(AsyncEnumerable.Empty>(), AsyncEnumerable.Empty().CountBy(async (i, ct) => i)); + } + #if NET [Fact] public async Task VariousValues_MatchesEnumerable_Strings() diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/DistinctByTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/DistinctByTests.cs index 3a8b4fd64145b0..d7079fd462bf5f 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/DistinctByTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/DistinctByTests.cs @@ -20,6 +20,13 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("keySelector", () => AsyncEnumerable.DistinctBy(AsyncEnumerable.Empty(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().DistinctBy(i => i)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().DistinctBy(async (i, ct) => i)); + } + #if NET [Theory] [InlineData(new int[0])] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/DistinctTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/DistinctTests.cs index f30c58a5d97ab5..0f7524827eb405 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/DistinctTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/DistinctTests.cs @@ -16,6 +16,13 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("source", () => AsyncEnumerable.Distinct(null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Distinct()); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Distinct()); + } + [Theory] [InlineData(new int[0])] [InlineData(new int[] { 1 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/ExceptByTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/ExceptByTests.cs index b455f76f33492f..894063958c5178 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/ExceptByTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/ExceptByTests.cs @@ -22,6 +22,13 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("keySelector", () => AsyncEnumerable.ExceptBy(AsyncEnumerable.Empty(), AsyncEnumerable.Empty(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().ExceptBy(CreateSource(1, 2, 3), i => i)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().ExceptBy(CreateSource(1, 2, 3), async (i, ct) => i)); + } + #if NET [Theory] [InlineData(new int[0], new int[0])] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/ExceptTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/ExceptTests.cs index b79ad34dda52ca..fcd16bfc103792 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/ExceptTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/ExceptTests.cs @@ -17,6 +17,12 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("second", () => AsyncEnumerable.Except(AsyncEnumerable.Empty(), null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Except(CreateSource(1, 2, 3))); + } + [Theory] [InlineData(new int[0], new int[0])] [InlineData(new int[0], new int[] { 42 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/GroupByTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/GroupByTests.cs index a908913f80a018..0d8656995c6821 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/GroupByTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/GroupByTests.cs @@ -42,6 +42,22 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("resultSelector", () => AsyncEnumerable.GroupBy(AsyncEnumerable.Empty(), async (s, ct) => s, async (s, ct) => s, (Func, CancellationToken, ValueTask>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty>(), AsyncEnumerable.Empty().GroupBy(i => i)); + Assert.Same(AsyncEnumerable.Empty>(), AsyncEnumerable.Empty().GroupBy(async (i, ct) => i)); + + Assert.Same(AsyncEnumerable.Empty>(), AsyncEnumerable.Empty().GroupBy(i => i, i => i.Length)); + Assert.Same(AsyncEnumerable.Empty>(), AsyncEnumerable.Empty().GroupBy(async (i, ct) => i, async (i, ct) => i.Length)); + + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().GroupBy(i => i, (i, elements) => i.Length)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().GroupBy(async (i, ct) => i, async (i, elements, ct) => i.Length)); + + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().GroupBy(i => i, i => i.Length, (i, elements) => i.Length)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().GroupBy(async (i, ct) => i, async (i, ct) => i.Length, async (i, elements, ct) => i.Length)); + } + [Fact] public async Task VariousValues_MatchesEnumerable_String() { diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/GroupJoinTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/GroupJoinTests.cs index c6cb865ca61e07..305faf3c77681f 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/GroupJoinTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/GroupJoinTests.cs @@ -26,6 +26,13 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("resultSelector", () => AsyncEnumerable.GroupJoin(AsyncEnumerable.Empty(), AsyncEnumerable.Empty(), async (outer, ct) => outer, async (inner, ct) => inner, (Func, CancellationToken, ValueTask>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().GroupJoin(CreateSource(1, 2, 3), s => s, i => i.ToString(), (s, e) => s)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().GroupJoin(CreateSource(1, 2, 3), async (s, ct) => s, async (i, ct) => i.ToString(), async (s, e, ct) => s)); + } + [Fact] public async Task VariousValues_MatchesEnumerable_String() { diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/IndexTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/IndexTests.cs index 3c3c081bd5a16e..8aabdb7fc8ef6e 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/IndexTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/IndexTests.cs @@ -16,6 +16,12 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("source", () => AsyncEnumerable.Index(null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty<(int, string)>(), AsyncEnumerable.Empty().Index()); + } + #if NET [Theory] [InlineData(new int[0])] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/IntersectByTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/IntersectByTests.cs index 0d60650ae80b79..68ae73c5cbe484 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/IntersectByTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/IntersectByTests.cs @@ -22,6 +22,19 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("keySelector", () => AsyncEnumerable.IntersectBy(AsyncEnumerable.Empty(), AsyncEnumerable.Empty(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + IAsyncEnumerable empty = AsyncEnumerable.Empty(); + IAsyncEnumerable nonEmpty = CreateSource(1, 2, 3); + + Assert.Same(empty, empty.IntersectBy(nonEmpty, x => x)); + Assert.Same(empty, empty.IntersectBy(nonEmpty, async (x, ct) => x)); + + Assert.Same(empty, nonEmpty.IntersectBy(empty, x => x)); + Assert.Same(empty, nonEmpty.IntersectBy(empty, async (x, ct) => x)); + } + #if NET [Theory] [InlineData(new int[0], new int[0])] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/IntersectTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/IntersectTests.cs index 423ec8a5a7e741..a3ed3eb479d53c 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/IntersectTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/IntersectTests.cs @@ -17,6 +17,16 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("second", () => AsyncEnumerable.Intersect(AsyncEnumerable.Empty(), null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + IAsyncEnumerable empty = AsyncEnumerable.Empty(); + IAsyncEnumerable nonEmpty = CreateSource(1, 2, 3); + + Assert.Same(empty, empty.Intersect(nonEmpty)); + Assert.Same(empty, nonEmpty.Intersect(empty)); + } + [Theory] [InlineData(new int[0], new int[0])] [InlineData(new int[0], new int[] { 42 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/JoinTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/JoinTests.cs index 5863f7fb2c4ef9..258ea44c57ba49 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/JoinTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/JoinTests.cs @@ -26,6 +26,22 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("resultSelector", () => AsyncEnumerable.Join(AsyncEnumerable.Empty(), AsyncEnumerable.Empty(), async (outer, ct) => outer, async (inner, ct) => inner, (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + IAsyncEnumerable empty = AsyncEnumerable.Empty(); + IAsyncEnumerable nonEmpty = CreateSource("1", "2", "3"); + + Assert.Same(AsyncEnumerable.Empty(), empty.Join(empty, s => s, s => s, (s1, s2) => s1)); + Assert.Same(AsyncEnumerable.Empty(), empty.Join(empty, async (s, ct) => s, async (s, ct) => s, async (s1, s2, ct) => s1)); + + Assert.Same(AsyncEnumerable.Empty(), nonEmpty.Join(empty, s => s, s => s, (s1, s2) => s1)); + Assert.Same(AsyncEnumerable.Empty(), nonEmpty.Join(empty, async (s, ct) => s, async (s, ct) => s, async (s1, s2, ct) => s1)); + + Assert.Same(AsyncEnumerable.Empty(), empty.Join(nonEmpty, s => s, s => s, (s1, s2) => s1)); + Assert.Same(AsyncEnumerable.Empty(), empty.Join(nonEmpty, async (s, ct) => s, async (s, ct) => s, async (s1, s2, ct) => s1)); + } + [Fact] public async Task VariousValues_MatchesEnumerable_String() { diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/LeftJoinTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/LeftJoinTests.cs index 334b8ba202b41a..759ae3ab1ed1d8 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/LeftJoinTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/LeftJoinTests.cs @@ -26,6 +26,22 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("resultSelector", () => AsyncEnumerable.LeftJoin(AsyncEnumerable.Empty(), AsyncEnumerable.Empty(), async (outer, ct) => outer, async (inner, ct) => inner, (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + IAsyncEnumerable empty = AsyncEnumerable.Empty(); + IAsyncEnumerable nonEmpty = CreateSource("1", "2", "3"); + + Assert.Same(AsyncEnumerable.Empty(), empty.LeftJoin(empty, s => s, s => s, (s1, s2) => s1)); + Assert.Same(AsyncEnumerable.Empty(), empty.LeftJoin(empty, async (s, ct) => s, async (s, ct) => s, async (s1, s2, ct) => s1)); + + Assert.NotSame(AsyncEnumerable.Empty(), nonEmpty.LeftJoin(empty, s => s, s => s, (s1, s2) => s1)); + Assert.NotSame(AsyncEnumerable.Empty(), nonEmpty.LeftJoin(empty, async (s, ct) => s, async (s, ct) => s, async (s1, s2, ct) => s1)); + + Assert.Same(AsyncEnumerable.Empty(), empty.LeftJoin(nonEmpty, s => s, s => s, (s1, s2) => s1)); + Assert.Same(AsyncEnumerable.Empty(), empty.LeftJoin(nonEmpty, async (s, ct) => s, async (s, ct) => s, async (s1, s2, ct) => s1)); + } + #if NET [Fact] public async Task VariousValues_MatchesEnumerable_String() diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/OfTypeTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/OfTypeTests.cs index 6bf667b12699ca..87371dfcbc33ec 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/OfTypeTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/OfTypeTests.cs @@ -18,10 +18,10 @@ public void InvalidInputs_Throws() } [Fact] - public async Task Empty_ProducesEmpty() + public void Empty_ProducesEmpty() // validating an optimization / implementation detail { - await AssertEqual(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OfType()); - await AssertEqual(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OfType()); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OfType()); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OfType()); } [Fact] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/OrderByTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/OrderByTests.cs index 558bf4c1dfaf67..af6b1d4d078fdb 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/OrderByTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/OrderByTests.cs @@ -37,6 +37,22 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("keySelector", () => AsyncEnumerable.ThenByDescending(AsyncEnumerable.Empty().Order(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OrderBy(i => i)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OrderBy(async (i, ct) => i)); + + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OrderByDescending(i => i)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OrderByDescending(async (i, ct) => i)); + + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OrderBy(i => i).ThenBy(i => i)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OrderBy(async (i, ct) => i).ThenBy(async (i, ct) => i)); + + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OrderByDescending(i => i).ThenByDescending(i => i)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().OrderByDescending(async (i, ct) => i).ThenByDescending(async (i, ct) => i)); + } + [Fact] public async Task VariousValues_MatchesEnumerable_Int32() { diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/RangeTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/RangeTests.cs index 75413ef13fc7c4..7fcff0f06ae817 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/RangeTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/RangeTests.cs @@ -16,6 +16,12 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("count", () => AsyncEnumerable.Range(int.MaxValue - 1, 3)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Range(42, 0)); + } + [Fact] public async Task VariousValues_MatchesEnumerable() { diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/RepeatTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/RepeatTests.cs index f9bea5532d5b7f..d27a0526a478c7 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/RepeatTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/RepeatTests.cs @@ -14,6 +14,12 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("count", () => AsyncEnumerable.Repeat("a", -1)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Repeat("42", 0)); + } + [Fact] public async Task VariousValues_MatchesEnumerable() { diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/ReverseTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/ReverseTests.cs index 2c4678e3d3a95c..1b4e7c69906709 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/ReverseTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/ReverseTests.cs @@ -16,6 +16,12 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("source", () => AsyncEnumerable.Reverse(null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Reverse()); + } + [Theory] [InlineData(new int[0])] [InlineData(new int[] { 1 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/RightJoinTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/RightJoinTests.cs index 1c5c02eb9831e1..e7f123a464e463 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/RightJoinTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/RightJoinTests.cs @@ -26,6 +26,22 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("resultSelector", () => AsyncEnumerable.RightJoin(AsyncEnumerable.Empty(), AsyncEnumerable.Empty(), async (outer, ct) => outer, async (inner, ct) => inner, (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + IAsyncEnumerable empty = AsyncEnumerable.Empty(); + IAsyncEnumerable nonEmpty = CreateSource("1", "2", "3"); + + Assert.Same(AsyncEnumerable.Empty(), empty.RightJoin(empty, s => s, s => s, (s1, s2) => s1)); + Assert.Same(AsyncEnumerable.Empty(), empty.RightJoin(empty, async (s, ct) => s, async (s, ct) => s, async (s1, s2, ct) => s1)); + + Assert.Same(AsyncEnumerable.Empty(), nonEmpty.RightJoin(empty, s => s, s => s, (s1, s2) => s1)); + Assert.Same(AsyncEnumerable.Empty(), nonEmpty.RightJoin(empty, async (s, ct) => s, async (s, ct) => s, async (s1, s2, ct) => s1)); + + Assert.NotSame(AsyncEnumerable.Empty(), empty.RightJoin(nonEmpty, s => s, s => s, (s1, s2) => s1)); + Assert.NotSame(AsyncEnumerable.Empty(), empty.RightJoin(nonEmpty, async (s, ct) => s, async (s, ct) => s, async (s1, s2, ct) => s1)); + } + #if NET [Fact] public async Task VariousValues_MatchesEnumerable_String() diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/SelectManyTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/SelectManyTests.cs index ffdb4c8593026a..a0e00053fd15c8 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/SelectManyTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/SelectManyTests.cs @@ -57,6 +57,27 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("resultSelector", () => AsyncEnumerable.SelectMany(AsyncEnumerable.Empty(), (i, index) => AsyncEnumerable.Empty(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany(s => s.ToCharArray())); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany(async (s, ct) => (IEnumerable)s.ToCharArray())); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany(s => s.ToAsyncEnumerable())); + + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany((s, i) => s.ToCharArray())); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany(async (s, i, ct) => (IEnumerable)s.ToCharArray())); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany((s, i) => s.ToAsyncEnumerable())); + + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany(s => s.ToCharArray(), (s, c) => s)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany(async (s, ct) => (IEnumerable)s.ToCharArray(), async (s, c, ct) => s)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany(s => s.ToAsyncEnumerable(), (s, c) => s)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany(s => s.ToAsyncEnumerable(), async (s, c, ct) => s)); + + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany((s, i) => s.ToCharArray(), (s, c) => s)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany(async (s, i, ct) => (IEnumerable)s.ToCharArray(), async (s, c, ct) => s)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SelectMany((s, i) => s.ToAsyncEnumerable(), async (s, c, ct) => s)); + } + [Fact] public async Task VariousValues_MatchesEnumerable() { diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/SelectTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/SelectTests.cs index 9e82c46e73cf98..5adf11d5a7b366 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/SelectTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/SelectTests.cs @@ -24,6 +24,15 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("selector", () => AsyncEnumerable.Select(AsyncEnumerable.Empty(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Select(s => s)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Select((s, index) => s)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Select(async (string s, CancellationToken ct) => s)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Select(async (string s, int index, CancellationToken ct) => s)); + } + [Theory] [InlineData(new int[0])] [InlineData(new int[] { 42 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/ShuffleTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/ShuffleTests.cs index 39130f280099df..6798ca81d80304 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/ShuffleTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/ShuffleTests.cs @@ -16,6 +16,12 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("source", () => AsyncEnumerable.Shuffle(null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Shuffle()); + } + [Theory] [InlineData(new int[0])] [InlineData(new int[] { 1 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/SkipLastTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/SkipLastTests.cs index 1da94e367d3d0e..5fb5a76f848a8e 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/SkipLastTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/SkipLastTests.cs @@ -16,6 +16,16 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("source", () => AsyncEnumerable.SkipLast((IAsyncEnumerable)null, 42)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SkipLast(42)); + + IAsyncEnumerable source = CreateSource(2, 4, 8, 16); + Assert.Same(source, source.SkipLast(0)); + Assert.Same(source, source.SkipLast(-1)); + } + #if NET [Theory] [InlineData(new int[0])] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/SkipTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/SkipTests.cs index e3f1b3d121b0d8..84c52f7ba10211 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/SkipTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/SkipTests.cs @@ -16,6 +16,16 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("source", () => AsyncEnumerable.Skip((IAsyncEnumerable)null, 42)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Skip(42)); + + IAsyncEnumerable source = CreateSource(2, 4, 8, 16); + Assert.Same(source, source.Skip(0)); + Assert.Same(source, source.Skip(-1)); + } + [Theory] [InlineData(new int[0])] [InlineData(new int[] { 42 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/SkipWhileTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/SkipWhileTests.cs index 1da93416d22a3e..60f7c7bca4c3b4 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/SkipWhileTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/SkipWhileTests.cs @@ -24,6 +24,15 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("predicate", () => AsyncEnumerable.SkipWhile(AsyncEnumerable.Empty(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SkipWhile(i => true)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SkipWhile((i, index) => true)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SkipWhile(async (i, ct) => true)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().SkipWhile(async (i, index, ct) => true)); + } + [Theory] [InlineData(new int[0])] [InlineData(new int[] { 42 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/TakeLastTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/TakeLastTests.cs index 0b723c7268befc..ef8b3192d54a3a 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/TakeLastTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/TakeLastTests.cs @@ -16,6 +16,14 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("source", () => AsyncEnumerable.TakeLast((IAsyncEnumerable)null, 42)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().TakeLast(42)); + Assert.Same(AsyncEnumerable.Empty(), CreateSource(1, 2, 3).TakeLast(0)); + Assert.Same(AsyncEnumerable.Empty(), CreateSource(1, 2, 3).TakeLast(-1)); + } + #if NET [Theory] [InlineData(new int[0])] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/TakeTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/TakeTests.cs index 72582de10a2205..9341b1671a0899 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/TakeTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/TakeTests.cs @@ -18,8 +18,10 @@ public void InvalidInputs_Throws() } [Fact] - public void TakeNothing_ReturnsEmpty() + public void Empty_ProducesEmpty() // validating an optimization / implementation detail { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().TakeLast(42)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Take(new int[] { 1, 2, 3 }.ToAsyncEnumerable(), 0)); Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Take(new int[] { 1, 2, 3 }.ToAsyncEnumerable(), -1)); Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Take(new int[] { 1, 2, 3 }.ToAsyncEnumerable(), new Range(new(0), new(0)))); diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/TakeWhileTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/TakeWhileTests.cs index fbcb2a97f470e6..ac7fbdcd5e02a0 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/TakeWhileTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/TakeWhileTests.cs @@ -24,6 +24,15 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("predicate", () => AsyncEnumerable.TakeWhile(AsyncEnumerable.Empty(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().TakeWhile(i => true)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().TakeWhile((i, index) => true)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().TakeWhile(async (i, ct) => true)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().TakeWhile(async (i, index, ct) => true)); + } + [Theory] [InlineData(new int[0])] [InlineData(new int[] { 42 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/ToAsyncEnumerableTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/ToAsyncEnumerableTests.cs index da851de0037d12..87808468229e8b 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/ToAsyncEnumerableTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/ToAsyncEnumerableTests.cs @@ -16,6 +16,18 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("source", () => AsyncEnumerable.ToAsyncEnumerable(null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), Enumerable.Empty().ToAsyncEnumerable()); + Assert.Same(AsyncEnumerable.Empty(), Array.Empty().ToAsyncEnumerable()); + Assert.Same(AsyncEnumerable.Empty(), new int[0].ToAsyncEnumerable()); + + Assert.NotSame(AsyncEnumerable.Empty(), new List().ToAsyncEnumerable()); + Assert.NotSame(AsyncEnumerable.Empty(), new HashSet().ToAsyncEnumerable()); + Assert.NotSame(AsyncEnumerable.Empty(), new ReadOnlyCollection([]).ToAsyncEnumerable()); + } + [Theory] [InlineData(new int[0])] [InlineData(new int[] { 1 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/UnionByTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/UnionByTests.cs index e363dda433c1e6..86ed66b086beb7 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/UnionByTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/UnionByTests.cs @@ -23,6 +23,23 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("keySelector", () => AsyncEnumerable.UnionBy(AsyncEnumerable.Empty(), AsyncEnumerable.Empty(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + IAsyncEnumerable empty = AsyncEnumerable.Empty(); + IAsyncEnumerable nonEmpty = CreateSource(1, 2, 3); + + Assert.Same(empty, empty.UnionBy(empty, i => i)); + Assert.NotSame(empty, empty.UnionBy(nonEmpty, i => i)); + Assert.NotSame(empty, nonEmpty.UnionBy(empty, i => i)); + Assert.NotSame(empty, nonEmpty.UnionBy(nonEmpty, i => i)); + + Assert.Same(empty, empty.UnionBy(empty, async (i, ct) => i)); + Assert.NotSame(empty, empty.UnionBy(nonEmpty, async (i, ct) => i)); + Assert.NotSame(empty, nonEmpty.UnionBy(empty, async (i, ct) => i)); + Assert.NotSame(empty, nonEmpty.UnionBy(nonEmpty, async (i, ct) => i)); + } + [Theory] [InlineData(new int[0], new int[0])] [InlineData(new int[0], new int[] { 42 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/UnionTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/UnionTests.cs index 90219b88e62a4d..a27079ad87fce7 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/UnionTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/UnionTests.cs @@ -17,6 +17,18 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("second", () => AsyncEnumerable.Union(AsyncEnumerable.Empty(), null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + IAsyncEnumerable empty = AsyncEnumerable.Empty(); + IAsyncEnumerable nonEmpty = CreateSource(1, 2, 3); + + Assert.Same(empty, empty.Union(empty)); + Assert.NotSame(empty, empty.Union(nonEmpty)); + Assert.NotSame(empty, nonEmpty.Union(empty)); + Assert.NotSame(empty, nonEmpty.Union(nonEmpty)); + } + [Theory] [InlineData(new int[0], new int[0])] [InlineData(new int[0], new int[] { 42 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/WhereTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/WhereTests.cs index b64a149b253d64..31db1eafe8e5d6 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/WhereTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/WhereTests.cs @@ -24,6 +24,15 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("predicate", () => AsyncEnumerable.Where(AsyncEnumerable.Empty(), (Func>)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Where(i => true)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Where((i, index) => true)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Where(async (i, ct) => true)); + Assert.Same(AsyncEnumerable.Empty(), AsyncEnumerable.Empty().Where(async (i, index, ct) => true)); + } + [Theory] [InlineData(new int[0])] [InlineData(new int[] { 42 })] diff --git a/src/libraries/System.Linq.AsyncEnumerable/tests/ZipTests.cs b/src/libraries/System.Linq.AsyncEnumerable/tests/ZipTests.cs index b09730ad3d7e2d..3e4501b03fd942 100644 --- a/src/libraries/System.Linq.AsyncEnumerable/tests/ZipTests.cs +++ b/src/libraries/System.Linq.AsyncEnumerable/tests/ZipTests.cs @@ -29,6 +29,37 @@ public void InvalidInputs_Throws() AssertExtensions.Throws("third", () => AsyncEnumerable.Zip(AsyncEnumerable.Empty(), AsyncEnumerable.Empty(), (IAsyncEnumerable)null)); } + [Fact] + public void Empty_ProducesEmpty() // validating an optimization / implementation detail + { + IAsyncEnumerable empty = AsyncEnumerable.Empty(); + IAsyncEnumerable nonEmpty = CreateSource(1, 2, 3); + + Assert.Same(AsyncEnumerable.Empty<(int, int)>(), empty.Zip(empty)); + Assert.Same(AsyncEnumerable.Empty<(int, int)>(), empty.Zip(nonEmpty)); + Assert.Same(AsyncEnumerable.Empty<(int, int)>(), nonEmpty.Zip(empty)); + Assert.NotSame(AsyncEnumerable.Empty<(int, int)>(), nonEmpty.Zip(nonEmpty)); + + Assert.Same(AsyncEnumerable.Empty(), empty.Zip(empty, (i1, i2) => i1 + i2)); + Assert.Same(AsyncEnumerable.Empty(), nonEmpty.Zip(empty, (i1, i2) => i1 + i2)); + Assert.Same(AsyncEnumerable.Empty(), empty.Zip(nonEmpty, (i1, i2) => i1 + i2)); + Assert.NotSame(AsyncEnumerable.Empty(), nonEmpty.Zip(nonEmpty, (i1, i2) => i1 + i2)); + + Assert.Same(AsyncEnumerable.Empty(), empty.Zip(empty, async (i1, i2, ct) => i1 + i2)); + Assert.Same(AsyncEnumerable.Empty(), nonEmpty.Zip(empty, async (i1, i2, ct) => i1 + i2)); + Assert.Same(AsyncEnumerable.Empty(), empty.Zip(nonEmpty, async (i1, i2, ct) => i1 + i2)); + Assert.NotSame(AsyncEnumerable.Empty(), nonEmpty.Zip(nonEmpty, async (i1, i2, ct) => i1 + i2)); + + Assert.Same(AsyncEnumerable.Empty<(int, int, int)>(), empty.Zip(empty, empty)); + Assert.Same(AsyncEnumerable.Empty<(int, int, int)>(), nonEmpty.Zip(empty, empty)); + Assert.Same(AsyncEnumerable.Empty<(int, int, int)>(), empty.Zip(nonEmpty, empty)); + Assert.Same(AsyncEnumerable.Empty<(int, int, int)>(), empty.Zip(empty, nonEmpty)); + Assert.Same(AsyncEnumerable.Empty<(int, int, int)>(), nonEmpty.Zip(nonEmpty, empty)); + Assert.Same(AsyncEnumerable.Empty<(int, int, int)>(), nonEmpty.Zip(empty, nonEmpty)); + Assert.Same(AsyncEnumerable.Empty<(int, int, int)>(), empty.Zip(nonEmpty, nonEmpty)); + Assert.NotSame(AsyncEnumerable.Empty<(int, int, int)>(), nonEmpty.Zip(nonEmpty, nonEmpty)); + } + [Theory] [InlineData(new int[0], new int[0])] [InlineData(new int[0], new int[] { 42 })]