// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT License. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace System.Linq { public static partial class AsyncEnumerable { #if INCLUDE_SYSTEM_LINQ_ASYNCENUMERABLE_DUPLICATES // https://learn.microsoft.com/en-us/dotnet/api/system.linq.asyncenumerable.groupjoin?view=net-9.0-pp#system-linq-asyncenumerable-groupjoin-4(system-collections-generic-iasyncenumerable((-0))-system-collections-generic-iasyncenumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-system-collections-generic-ienumerable((-1))-3))-system-collections-generic-iequalitycomparer((-2))) // The method above covers the next two overloads because it supplies a default null value for comparer. /// /// Correlates the elements of two async-enumerable sequences based on equality of keys and groups the results. The default equality comparer is used to compare keys. /// /// The type of the elements of the first async-enumerable sequence. /// The type of the elements of the second async-enumerable sequence. /// The type of the keys returned by the key selector functions. /// The type of the result elements. /// The first async-enumerable sequence to join. /// The async-enumerable sequence to join to the first sequence. /// A function to extract the join key from each element of the first sequence. /// A function to extract the join key from each element of the second sequence. /// A function to create a result element from an element from the first sequence and a collection of matching elements from the second sequence. /// An async-enumerable sequence that contains elements of type TResult that are obtained by performing a grouped join on two sequences. /// or or or or is null. public static IAsyncEnumerable GroupJoin(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector) => GroupJoin(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer: null); /// /// Correlates the elements of two async-enumerable sequences based on equality of keys and groups the results. The specified equality comparer is used to compare keys. /// /// The type of the elements of the first async-enumerable sequence. /// The type of the elements of the second async-enumerable sequence. /// The type of the keys returned by the key selector functions. /// The type of the result elements. /// The first async-enumerable sequence to join. /// The async-enumerable sequence to join to the first async-enumerable sequence. /// A function to extract the join key from each element of the first sequence. /// A function to extract the join key from each element of the second sequence. /// A function to create a result element from an element from the first sequence and a collection of matching elements from the second sequence. /// An equality comparer to hash and compare keys. /// An async-enumerable sequence that contains elements of type TResult that are obtained by performing a grouped join on two sequences. /// or or or or is null. public static IAsyncEnumerable GroupJoin(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector, IEqualityComparer? comparer) { if (outer == null) throw Error.ArgumentNull(nameof(outer)); if (inner == null) throw Error.ArgumentNull(nameof(inner)); if (outerKeySelector == null) throw Error.ArgumentNull(nameof(outerKeySelector)); if (innerKeySelector == null) throw Error.ArgumentNull(nameof(innerKeySelector)); if (resultSelector == null) throw Error.ArgumentNull(nameof(resultSelector)); return Core(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); static async IAsyncEnumerable Core(IAsyncEnumerable outer, IAsyncEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector, IEqualityComparer? comparer, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) { await using var e = outer.GetConfiguredAsyncEnumerator(cancellationToken, false); if (await e.MoveNextAsync()) { var lookup = await Internal.Lookup.CreateForJoinAsync(inner, innerKeySelector, comparer, cancellationToken).ConfigureAwait(false); do { var item = e.Current; var outerKey = outerKeySelector(item); yield return resultSelector(item, lookup[outerKey].ToAsyncEnumerable()); } while (await e.MoveNextAsync()); } } } #endif // INCLUDE_SYSTEM_LINQ_ASYNCENUMERABLE_DUPLICATES [GenerateAsyncOverload] [Obsolete("Use GroupJoin. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the GroupJoinAwait functionality now exists as an overload of GroupJoin. You will need to modify your callback to take an additional CancellationToken argument.")] private static IAsyncEnumerable GroupJoinAwaitCore(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func> outerKeySelector, Func> innerKeySelector, Func, ValueTask> resultSelector) => GroupJoinAwaitCore(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer: null); [GenerateAsyncOverload] [Obsolete("Use GroupJoin. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the GroupJoinAwait functionality now exists as an overload of GroupJoin. You will need to modify your callback to take an additional CancellationToken argument. You will need to modify your callback to take an additional CancellationToken argument.")] private static IAsyncEnumerable GroupJoinAwaitCore(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func> outerKeySelector, Func> innerKeySelector, Func, ValueTask> resultSelector, IEqualityComparer? comparer) { if (outer == null) throw Error.ArgumentNull(nameof(outer)); if (inner == null) throw Error.ArgumentNull(nameof(inner)); if (outerKeySelector == null) throw Error.ArgumentNull(nameof(outerKeySelector)); if (innerKeySelector == null) throw Error.ArgumentNull(nameof(innerKeySelector)); if (resultSelector == null) throw Error.ArgumentNull(nameof(resultSelector)); return Core(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); static async IAsyncEnumerable Core(IAsyncEnumerable outer, IAsyncEnumerable inner, Func> outerKeySelector, Func> innerKeySelector, Func, ValueTask> resultSelector, IEqualityComparer? comparer, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) { await using var e = outer.GetConfiguredAsyncEnumerator(cancellationToken, false); if (await e.MoveNextAsync()) { var lookup = await Internal.LookupWithTask.CreateForJoinAsync(inner, innerKeySelector, comparer, cancellationToken).ConfigureAwait(false); do { var item = e.Current; var outerKey = await outerKeySelector(item).ConfigureAwait(false); yield return await resultSelector(item, lookup[outerKey].ToAsyncEnumerable()).ConfigureAwait(false); } while (await e.MoveNextAsync()); } } } #if !NO_DEEP_CANCELLATION [GenerateAsyncOverload] [Obsolete("Use GroupJoin. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the GroupJoinAwaitWithCancellation functionality now exists as an overload of GroupJoin.")] private static IAsyncEnumerable GroupJoinAwaitWithCancellationCore(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func> outerKeySelector, Func> innerKeySelector, Func, CancellationToken, ValueTask> resultSelector) => GroupJoinAwaitWithCancellationCore(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer: null); [GenerateAsyncOverload] [Obsolete("Use GroupJoin. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the GroupJoinAwaitWithCancellation functionality now exists as an overload of GroupJoin.")] private static IAsyncEnumerable GroupJoinAwaitWithCancellationCore(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func> outerKeySelector, Func> innerKeySelector, Func, CancellationToken, ValueTask> resultSelector, IEqualityComparer? comparer) { if (outer == null) throw Error.ArgumentNull(nameof(outer)); if (inner == null) throw Error.ArgumentNull(nameof(inner)); if (outerKeySelector == null) throw Error.ArgumentNull(nameof(outerKeySelector)); if (innerKeySelector == null) throw Error.ArgumentNull(nameof(innerKeySelector)); if (resultSelector == null) throw Error.ArgumentNull(nameof(resultSelector)); return Core(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); static async IAsyncEnumerable Core(IAsyncEnumerable outer, IAsyncEnumerable inner, Func> outerKeySelector, Func> innerKeySelector, Func, CancellationToken, ValueTask> resultSelector, IEqualityComparer? comparer, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) { await using var e = outer.GetConfiguredAsyncEnumerator(cancellationToken, false); if (await e.MoveNextAsync()) { var lookup = await Internal.LookupWithTask.CreateForJoinAsync(inner, innerKeySelector, comparer, cancellationToken).ConfigureAwait(false); do { var item = e.Current; var outerKey = await outerKeySelector(item, cancellationToken).ConfigureAwait(false); yield return await resultSelector(item, lookup[outerKey].ToAsyncEnumerable(), cancellationToken).ConfigureAwait(false); } while (await e.MoveNextAsync()); } } } #endif } }