// 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.join?view=net-9.0-pp#system-linq-asyncenumerable-join-4(system-collections-generic-iasyncenumerable((-0))-system-collections-generic-iasyncenumerable((-1))-system-func((-0-2))-system-func((-1-2))-system-func((-0-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 sequences based on matching keys. 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 two matching elements.
/// An async-enumerable sequence that has elements of type TResult that are obtained by performing an inner join on two sequences.
/// or or or or is null.
public static IAsyncEnumerable Join(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) =>
Join(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer: null);
///
/// Correlates the elements of two sequences based on matching keys. A 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 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 two matching elements.
/// An equality comparer to hash and compare keys.
/// An async-enumerable sequence that has elements of type TResult that are obtained by performing an inner join on two sequences.
/// or or or or is null.
public static IAsyncEnumerable Join(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func 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 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);
if (lookup.Count != 0)
{
do
{
var item = e.Current;
var outerKey = outerKeySelector(item);
var g = lookup.GetGrouping(outerKey);
if (g != null)
{
var count = g._count;
var elements = g._elements;
for (var i = 0; i != count; ++i)
{
yield return resultSelector(item, elements[i]);
}
}
}
while (await e.MoveNextAsync());
}
}
}
}
#endif // INCLUDE_SYSTEM_LINQ_ASYNCENUMERABLE_DUPLICATES
// https://learn.microsoft.com/en-us/dotnet/api/system.linq.asyncenumerable.join?view=net-9.0-pp#system-linq-asyncenumerable-join-4(system-collections-generic-iasyncenumerable((-0))-system-collections-generic-iasyncenumerable((-1))-system-func((-0-system-threading-cancellationtoken-system-threading-tasks-valuetask((-2))))-system-func((-1-system-threading-cancellationtoken-system-threading-tasks-valuetask((-2))))-system-func((-0-1-system-threading-cancellationtoken-system-threading-tasks-valuetask((-3))))-system-collections-generic-iequalitycomparer((-2)))
[GenerateAsyncOverload]
[Obsolete("Use Join. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the JoinAwait functionality now exists as an overload of Join. You will need to modify your callback to take an additional CancellationToken argument.")]
private static IAsyncEnumerable JoinAwaitCore(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func> outerKeySelector, Func> innerKeySelector, Func> resultSelector) =>
JoinAwaitCore(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer: null);
[GenerateAsyncOverload]
[Obsolete("Use Join. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the JoinAwait functionality now exists as an overload of Join. You will need to modify your callback to take an additional CancellationToken argument.")]
private static IAsyncEnumerable JoinAwaitCore(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func> outerKeySelector, Func> innerKeySelector, Func> 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> 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);
if (lookup.Count != 0)
{
do
{
var item = e.Current;
var outerKey = await outerKeySelector(item).ConfigureAwait(false);
var g = lookup.GetGrouping(outerKey);
if (g != null)
{
var count = g._count;
var elements = g._elements;
for (var i = 0; i != count; ++i)
{
yield return await resultSelector(item, elements[i]).ConfigureAwait(false);
}
}
}
while (await e.MoveNextAsync());
}
}
}
}
#if !NO_DEEP_CANCELLATION
[GenerateAsyncOverload]
[Obsolete("Use Join. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the JoinAwaitWithCancellation functionality now exists as an overload of Join.")]
private static IAsyncEnumerable JoinAwaitWithCancellationCore(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func> outerKeySelector, Func> innerKeySelector, Func> resultSelector) =>
JoinAwaitWithCancellationCore(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer: null);
[GenerateAsyncOverload]
[Obsolete("Use Join. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the JoinAwaitWithCancellation functionality now exists as an overload of Join.")]
private static IAsyncEnumerable JoinAwaitWithCancellationCore(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func> outerKeySelector, Func> innerKeySelector, Func> 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> 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);
if (lookup.Count != 0)
{
do
{
var item = e.Current;
var outerKey = await outerKeySelector(item, cancellationToken).ConfigureAwait(false);
var g = lookup.GetGrouping(outerKey);
if (g != null)
{
var count = g._count;
var elements = g._elements;
for (var i = 0; i != count; ++i)
{
yield return await resultSelector(item, elements[i], cancellationToken).ConfigureAwait(false);
}
}
}
while (await e.MoveNextAsync());
}
}
}
}
#endif
}
}