// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace System.Linq
{
public static partial class AsyncEnumerable
{
///
/// Append a value to an async-enumerable sequence.
///
/// The type of the elements in the source sequence.
/// Source sequence to append the value to.
/// Element to append to the specified sequence.
/// The source sequence appended with the specified value.
/// is null.
public static IAsyncEnumerable Append(this IAsyncEnumerable source, TSource element)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (source is AppendPrependAsyncIterator appendable)
{
return appendable.Append(element);
}
return new AppendPrepend1AsyncIterator(source, element, appending: true);
}
///
/// Prepend a value to an async-enumerable sequence.
///
/// The type of the elements in the source sequence.
/// Source sequence to prepend the value to.
/// Element to prepend to the specified sequence.
/// The source sequence prepended with the specified value.
/// is null.
public static IAsyncEnumerable Prepend(this IAsyncEnumerable source, TSource element)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (source is AppendPrependAsyncIterator appendable)
{
return appendable.Prepend(element);
}
return new AppendPrepend1AsyncIterator(source, element, appending: false);
}
private abstract class AppendPrependAsyncIterator : AsyncIterator, IAsyncIListProvider
{
protected readonly IAsyncEnumerable _source;
protected IAsyncEnumerator? _enumerator;
protected AppendPrependAsyncIterator(IAsyncEnumerable source)
{
_source = source;
}
protected void GetSourceEnumerator(CancellationToken cancellationToken)
{
Debug.Assert(_enumerator == null);
_enumerator = _source.GetAsyncEnumerator(cancellationToken);
}
public abstract AppendPrependAsyncIterator Append(TSource item);
public abstract AppendPrependAsyncIterator Prepend(TSource item);
protected async Task LoadFromEnumeratorAsync()
{
if (await _enumerator!.MoveNextAsync().ConfigureAwait(false))
{
_current = _enumerator.Current;
return true;
}
if (_enumerator != null)
{
await _enumerator.DisposeAsync().ConfigureAwait(false);
_enumerator = null;
}
return false;
}
public override async ValueTask DisposeAsync()
{
if (_enumerator != null)
{
await _enumerator.DisposeAsync().ConfigureAwait(false);
_enumerator = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
public abstract ValueTask ToArrayAsync(CancellationToken cancellationToken);
public abstract ValueTask> ToListAsync(CancellationToken cancellationToken);
public abstract ValueTask GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken);
}
private sealed class AppendPrepend1AsyncIterator : AppendPrependAsyncIterator
{
private readonly TSource _item;
private readonly bool _appending;
private bool _hasEnumerator;
public AppendPrepend1AsyncIterator(IAsyncEnumerable source, TSource item, bool appending)
: base(source)
{
_item = item;
_appending = appending;
}
public override AsyncIteratorBase Clone()
{
return new AppendPrepend1AsyncIterator(_source, _item, _appending);
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_hasEnumerator = false;
_state = AsyncIteratorState.Iterating;
if (!_appending)
{
_current = _item;
return true;
}
goto case AsyncIteratorState.Iterating;
case AsyncIteratorState.Iterating:
if (!_hasEnumerator)
{
GetSourceEnumerator(_cancellationToken);
_hasEnumerator = true;
}
if (_enumerator != null)
{
if (await LoadFromEnumeratorAsync().ConfigureAwait(false))
{
return true;
}
if (_appending)
{
_current = _item;
return true;
}
}
break;
}
await DisposeAsync().ConfigureAwait(false);
return false;
}
public override AppendPrependAsyncIterator Append(TSource element)
{
if (_appending)
{
return new AppendPrependNAsyncIterator(_source, null, new SingleLinkedNode(_item).Add(element), prependCount: 0, appendCount: 2);
}
else
{
return new AppendPrependNAsyncIterator(_source, new SingleLinkedNode(_item), new SingleLinkedNode(element), prependCount: 1, appendCount: 1);
}
}
public override AppendPrependAsyncIterator Prepend(TSource element)
{
if (_appending)
{
return new AppendPrependNAsyncIterator(_source, new SingleLinkedNode(element), new SingleLinkedNode(_item), prependCount: 1, appendCount: 1);
}
else
{
return new AppendPrependNAsyncIterator(_source, new SingleLinkedNode(_item).Add(element), null, prependCount: 2, appendCount: 0);
}
}
public override async ValueTask ToArrayAsync(CancellationToken cancellationToken)
{
var count = await GetCountAsync(onlyIfCheap: true, cancellationToken).ConfigureAwait(false);
if (count == -1)
{
return await AsyncEnumerableHelpers.ToArray(this, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
var array = new TSource[count];
int index;
if (_appending)
{
index = 0;
}
else
{
array[0] = _item;
index = 1;
}
if (_source is ICollection sourceCollection)
{
sourceCollection.CopyTo(array, index);
}
else
{
await foreach (var item in _source.WithCancellation(cancellationToken).ConfigureAwait(false))
{
array[index] = item;
++index;
}
}
if (_appending)
{
array[array.Length - 1] = _item;
}
return array;
}
public override async ValueTask> ToListAsync(CancellationToken cancellationToken)
{
var count = await GetCountAsync(onlyIfCheap: true, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
var list = count == -1 ? new List() : new List(count);
if (!_appending)
{
list.Add(_item);
}
await foreach (var item in _source.WithCancellation(cancellationToken).ConfigureAwait(false))
{
list.Add(item);
}
if (_appending)
{
list.Add(_item);
}
return list;
}
public override async ValueTask GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
{
if (_source is IAsyncIListProvider listProv)
{
var count = await listProv.GetCountAsync(onlyIfCheap, cancellationToken).ConfigureAwait(false);
return count == -1 ? -1 : count + 1;
}
return !onlyIfCheap || _source is ICollection || _source is ICollection ? await _source.CountAsync(cancellationToken).ConfigureAwait(false) + 1 : -1;
}
}
private sealed class AppendPrependNAsyncIterator : AppendPrependAsyncIterator
{
private readonly SingleLinkedNode? _prepended;
private readonly SingleLinkedNode? _appended;
private readonly int _prependCount;
private readonly int _appendCount;
private SingleLinkedNode? _node;
private int _mode;
private IEnumerator? _appendedEnumerator;
public AppendPrependNAsyncIterator(IAsyncEnumerable source, SingleLinkedNode? prepended, SingleLinkedNode? appended, int prependCount, int appendCount)
: base(source)
{
Debug.Assert(prepended != null || appended != null);
Debug.Assert(prependCount > 0 || appendCount > 0);
Debug.Assert(prependCount + appendCount >= 2);
Debug.Assert((prepended?.GetCount() ?? 0) == prependCount);
Debug.Assert((appended?.GetCount() ?? 0) == appendCount);
_prepended = prepended;
_appended = appended;
_prependCount = prependCount;
_appendCount = appendCount;
}
public override AsyncIteratorBase Clone()
{
return new AppendPrependNAsyncIterator(_source, _prepended, _appended, _prependCount, _appendCount);
}
public override async ValueTask DisposeAsync()
{
if (_appendedEnumerator != null)
{
_appendedEnumerator.Dispose();
_appendedEnumerator = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_mode = 1;
_state = AsyncIteratorState.Iterating;
goto case AsyncIteratorState.Iterating;
case AsyncIteratorState.Iterating:
switch (_mode)
{
case 1:
_node = _prepended;
_mode = 2;
goto case 2;
case 2:
if (_node != null)
{
_current = _node.Item;
_node = _node.Linked;
return true;
}
GetSourceEnumerator(_cancellationToken);
_mode = 3;
goto case 3;
case 3:
if (await LoadFromEnumeratorAsync().ConfigureAwait(false))
{
return true;
}
if (_appended != null)
{
_appendedEnumerator = _appended.GetEnumerator(_appendCount);
_mode = 4;
goto case 4;
}
break;
case 4:
if (_appendedEnumerator!.MoveNext())
{
_current = _appendedEnumerator.Current;
return true;
}
break;
}
break;
}
await DisposeAsync().ConfigureAwait(false);
return false;
}
public override AppendPrependAsyncIterator Append(TSource item)
{
var res = _appended != null ? _appended.Add(item) : new SingleLinkedNode(item);
return new AppendPrependNAsyncIterator(_source, _prepended, res, _prependCount, _appendCount + 1);
}
public override AppendPrependAsyncIterator Prepend(TSource item)
{
var res = _prepended != null ? _prepended.Add(item) : new SingleLinkedNode(item);
return new AppendPrependNAsyncIterator(_source, res, _appended, _prependCount + 1, _appendCount);
}
public override async ValueTask ToArrayAsync(CancellationToken cancellationToken)
{
var count = await GetCountAsync(onlyIfCheap: true, cancellationToken).ConfigureAwait(false);
if (count == -1)
{
return await AsyncEnumerableHelpers.ToArray(this, cancellationToken).ConfigureAwait(false);
}
var array = new TSource[count];
var index = 0;
for (var n = _prepended; n != null; n = n.Linked)
{
array[index] = n.Item;
++index;
}
if (_source is ICollection sourceCollection)
{
sourceCollection.CopyTo(array, index);
}
else
{
await foreach (var item in _source.WithCancellation(cancellationToken).ConfigureAwait(false))
{
array[index] = item;
++index;
}
}
index = array.Length;
for (var n = _appended; n != null; n = n.Linked)
{
--index;
array[index] = n.Item;
}
return array;
}
public override async ValueTask> ToListAsync(CancellationToken cancellationToken)
{
var count = await GetCountAsync(onlyIfCheap: true, cancellationToken).ConfigureAwait(false);
var list = count == -1 ? new List() : new List(count);
for (var n = _prepended; n != null; n = n.Linked)
{
list.Add(n.Item);
}
await foreach (var item in _source.WithCancellation(cancellationToken).ConfigureAwait(false))
{
list.Add(item);
}
if (_appended != null)
{
using var en2 = _appended.GetEnumerator(_appendCount);
while (en2.MoveNext())
{
list.Add(en2.Current);
}
}
return list;
}
public override async ValueTask GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
{
if (_source is IAsyncIListProvider listProv)
{
var count = await listProv.GetCountAsync(onlyIfCheap, cancellationToken).ConfigureAwait(false);
return count == -1 ? -1 : count + _appendCount + _prependCount;
}
return !onlyIfCheap || _source is ICollection || _source is ICollection ? await _source.CountAsync(cancellationToken).ConfigureAwait(false) + _appendCount + _prependCount : -1;
}
}
}
}