// 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.select?view=net-9.0-pp#system-linq-asyncenumerable-select-2(system-collections-generic-iasyncenumerable((-0))-system-func((-0-1)))
///
/// Projects each element of an async-enumerable sequence into a new form.
///
/// The type of the elements in the source sequence.
/// The type of the elements in the result sequence, obtained by running the selector function for each element in the source sequence.
/// A sequence of elements to invoke a transform function on.
/// A transform function to apply to each source element.
/// An async-enumerable sequence whose elements are the result of invoking the transform function on each element of source.
/// or is null.
public static IAsyncEnumerable Select(this IAsyncEnumerable source, Func selector)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (selector == null)
throw Error.ArgumentNull(nameof(selector));
return source switch
{
AsyncIterator iterator => iterator.Select(selector),
IList list => new SelectIListIterator(list, selector),
_ => new SelectEnumerableAsyncIterator(source, selector),
};
}
// https://learn.microsoft.com/en-us/dotnet/api/system.linq.asyncenumerable.select?view=net-9.0-pp#system-linq-asyncenumerable-select-2(system-collections-generic-iasyncenumerable((-0))-system-func((-0-system-int32-1)))
///
/// Projects each element of an async-enumerable sequence into a new form by incorporating the element's index.
///
/// The type of the elements in the source sequence.
/// The type of the elements in the result sequence, obtained by running the selector function for each element in the source sequence.
/// A sequence of elements to invoke a transform function on.
/// A transform function to apply to each source element; the second parameter of the function represents the index of the source element.
/// An async-enumerable sequence whose elements are the result of invoking the transform function on each element of source.
/// or is null.
public static IAsyncEnumerable Select(this IAsyncEnumerable source, Func selector)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (selector == null)
throw Error.ArgumentNull(nameof(selector));
return Core(source, selector);
static async IAsyncEnumerable Core(IAsyncEnumerable source, Func selector, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var index = -1;
await foreach (var element in source.WithCancellation(cancellationToken).ConfigureAwait(false))
{
checked
{
index++;
}
yield return selector(element, index);
}
}
}
#endif // INCLUDE_SYSTEM_LINQ_ASYNCENUMERABLE_DUPLICATES
///
/// Projects each element of an async-enumerable sequence into a new form by applying an asynchronous selector function to each member of the source sequence and awaiting the result.
///
/// The type of the elements in the source sequence.
/// The type of the elements in the result sequence, obtained by running the selector function for each element in the source sequence and awaiting the result.
/// A sequence of elements to invoke a transform function on.
/// An asynchronous transform function to apply to each source element.
/// An async-enumerable sequence whose elements are the result of invoking the transform function on each element of the source sequence and awaiting the result.
/// or is null.
[GenerateAsyncOverload]
[Obsolete("Use Select. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the SelectAwait functionality now exists as overloads of Select. You will need to modify your callback to take an additional CancellationToken argument.")]
private static IAsyncEnumerable SelectAwaitCore(this IAsyncEnumerable source, Func> selector)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (selector == null)
throw Error.ArgumentNull(nameof(selector));
return source switch
{
AsyncIterator iterator => iterator.Select(selector),
IList list => new SelectIListIteratorWithTask(list, selector),
_ => new SelectEnumerableAsyncIteratorWithTask(source, selector),
};
}
#if !NO_DEEP_CANCELLATION
[GenerateAsyncOverload]
[Obsolete("Use Select. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the SelectAwaitWithCancellation functionality now exists as overloads of Select.")]
private static IAsyncEnumerable SelectAwaitWithCancellationCore(this IAsyncEnumerable source, Func> selector)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (selector == null)
throw Error.ArgumentNull(nameof(selector));
return source switch
{
AsyncIterator iterator => iterator.Select(selector),
IList list => new SelectIListIteratorWithTaskAndCancellation(list, selector),
_ => new SelectEnumerableAsyncIteratorWithTaskAndCancellation(source, selector),
};
}
#endif
///
/// Projects each element of an async-enumerable sequence into a new form by applying an asynchronous selector function that incorporates each element's index to each element of the source sequence and awaiting the result.
///
/// The type of elements in the source sequence.
/// The type of elements in the result sequence, obtained by running the selector function for each element and its index, and awaiting the result.
/// A sequence of elements to invoke a transform function on.
/// An asynchronous transform function to apply to each source element; the second parameter represents the index of the element.
/// An async-enumerable sequence whose elements are the result of invoking the transform function on each element and its index of the source sequence and awaiting the result.
/// or is null.
[GenerateAsyncOverload]
[Obsolete("Use Select. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the SelectAwait functionality now exists as overloads of Select. You will need to modify your callback to take an additional CancellationToken argument.")]
private static IAsyncEnumerable SelectAwaitCore(this IAsyncEnumerable source, Func> selector)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (selector == null)
throw Error.ArgumentNull(nameof(selector));
return Core(source, selector);
static async IAsyncEnumerable Core(IAsyncEnumerable source, Func> selector, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var index = -1;
await foreach (var element in source.WithCancellation(cancellationToken).ConfigureAwait(false))
{
checked
{
index++;
}
yield return await selector(element, index).ConfigureAwait(false);
}
}
}
#if !NO_DEEP_CANCELLATION
[GenerateAsyncOverload]
[Obsolete("Use Select. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the SelectAwaitWithCancellation functionality now exists as overloads of Select.")]
private static IAsyncEnumerable SelectAwaitWithCancellationCore(this IAsyncEnumerable source, Func> selector)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (selector == null)
throw Error.ArgumentNull(nameof(selector));
return Core(source, selector);
static async IAsyncEnumerable Core(IAsyncEnumerable source, Func> selector, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var index = -1;
await foreach (var element in source.WithCancellation(cancellationToken).ConfigureAwait(false))
{
checked
{
index++;
}
yield return await selector(element, index, cancellationToken).ConfigureAwait(false);
}
}
}
#endif
internal sealed class SelectEnumerableAsyncIterator : AsyncIterator
{
private readonly Func _selector;
private readonly IAsyncEnumerable _source;
private IAsyncEnumerator? _enumerator;
public SelectEnumerableAsyncIterator(IAsyncEnumerable source, Func selector)
{
_source = source;
_selector = selector;
}
public override AsyncIteratorBase Clone()
{
return new SelectEnumerableAsyncIterator(_source, _selector);
}
public override async ValueTask DisposeAsync()
{
if (_enumerator != null)
{
await _enumerator.DisposeAsync().ConfigureAwait(false);
_enumerator = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
public override IAsyncEnumerable Select(Func selector)
{
return new SelectEnumerableAsyncIterator(_source, CombineSelectors(_selector, selector));
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_enumerator = _source.GetAsyncEnumerator(_cancellationToken);
_state = AsyncIteratorState.Iterating;
goto case AsyncIteratorState.Iterating;
case AsyncIteratorState.Iterating:
if (await _enumerator!.MoveNextAsync().ConfigureAwait(false))
{
_current = _selector(_enumerator.Current);
return true;
}
break;
}
await DisposeAsync().ConfigureAwait(false);
return false;
}
}
internal sealed class SelectIListIterator : AsyncIterator, IAsyncIListProvider
{
private readonly Func _selector;
private readonly IList _source;
private IEnumerator? _enumerator;
public SelectIListIterator(IList source, Func selector)
{
_source = source;
_selector = selector;
}
public override AsyncIteratorBase Clone()
{
return new SelectIListIterator(_source, _selector);
}
public override async ValueTask DisposeAsync()
{
if (_enumerator != null)
{
_enumerator.Dispose();
_enumerator = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
public ValueTask GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
{
if (onlyIfCheap)
{
return new ValueTask(-1);
}
cancellationToken.ThrowIfCancellationRequested();
var count = 0;
foreach (var item in _source)
{
_selector(item);
checked
{
count++;
}
}
return new ValueTask(count);
}
public override IAsyncEnumerable Select(Func selector)
{
return new SelectIListIterator(_source, CombineSelectors(_selector, selector));
}
public ValueTask ToArrayAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var n = _source.Count;
var res = new TResult[n];
for (var i = 0; i < n; i++)
{
res[i] = _selector(_source[i]);
}
return new ValueTask(res);
}
public ValueTask> ToListAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var n = _source.Count;
var res = new List(n);
for (var i = 0; i < n; i++)
{
res.Add(_selector(_source[i]));
}
return new ValueTask>(res);
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_enumerator = _source.GetEnumerator();
_state = AsyncIteratorState.Iterating;
goto case AsyncIteratorState.Iterating;
case AsyncIteratorState.Iterating:
if (_enumerator!.MoveNext())
{
_current = _selector(_enumerator.Current);
return true;
}
await DisposeAsync().ConfigureAwait(false);
break;
}
return false;
}
}
internal sealed class SelectEnumerableAsyncIteratorWithTask : AsyncIterator
{
private readonly Func> _selector;
private readonly IAsyncEnumerable _source;
private IAsyncEnumerator? _enumerator;
public SelectEnumerableAsyncIteratorWithTask(IAsyncEnumerable source, Func> selector)
{
_source = source;
_selector = selector;
}
public override AsyncIteratorBase Clone()
{
return new SelectEnumerableAsyncIteratorWithTask(_source, _selector);
}
public override async ValueTask DisposeAsync()
{
if (_enumerator != null)
{
await _enumerator.DisposeAsync().ConfigureAwait(false);
_enumerator = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
public override IAsyncEnumerable Select(Func> selector)
{
return new SelectEnumerableAsyncIteratorWithTask(_source, CombineSelectors(_selector, selector));
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_enumerator = _source.GetAsyncEnumerator(_cancellationToken);
_state = AsyncIteratorState.Iterating;
goto case AsyncIteratorState.Iterating;
case AsyncIteratorState.Iterating:
if (await _enumerator!.MoveNextAsync().ConfigureAwait(false))
{
_current = await _selector(_enumerator.Current).ConfigureAwait(false);
return true;
}
break;
}
await DisposeAsync().ConfigureAwait(false);
return false;
}
}
#if !NO_DEEP_CANCELLATION
internal sealed class SelectEnumerableAsyncIteratorWithTaskAndCancellation : AsyncIterator
{
private readonly Func> _selector;
private readonly IAsyncEnumerable _source;
private IAsyncEnumerator? _enumerator;
public SelectEnumerableAsyncIteratorWithTaskAndCancellation(IAsyncEnumerable source, Func> selector)
{
_source = source;
_selector = selector;
}
public override AsyncIteratorBase Clone()
{
return new SelectEnumerableAsyncIteratorWithTaskAndCancellation(_source, _selector);
}
public override async ValueTask DisposeAsync()
{
if (_enumerator != null)
{
await _enumerator.DisposeAsync().ConfigureAwait(false);
_enumerator = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
public override IAsyncEnumerable Select(Func> selector)
{
return new SelectEnumerableAsyncIteratorWithTaskAndCancellation(_source, CombineSelectors(_selector, selector));
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_enumerator = _source.GetAsyncEnumerator(_cancellationToken);
_state = AsyncIteratorState.Iterating;
goto case AsyncIteratorState.Iterating;
case AsyncIteratorState.Iterating:
if (await _enumerator!.MoveNextAsync().ConfigureAwait(false))
{
_current = await _selector(_enumerator.Current, _cancellationToken).ConfigureAwait(false);
return true;
}
break;
}
await DisposeAsync().ConfigureAwait(false);
return false;
}
}
#endif
// NB: LINQ to Objects implements IPartition for this. However, it seems incorrect to do so in a trivial
// manner where e.g. TryGetLast simply indexes into the list without running the selector for the first n - 1
// elements in order to ensure side-effects. We should consider whether we want to follow this implementation
// strategy or support IAsyncPartition in a less efficient but more correct manner here.
private sealed class SelectIListIteratorWithTask : AsyncIterator, IAsyncIListProvider
{
private readonly Func> _selector;
private readonly IList _source;
private IEnumerator? _enumerator;
public SelectIListIteratorWithTask(IList source, Func> selector)
{
_source = source;
_selector = selector;
}
public override AsyncIteratorBase Clone()
{
return new SelectIListIteratorWithTask(_source, _selector);
}
public override async ValueTask DisposeAsync()
{
if (_enumerator != null)
{
_enumerator.Dispose();
_enumerator = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
public ValueTask GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
{
if (onlyIfCheap)
{
return new ValueTask(-1);
}
return Core();
async ValueTask Core()
{
cancellationToken.ThrowIfCancellationRequested();
var count = 0;
foreach (var item in _source)
{
await _selector(item).ConfigureAwait(false);
checked
{
count++;
}
}
return count;
}
}
public override IAsyncEnumerable Select(Func> selector)
{
return new SelectIListIteratorWithTask(_source, CombineSelectors(_selector, selector));
}
public async ValueTask ToArrayAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var n = _source.Count;
var res = new TResult[n];
for (var i = 0; i < n; i++)
{
res[i] = await _selector(_source[i]).ConfigureAwait(false);
}
return res;
}
public async ValueTask> ToListAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var n = _source.Count;
var res = new List(n);
for (var i = 0; i < n; i++)
{
res.Add(await _selector(_source[i]).ConfigureAwait(false));
}
return res;
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_enumerator = _source.GetEnumerator();
_state = AsyncIteratorState.Iterating;
goto case AsyncIteratorState.Iterating;
case AsyncIteratorState.Iterating:
if (_enumerator!.MoveNext())
{
_current = await _selector(_enumerator.Current).ConfigureAwait(false);
return true;
}
break;
}
await DisposeAsync().ConfigureAwait(false);
return false;
}
}
#if !NO_DEEP_CANCELLATION
private sealed class SelectIListIteratorWithTaskAndCancellation : AsyncIterator, IAsyncIListProvider
{
private readonly Func> _selector;
private readonly IList _source;
private IEnumerator? _enumerator;
public SelectIListIteratorWithTaskAndCancellation(IList source, Func> selector)
{
_source = source;
_selector = selector;
}
public override AsyncIteratorBase Clone()
{
return new SelectIListIteratorWithTaskAndCancellation(_source, _selector);
}
public override async ValueTask DisposeAsync()
{
if (_enumerator != null)
{
_enumerator.Dispose();
_enumerator = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
public ValueTask GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
{
if (onlyIfCheap)
{
return new ValueTask(-1);
}
return Core();
async ValueTask Core()
{
cancellationToken.ThrowIfCancellationRequested();
var count = 0;
foreach (var item in _source)
{
await _selector(item, cancellationToken).ConfigureAwait(false);
checked
{
count++;
}
}
return count;
}
}
public override IAsyncEnumerable Select(Func> selector)
{
return new SelectIListIteratorWithTaskAndCancellation(_source, CombineSelectors(_selector, selector));
}
public async ValueTask ToArrayAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var n = _source.Count;
var res = new TResult[n];
for (var i = 0; i < n; i++)
{
res[i] = await _selector(_source[i], cancellationToken).ConfigureAwait(false);
}
return res;
}
public async ValueTask> ToListAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var n = _source.Count;
var res = new List(n);
for (var i = 0; i < n; i++)
{
res.Add(await _selector(_source[i], cancellationToken).ConfigureAwait(false));
}
return res;
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_enumerator = _source.GetEnumerator();
_state = AsyncIteratorState.Iterating;
goto case AsyncIteratorState.Iterating;
case AsyncIteratorState.Iterating:
if (_enumerator!.MoveNext())
{
_current = await _selector(_enumerator.Current, _cancellationToken).ConfigureAwait(false);
return true;
}
break;
}
await DisposeAsync().ConfigureAwait(false);
return false;
}
}
#endif
}
}