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> GroupBy(this IUniTaskAsyncEnumerable source, Func keySelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); return new GroupBy(source, keySelector, x => x, EqualityComparer.Default); } public static IUniTaskAsyncEnumerable> GroupBy(this IUniTaskAsyncEnumerable source, Func keySelector, IEqualityComparer comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupBy(source, keySelector, x => x, comparer); } public static IUniTaskAsyncEnumerable> GroupBy(this IUniTaskAsyncEnumerable source, Func keySelector, Func elementSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); return new GroupBy(source, keySelector, elementSelector, EqualityComparer.Default); } public static IUniTaskAsyncEnumerable> GroupBy(this IUniTaskAsyncEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupBy(source, keySelector, elementSelector, comparer); } public static IUniTaskAsyncEnumerable GroupBy(this IUniTaskAsyncEnumerable source, Func keySelector, Func, TResult> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupBy(source, keySelector, x => x, resultSelector, EqualityComparer.Default); } public static IUniTaskAsyncEnumerable GroupBy(this IUniTaskAsyncEnumerable source, Func keySelector, Func, TResult> resultSelector, IEqualityComparer comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupBy(source, keySelector, x => x, resultSelector, comparer); } public static IUniTaskAsyncEnumerable GroupBy(this IUniTaskAsyncEnumerable source, Func keySelector, Func elementSelector, Func, 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(source, keySelector, elementSelector, resultSelector, EqualityComparer.Default); } public static IUniTaskAsyncEnumerable GroupBy(this IUniTaskAsyncEnumerable source, Func keySelector, Func elementSelector, Func, TResult> resultSelector, IEqualityComparer 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(source, keySelector, elementSelector, resultSelector, comparer); } // await public static IUniTaskAsyncEnumerable> GroupByAwait(this IUniTaskAsyncEnumerable source, Func> keySelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); return new GroupByAwait(source, keySelector, x => UniTask.FromResult(x), EqualityComparer.Default); } public static IUniTaskAsyncEnumerable> GroupByAwait(this IUniTaskAsyncEnumerable source, Func> keySelector, IEqualityComparer comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwait(source, keySelector, x => UniTask.FromResult(x), comparer); } public static IUniTaskAsyncEnumerable> GroupByAwait(this IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); return new GroupByAwait(source, keySelector, elementSelector, EqualityComparer.Default); } public static IUniTaskAsyncEnumerable> GroupByAwait(this IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, IEqualityComparer comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwait(source, keySelector, elementSelector, comparer); } public static IUniTaskAsyncEnumerable GroupByAwait(this IUniTaskAsyncEnumerable source, Func> keySelector, Func, UniTask> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupByAwait(source, keySelector, x => UniTask.FromResult(x), resultSelector, EqualityComparer.Default); } public static IUniTaskAsyncEnumerable GroupByAwait(this IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, Func, UniTask> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupByAwait(source, keySelector, elementSelector, resultSelector, EqualityComparer.Default); } public static IUniTaskAsyncEnumerable GroupByAwait(this IUniTaskAsyncEnumerable source, Func> keySelector, Func, UniTask> resultSelector, IEqualityComparer comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwait(source, keySelector, x => UniTask.FromResult(x), resultSelector, comparer); } public static IUniTaskAsyncEnumerable GroupByAwait(this IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, Func, UniTask> resultSelector, IEqualityComparer 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(source, keySelector, elementSelector, resultSelector, comparer); } // with ct public static IUniTaskAsyncEnumerable> GroupByAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> keySelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); return new GroupByAwaitWithCancellation(source, keySelector, (x, _) => UniTask.FromResult(x), EqualityComparer.Default); } public static IUniTaskAsyncEnumerable> GroupByAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> keySelector, IEqualityComparer comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwaitWithCancellation(source, keySelector, (x, _) => UniTask.FromResult(x), comparer); } public static IUniTaskAsyncEnumerable> GroupByAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); return new GroupByAwaitWithCancellation(source, keySelector, elementSelector, EqualityComparer.Default); } public static IUniTaskAsyncEnumerable> GroupByAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, IEqualityComparer comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwaitWithCancellation(source, keySelector, elementSelector, comparer); } public static IUniTaskAsyncEnumerable GroupByAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> keySelector, Func, CancellationToken, UniTask> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupByAwaitWithCancellation(source, keySelector, (x, _) => UniTask.FromResult(x), resultSelector, EqualityComparer.Default); } public static IUniTaskAsyncEnumerable GroupByAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, Func, CancellationToken, UniTask> resultSelector) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(elementSelector, nameof(elementSelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); return new GroupByAwaitWithCancellation(source, keySelector, elementSelector, resultSelector, EqualityComparer.Default); } public static IUniTaskAsyncEnumerable GroupByAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> keySelector, Func, CancellationToken, UniTask> resultSelector, IEqualityComparer comparer) { Error.ThrowArgumentNullException(source, nameof(source)); Error.ThrowArgumentNullException(keySelector, nameof(keySelector)); Error.ThrowArgumentNullException(resultSelector, nameof(resultSelector)); Error.ThrowArgumentNullException(comparer, nameof(comparer)); return new GroupByAwaitWithCancellation(source, keySelector, (x, _) => UniTask.FromResult(x), resultSelector, comparer); } public static IUniTaskAsyncEnumerable GroupByAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, Func, CancellationToken, UniTask> resultSelector, IEqualityComparer 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(source, keySelector, elementSelector, resultSelector, comparer); } } internal sealed class GroupBy : IUniTaskAsyncEnumerable> { readonly IUniTaskAsyncEnumerable source; readonly Func keySelector; readonly Func elementSelector; readonly IEqualityComparer comparer; public GroupBy(IUniTaskAsyncEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupBy(source, keySelector, elementSelector, comparer, cancellationToken); } sealed class _GroupBy : MoveNextSource, IUniTaskAsyncEnumerator> { readonly IUniTaskAsyncEnumerable source; readonly Func keySelector; readonly Func elementSelector; readonly IEqualityComparer comparer; CancellationToken cancellationToken; IEnumerator> groupEnumerator; public _GroupBy(IUniTaskAsyncEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer, CancellationToken cancellationToken) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; this.cancellationToken = cancellationToken; TaskTracker.TrackActiveTask(this, 3); } public IGrouping Current { get; private set; } public UniTask MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask(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; 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 : IUniTaskAsyncEnumerable { readonly IUniTaskAsyncEnumerable source; readonly Func keySelector; readonly Func elementSelector; readonly Func, TResult> resultSelector; readonly IEqualityComparer comparer; public GroupBy(IUniTaskAsyncEnumerable source, Func keySelector, Func elementSelector, Func, TResult> resultSelector, IEqualityComparer comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.resultSelector = resultSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupBy(source, keySelector, elementSelector, resultSelector, comparer, cancellationToken); } sealed class _GroupBy : MoveNextSource, IUniTaskAsyncEnumerator { readonly IUniTaskAsyncEnumerable source; readonly Func keySelector; readonly Func elementSelector; readonly Func, TResult> resultSelector; readonly IEqualityComparer comparer; CancellationToken cancellationToken; IEnumerator> groupEnumerator; public _GroupBy(IUniTaskAsyncEnumerable source, Func keySelector, Func elementSelector, Func, TResult> resultSelector, IEqualityComparer 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 MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask(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 : IUniTaskAsyncEnumerable> { readonly IUniTaskAsyncEnumerable source; readonly Func> keySelector; readonly Func> elementSelector; readonly IEqualityComparer comparer; public GroupByAwait(IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, IEqualityComparer comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupByAwait(source, keySelector, elementSelector, comparer, cancellationToken); } sealed class _GroupByAwait : MoveNextSource, IUniTaskAsyncEnumerator> { readonly IUniTaskAsyncEnumerable source; readonly Func> keySelector; readonly Func> elementSelector; readonly IEqualityComparer comparer; CancellationToken cancellationToken; IEnumerator> groupEnumerator; public _GroupByAwait(IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, IEqualityComparer comparer, CancellationToken cancellationToken) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; this.cancellationToken = cancellationToken; TaskTracker.TrackActiveTask(this, 3); } public IGrouping Current { get; private set; } public UniTask MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask(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; 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 : IUniTaskAsyncEnumerable { readonly IUniTaskAsyncEnumerable source; readonly Func> keySelector; readonly Func> elementSelector; readonly Func, UniTask> resultSelector; readonly IEqualityComparer comparer; public GroupByAwait(IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, Func, UniTask> resultSelector, IEqualityComparer comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.resultSelector = resultSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupByAwait(source, keySelector, elementSelector, resultSelector, comparer, cancellationToken); } sealed class _GroupByAwait : MoveNextSource, IUniTaskAsyncEnumerator { readonly static Action ResultSelectCoreDelegate = ResultSelectCore; readonly IUniTaskAsyncEnumerable source; readonly Func> keySelector; readonly Func> elementSelector; readonly Func, UniTask> resultSelector; readonly IEqualityComparer comparer; CancellationToken cancellationToken; IEnumerator> groupEnumerator; UniTask.Awaiter awaiter; public _GroupByAwait(IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, Func, UniTask> resultSelector, IEqualityComparer 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 MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask(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 : IUniTaskAsyncEnumerable> { readonly IUniTaskAsyncEnumerable source; readonly Func> keySelector; readonly Func> elementSelector; readonly IEqualityComparer comparer; public GroupByAwaitWithCancellation(IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, IEqualityComparer comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupByAwaitWithCancellation(source, keySelector, elementSelector, comparer, cancellationToken); } sealed class _GroupByAwaitWithCancellation : MoveNextSource, IUniTaskAsyncEnumerator> { readonly IUniTaskAsyncEnumerable source; readonly Func> keySelector; readonly Func> elementSelector; readonly IEqualityComparer comparer; CancellationToken cancellationToken; IEnumerator> groupEnumerator; public _GroupByAwaitWithCancellation(IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, IEqualityComparer comparer, CancellationToken cancellationToken) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.comparer = comparer; this.cancellationToken = cancellationToken; TaskTracker.TrackActiveTask(this, 3); } public IGrouping Current { get; private set; } public UniTask MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask(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; 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 : IUniTaskAsyncEnumerable { readonly IUniTaskAsyncEnumerable source; readonly Func> keySelector; readonly Func> elementSelector; readonly Func, CancellationToken, UniTask> resultSelector; readonly IEqualityComparer comparer; public GroupByAwaitWithCancellation(IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, Func, CancellationToken, UniTask> resultSelector, IEqualityComparer comparer) { this.source = source; this.keySelector = keySelector; this.elementSelector = elementSelector; this.resultSelector = resultSelector; this.comparer = comparer; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new _GroupByAwaitWithCancellation(source, keySelector, elementSelector, resultSelector, comparer, cancellationToken); } sealed class _GroupByAwaitWithCancellation : MoveNextSource, IUniTaskAsyncEnumerator { readonly static Action ResultSelectCoreDelegate = ResultSelectCore; readonly IUniTaskAsyncEnumerable source; readonly Func> keySelector; readonly Func> elementSelector; readonly Func, CancellationToken, UniTask> resultSelector; readonly IEqualityComparer comparer; CancellationToken cancellationToken; IEnumerator> groupEnumerator; UniTask.Awaiter awaiter; public _GroupByAwaitWithCancellation(IUniTaskAsyncEnumerable source, Func> keySelector, Func> elementSelector, Func, CancellationToken, UniTask> resultSelector, IEqualityComparer 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 MoveNextAsync() { cancellationToken.ThrowIfCancellationRequested(); completionSource.Reset(); if (groupEnumerator == null) { CreateLookup().Forget(); } else { SourceMoveNext(); } return new UniTask(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; } } } }