using Cysharp.Threading.Tasks.Internal; using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace Cysharp.Threading.Tasks.Linq { public static partial class UniTaskAsyncEnumerable { // Ix-Async returns IGrouping but it is competely waste, use standard IGrouping. public static IUniTaskAsyncEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); return new GroupBy<TSource, TKey, TSource>(source, keySelector, x => x, EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupBy<TSource, TKey, TSource>(source, keySelector, x => x, comparer); } public static IUniTaskAsyncEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); return new GroupBy<TSource, TKey, TElement>(source, keySelector, elementSelector, EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupBy<TSource, TKey, TElement>(source, keySelector, elementSelector, comparer); } public static IUniTaskAsyncEnumerable<TResult> GroupBy<TSource, TKey, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupBy<TSource, TKey, TSource, TResult>(source, keySelector, x => x, resultSelector, EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<TResult> GroupBy<TSource, TKey, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> resultSelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupBy<TSource, TKey, TSource, TResult>(source, keySelector, x => x, resultSelector, comparer); } public static IUniTaskAsyncEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupBy<TSource, TKey, TElement, TResult>(source, keySelector, elementSelector, resultSelector, EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupBy<TSource, TKey, TElement, TResult>(source, keySelector, elementSelector, resultSelector, comparer); } // await public static IUniTaskAsyncEnumerable<IGrouping<TKey, TSource>> GroupByAwait<TSource, TKey>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); return new GroupByAwait<TSource, TKey, TSource>(source, keySelector, x => UniTask.FromResult(x), EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<IGrouping<TKey, TSource>> GroupByAwait<TSource, TKey>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwait<TSource, TKey, TSource>(source, keySelector, x => UniTask.FromResult(x), comparer); } public static IUniTaskAsyncEnumerable<IGrouping<TKey, TElement>> GroupByAwait<TSource, TKey, TElement>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, Func<TSource, UniTask<TElement>> elementSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); return new GroupByAwait<TSource, TKey, TElement>(source, keySelector, elementSelector, EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<IGrouping<TKey, TElement>> GroupByAwait<TSource, TKey, TElement>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, Func<TSource, UniTask<TElement>> elementSelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwait<TSource, TKey, TElement>(source, keySelector, elementSelector, comparer); } public static IUniTaskAsyncEnumerable<TResult> GroupByAwait<TSource, TKey, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, Func<TKey, IEnumerable<TSource>, UniTask<TResult>> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupByAwait<TSource, TKey, TSource, TResult>(source, keySelector, x => UniTask.FromResult(x), resultSelector, EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<TResult> GroupByAwait<TSource, TKey, TElement, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, Func<TSource, UniTask<TElement>> elementSelector, Func<TKey, IEnumerable<TElement>, UniTask<TResult>> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupByAwait<TSource, TKey, TElement, TResult>(source, keySelector, elementSelector, resultSelector, EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<TResult> GroupByAwait<TSource, TKey, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, Func<TKey, IEnumerable<TSource>, UniTask<TResult>> resultSelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwait<TSource, TKey, TSource, TResult>(source, keySelector, x => UniTask.FromResult(x), resultSelector, comparer); } public static IUniTaskAsyncEnumerable<TResult> GroupByAwait<TSource, TKey, TElement, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, Func<TSource, UniTask<TElement>> elementSelector, Func<TKey, IEnumerable<TElement>, UniTask<TResult>> resultSelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwait<TSource, TKey, TElement, TResult>(source, keySelector, elementSelector, resultSelector, comparer); } // with ct public static IUniTaskAsyncEnumerable<IGrouping<TKey, TSource>> GroupByAwaitWithCancellation<TSource, TKey>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); return new GroupByAwaitWithCancellation<TSource, TKey, TSource>(source, keySelector, (x, _) => UniTask.FromResult(x), EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<IGrouping<TKey, TSource>> GroupByAwaitWithCancellation<TSource, TKey>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwaitWithCancellation<TSource, TKey, TSource>(source, keySelector, (x, _) => UniTask.FromResult(x), comparer); } public static IUniTaskAsyncEnumerable<IGrouping<TKey, TElement>> GroupByAwaitWithCancellation<TSource, TKey, TElement>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, Func<TSource, CancellationToken, UniTask<TElement>> elementSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); return new GroupByAwaitWithCancellation<TSource, TKey, TElement>(source, keySelector, elementSelector, EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<IGrouping<TKey, TElement>> GroupByAwaitWithCancellation<TSource, TKey, TElement>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, Func<TSource, CancellationToken, UniTask<TElement>> elementSelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwaitWithCancellation<TSource, TKey, TElement>(source, keySelector, elementSelector, comparer); } public static IUniTaskAsyncEnumerable<TResult> GroupByAwaitWithCancellation<TSource, TKey, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, Func<TKey, IEnumerable<TSource>, CancellationToken, UniTask<TResult>> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupByAwaitWithCancellation<TSource, TKey, TSource, TResult>(source, keySelector, (x, _) => UniTask.FromResult(x), resultSelector, EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<TResult> GroupByAwaitWithCancellation<TSource, TKey, TElement, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, Func<TSource, CancellationToken, UniTask<TElement>> elementSelector, Func<TKey, IEnumerable<TElement>, CancellationToken, UniTask<TResult>> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupByAwaitWithCancellation<TSource, TKey, TElement, TResult>(source, keySelector, elementSelector, resultSelector, EqualityComparer<TKey>.Default); } public static IUniTaskAsyncEnumerable<TResult> GroupByAwaitWithCancellation<TSource, TKey, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, Func<TKey, IEnumerable<TSource>, CancellationToken, UniTask<TResult>> resultSelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwaitWithCancellation<TSource, TKey, TSource, TResult>(source, keySelector, (x, _) => UniTask.FromResult(x), resultSelector, comparer); } public static IUniTaskAsyncEnumerable<TResult> GroupByAwaitWithCancellation<TSource, TKey, TElement, TResult>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, Func<TSource, CancellationToken, UniTask<TElement>> elementSelector, Func<TKey, IEnumerable<TElement>, CancellationToken, UniTask<TResult>> resultSelector, IEqualityComparer<TKey> comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwaitWithCancellation<TSource, TKey, TElement, TResult>(source, keySelector, elementSelector, resultSelector, comparer); } } internal sealed class GroupBy<TSource, TKey, TElement> : IUniTaskAsyncEnumerable<IGrouping<TKey, TElement>> { readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, TKey> keySelector; readonly Func<TSource, TElement> elementSelector; readonly IEqualityComparer<TKey> comparer; public GroupBy(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator<IGrouping<TKey, TElement>> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupBy(source, keySelector, elementSelector, comparer, cancellationToken); } sealed class _GroupBy : MoveNextSource, IUniTaskAsyncEnumerator<IGrouping<TKey, TElement>> { readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, TKey> keySelector; readonly Func<TSource, TElement> elementSelector; readonly IEqualityComparer<TKey> comparer; CancellationToken cancellationToken; IEnumerator<IGrouping<TKey, TElement>> groupEnumerator; public _GroupBy(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; this.cancellationToken = cancellationToken; TaskTracker.TrackActiveTask(this, 3); } public IGrouping<TKey, TElement> Current { get; private set; } public UniTask<bool> MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask<bool>(this, completionSource.Version); } async UniTaskVoid CreateLookup() { try { var lookup = await source.ToLookupAsync(keySelector, elementSelector, comparer, cancellationToken); groupEnumerator = lookup.GetEnumerator(); } catch (Exception ex) { completionSource.TrySetException(ex); return; } SourceMoveNext(); } void SourceMoveNext() { try { if (groupEnumerator.MoveNext()) { Current = groupEnumerator.Current as IGrouping<TKey, TElement>; completionSource.TrySetResult(true); } else { completionSource.TrySetResult(false); } } catch (Exception ex) { completionSource.TrySetException(ex); return; } } public UniTask DisposeAsync() { TaskTracker.RemoveTracking(this); if (groupEnumerator != null) { groupEnumerator.Dispose(); } return default; } } } internal sealed class GroupBy<TSource, TKey, TElement, TResult> : IUniTaskAsyncEnumerable<TResult> { readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, TKey> keySelector; readonly Func<TSource, TElement> elementSelector; readonly Func<TKey, IEnumerable<TElement>, TResult> resultSelector; readonly IEqualityComparer<TKey> comparer; public GroupBy(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector, IEqualityComparer<TKey> comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.resultSelector = resultSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupBy(source, keySelector, elementSelector, resultSelector, comparer, cancellationToken); } sealed class _GroupBy : MoveNextSource, IUniTaskAsyncEnumerator<TResult> { readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, TKey> keySelector; readonly Func<TSource, TElement> elementSelector; readonly Func<TKey, IEnumerable<TElement>, TResult> resultSelector; readonly IEqualityComparer<TKey> comparer; CancellationToken cancellationToken; IEnumerator<IGrouping<TKey, TElement>> groupEnumerator; public _GroupBy(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.resultSelector = resultSelector; this.comparer = comparer; this.cancellationToken = cancellationToken; TaskTracker.TrackActiveTask(this, 3); } public TResult Current { get; private set; } public UniTask<bool> MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask<bool>(this, completionSource.Version); } async UniTaskVoid CreateLookup() { try { var lookup = await source.ToLookupAsync(keySelector, elementSelector, comparer, cancellationToken); groupEnumerator = lookup.GetEnumerator(); } catch (Exception ex) { completionSource.TrySetException(ex); return; } SourceMoveNext(); } void SourceMoveNext() { try { if (groupEnumerator.MoveNext()) { var current = groupEnumerator.Current; Current = resultSelector(current.Key, current); completionSource.TrySetResult(true); } else { completionSource.TrySetResult(false); } } catch (Exception ex) { completionSource.TrySetException(ex); return; } } public UniTask DisposeAsync() { TaskTracker.RemoveTracking(this); if (groupEnumerator != null) { groupEnumerator.Dispose(); } return default; } } } internal sealed class GroupByAwait<TSource, TKey, TElement> : IUniTaskAsyncEnumerable<IGrouping<TKey, TElement>> { readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, UniTask<TKey>> keySelector; readonly Func<TSource, UniTask<TElement>> elementSelector; readonly IEqualityComparer<TKey> comparer; public GroupByAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, Func<TSource, UniTask<TElement>> elementSelector, IEqualityComparer<TKey> comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator<IGrouping<TKey, TElement>> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupByAwait(source, keySelector, elementSelector, comparer, cancellationToken); } sealed class _GroupByAwait : MoveNextSource, IUniTaskAsyncEnumerator<IGrouping<TKey, TElement>> { readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, UniTask<TKey>> keySelector; readonly Func<TSource, UniTask<TElement>> elementSelector; readonly IEqualityComparer<TKey> comparer; CancellationToken cancellationToken; IEnumerator<IGrouping<TKey, TElement>> groupEnumerator; public _GroupByAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, Func<TSource, UniTask<TElement>> elementSelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; this.cancellationToken = cancellationToken; TaskTracker.TrackActiveTask(this, 3); } public IGrouping<TKey, TElement> Current { get; private set; } public UniTask<bool> MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask<bool>(this, completionSource.Version); } async UniTaskVoid CreateLookup() { try { var lookup = await source.ToLookupAwaitAsync(keySelector, elementSelector, comparer, cancellationToken); groupEnumerator = lookup.GetEnumerator(); } catch (Exception ex) { completionSource.TrySetException(ex); return; } SourceMoveNext(); } void SourceMoveNext() { try { if (groupEnumerator.MoveNext()) { Current = groupEnumerator.Current as IGrouping<TKey, TElement>; completionSource.TrySetResult(true); } else { completionSource.TrySetResult(false); } } catch (Exception ex) { completionSource.TrySetException(ex); return; } } public UniTask DisposeAsync() { TaskTracker.RemoveTracking(this); if (groupEnumerator != null) { groupEnumerator.Dispose(); } return default; } } } internal sealed class GroupByAwait<TSource, TKey, TElement, TResult> : IUniTaskAsyncEnumerable<TResult> { readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, UniTask<TKey>> keySelector; readonly Func<TSource, UniTask<TElement>> elementSelector; readonly Func<TKey, IEnumerable<TElement>, UniTask<TResult>> resultSelector; readonly IEqualityComparer<TKey> comparer; public GroupByAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, Func<TSource, UniTask<TElement>> elementSelector, Func<TKey, IEnumerable<TElement>, UniTask<TResult>> resultSelector, IEqualityComparer<TKey> comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.resultSelector = resultSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupByAwait(source, keySelector, elementSelector, resultSelector, comparer, cancellationToken); } sealed class _GroupByAwait : MoveNextSource, IUniTaskAsyncEnumerator<TResult> { readonly static Action<object> ResultSelectCoreDelegate = ResultSelectCore; readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, UniTask<TKey>> keySelector; readonly Func<TSource, UniTask<TElement>> elementSelector; readonly Func<TKey, IEnumerable<TElement>, UniTask<TResult>> resultSelector; readonly IEqualityComparer<TKey> comparer; CancellationToken cancellationToken; IEnumerator<IGrouping<TKey, TElement>> groupEnumerator; UniTask<TResult>.Awaiter awaiter; public _GroupByAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, Func<TSource, UniTask<TElement>> elementSelector, Func<TKey, IEnumerable<TElement>, UniTask<TResult>> resultSelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.resultSelector = resultSelector; this.comparer = comparer; this.cancellationToken = cancellationToken; TaskTracker.TrackActiveTask(this, 3); } public TResult Current { get; private set; } public UniTask<bool> MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask<bool>(this, completionSource.Version); } async UniTaskVoid CreateLookup() { try { var lookup = await source.ToLookupAwaitAsync(keySelector, elementSelector, comparer, cancellationToken); groupEnumerator = lookup.GetEnumerator(); } catch (Exception ex) { completionSource.TrySetException(ex); return; } SourceMoveNext(); } void SourceMoveNext() { try { if (groupEnumerator.MoveNext()) { var current = groupEnumerator.Current; awaiter = resultSelector(current.Key, current).GetAwaiter(); if (awaiter.IsCompleted) { ResultSelectCore(this); } else { awaiter.SourceOnCompleted(ResultSelectCoreDelegate, this); } return; } else { completionSource.TrySetResult(false); } } catch (Exception ex) { completionSource.TrySetException(ex); return; } } static void ResultSelectCore(object state) { var self = (_GroupByAwait)state; if (self.TryGetResult(self.awaiter, out var result)) { self.Current = result; self.completionSource.TrySetResult(true); } } public UniTask DisposeAsync() { TaskTracker.RemoveTracking(this); if (groupEnumerator != null) { groupEnumerator.Dispose(); } return default; } } } internal sealed class GroupByAwaitWithCancellation<TSource, TKey, TElement> : IUniTaskAsyncEnumerable<IGrouping<TKey, TElement>> { readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, CancellationToken, UniTask<TKey>> keySelector; readonly Func<TSource, CancellationToken, UniTask<TElement>> elementSelector; readonly IEqualityComparer<TKey> comparer; public GroupByAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, Func<TSource, CancellationToken, UniTask<TElement>> elementSelector, IEqualityComparer<TKey> comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator<IGrouping<TKey, TElement>> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupByAwaitWithCancellation(source, keySelector, elementSelector, comparer, cancellationToken); } sealed class _GroupByAwaitWithCancellation : MoveNextSource, IUniTaskAsyncEnumerator<IGrouping<TKey, TElement>> { readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, CancellationToken, UniTask<TKey>> keySelector; readonly Func<TSource, CancellationToken, UniTask<TElement>> elementSelector; readonly IEqualityComparer<TKey> comparer; CancellationToken cancellationToken; IEnumerator<IGrouping<TKey, TElement>> groupEnumerator; public _GroupByAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, Func<TSource, CancellationToken, UniTask<TElement>> elementSelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; this.cancellationToken = cancellationToken; TaskTracker.TrackActiveTask(this, 3); } public IGrouping<TKey, TElement> Current { get; private set; } public UniTask<bool> MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask<bool>(this, completionSource.Version); } async UniTaskVoid CreateLookup() { try { var lookup = await source.ToLookupAwaitWithCancellationAsync(keySelector, elementSelector, comparer, cancellationToken); groupEnumerator = lookup.GetEnumerator(); } catch (Exception ex) { completionSource.TrySetException(ex); return; } SourceMoveNext(); } void SourceMoveNext() { try { if (groupEnumerator.MoveNext()) { Current = groupEnumerator.Current as IGrouping<TKey, TElement>; completionSource.TrySetResult(true); } else { completionSource.TrySetResult(false); } } catch (Exception ex) { completionSource.TrySetException(ex); return; } } public UniTask DisposeAsync() { TaskTracker.RemoveTracking(this); if (groupEnumerator != null) { groupEnumerator.Dispose(); } return default; } } } internal sealed class GroupByAwaitWithCancellation<TSource, TKey, TElement, TResult> : IUniTaskAsyncEnumerable<TResult> { readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, CancellationToken, UniTask<TKey>> keySelector; readonly Func<TSource, CancellationToken, UniTask<TElement>> elementSelector; readonly Func<TKey, IEnumerable<TElement>, CancellationToken, UniTask<TResult>> resultSelector; readonly IEqualityComparer<TKey> comparer; public GroupByAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, Func<TSource, CancellationToken, UniTask<TElement>> elementSelector, Func<TKey, IEnumerable<TElement>, CancellationToken, UniTask<TResult>> resultSelector, IEqualityComparer<TKey> comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.resultSelector = resultSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupByAwaitWithCancellation(source, keySelector, elementSelector, resultSelector, comparer, cancellationToken); } sealed class _GroupByAwaitWithCancellation : MoveNextSource, IUniTaskAsyncEnumerator<TResult> { readonly static Action<object> ResultSelectCoreDelegate = ResultSelectCore; readonly IUniTaskAsyncEnumerable<TSource> source; readonly Func<TSource, CancellationToken, UniTask<TKey>> keySelector; readonly Func<TSource, CancellationToken, UniTask<TElement>> elementSelector; readonly Func<TKey, IEnumerable<TElement>, CancellationToken, UniTask<TResult>> resultSelector; readonly IEqualityComparer<TKey> comparer; CancellationToken cancellationToken; IEnumerator<IGrouping<TKey, TElement>> groupEnumerator; UniTask<TResult>.Awaiter awaiter; public _GroupByAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, Func<TSource, CancellationToken, UniTask<TElement>> elementSelector, Func<TKey, IEnumerable<TElement>, CancellationToken, UniTask<TResult>> resultSelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.resultSelector = resultSelector; this.comparer = comparer; this.cancellationToken = cancellationToken; TaskTracker.TrackActiveTask(this, 3); } public TResult Current { get; private set; } public UniTask<bool> MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask<bool>(this, completionSource.Version); } async UniTaskVoid CreateLookup() { try { var lookup = await source.ToLookupAwaitWithCancellationAsync(keySelector, elementSelector, comparer, cancellationToken); groupEnumerator = lookup.GetEnumerator(); } catch (Exception ex) { completionSource.TrySetException(ex); return; } SourceMoveNext(); } void SourceMoveNext() { try { if (groupEnumerator.MoveNext()) { var current = groupEnumerator.Current; awaiter = resultSelector(current.Key, current, cancellationToken).GetAwaiter(); if (awaiter.IsCompleted) { ResultSelectCore(this); } else { awaiter.SourceOnCompleted(ResultSelectCoreDelegate, this); } return; } else { completionSource.TrySetResult(false); } } catch (Exception ex) { completionSource.TrySetException(ex); return; } } static void ResultSelectCore(object state) { var self = (_GroupByAwaitWithCancellation)state; if (self.TryGetResult(self.awaiter, out var result)) { self.Current = result; self.completionSource.TrySetResult(true); } } public UniTask DisposeAsync() { TaskTracker.RemoveTracking(this); if (groupEnumerator != null) { groupEnumerator.Dispose(); } return default; } } } }