// 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 AsyncEnumerableEx
{
///
/// Returns an async-enumerable sequence that contains only distinct elements according to the keySelector.
///
/// The type of the elements in the source sequence.
/// The type of the discriminator key computed for each element in the source sequence.
/// An async-enumerable sequence to retain distinct elements for.
/// A function to compute the comparison key for each element.
/// An async-enumerable sequence only containing the distinct elements, based on a computed key value, from the source sequence.
/// or is null.
/// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large.
public static IAsyncEnumerable Distinct(this IAsyncEnumerable source, Func keySelector)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (keySelector == null)
throw Error.ArgumentNull(nameof(keySelector));
return DistinctCore(source, keySelector, comparer: null);
}
///
/// Returns an async-enumerable sequence that contains only distinct elements according to the keySelector and the comparer.
///
/// The type of the elements in the source sequence.
/// The type of the discriminator key computed for each element in the source sequence.
/// An async-enumerable sequence to retain distinct elements for.
/// A function to compute the comparison key for each element.
/// Equality comparer for source elements.
/// An async-enumerable sequence only containing the distinct elements, based on a computed key value, from the source sequence.
/// or or is null.
/// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large.
public static IAsyncEnumerable Distinct(this IAsyncEnumerable source, Func keySelector, IEqualityComparer? comparer)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (keySelector == null)
throw Error.ArgumentNull(nameof(keySelector));
return DistinctCore(source, keySelector, comparer);
}
///
/// Returns an async-enumerable sequence that contains only distinct elements according to the asynchronous keySelector.
///
/// The type of the elements in the source sequence.
/// The type of the discriminator key computed for each element in the source sequence.
/// An async-enumerable sequence to retain distinct elements for.
/// An asynchronous function to compute the comparison key for each element.
/// An async-enumerable sequence only containing the distinct elements, based on a computed key value, from the source sequence.
/// or is null.
/// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large.
public static IAsyncEnumerable Distinct(this IAsyncEnumerable source, Func> keySelector)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (keySelector == null)
throw Error.ArgumentNull(nameof(keySelector));
return DistinctCore(source, keySelector, comparer: null);
}
#if !NO_DEEP_CANCELLATION
///
/// Returns an async-enumerable sequence that contains only distinct elements according to the asynchronous (cancellable) keySelector.
///
/// The type of the elements in the source sequence.
/// The type of the discriminator key computed for each element in the source sequence.
/// An async-enumerable sequence to retain distinct elements for.
/// An asynchronous (cancellable) function to compute the comparison key for each element.
/// An async-enumerable sequence only containing the distinct elements, based on a computed key value, from the source sequence.
/// or is null.
/// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large.
public static IAsyncEnumerable Distinct(this IAsyncEnumerable source, Func> keySelector)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (keySelector == null)
throw Error.ArgumentNull(nameof(keySelector));
return DistinctCore(source, keySelector, comparer: null);
}
#endif
///
/// Returns an async-enumerable sequence that contains only distinct elements according to the asynchronous keySelector and the comparer.
///
/// The type of the elements in the source sequence.
/// The type of the discriminator key computed for each element in the source sequence.
/// An async-enumerable sequence to retain distinct elements for.
/// An asynchronous function to compute the comparison key for each element.
/// Equality comparer for source elements.
/// An async-enumerable sequence only containing the distinct elements, based on a computed key value, from the source sequence.
/// or or is null.
/// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large.
public static IAsyncEnumerable Distinct(this IAsyncEnumerable source, Func> keySelector, IEqualityComparer? comparer)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (keySelector == null)
throw Error.ArgumentNull(nameof(keySelector));
return DistinctCore(source, keySelector, comparer);
}
#if !NO_DEEP_CANCELLATION
///
/// Returns an async-enumerable sequence that contains only distinct elements according to the asynchronous (cancellable) keySelector and the comparer.
///
/// The type of the elements in the source sequence.
/// The type of the discriminator key computed for each element in the source sequence.
/// An async-enumerable sequence to retain distinct elements for.
/// An asynchronous (cancellable) function to compute the comparison key for each element.
/// Equality comparer for source elements.
/// An async-enumerable sequence only containing the distinct elements, based on a computed key value, from the source sequence.
/// or or is null.
/// Usage of this operator should be considered carefully due to the maintenance of an internal lookup structure which can grow large.
public static IAsyncEnumerable Distinct(this IAsyncEnumerable source, Func> keySelector, IEqualityComparer? comparer)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (keySelector == null)
throw Error.ArgumentNull(nameof(keySelector));
return DistinctCore(source, keySelector, comparer);
}
#endif
private static IAsyncEnumerable DistinctCore(IAsyncEnumerable source, Func keySelector, IEqualityComparer? comparer)
{
return new DistinctAsyncIterator(source, keySelector, comparer);
}
private static IAsyncEnumerable DistinctCore(IAsyncEnumerable source, Func> keySelector, IEqualityComparer? comparer)
{
return new DistinctAsyncIteratorWithTask(source, keySelector, comparer);
}
#if !NO_DEEP_CANCELLATION
private static IAsyncEnumerable DistinctCore(IAsyncEnumerable source, Func> keySelector, IEqualityComparer? comparer)
{
return new DistinctAsyncIteratorWithTaskAndCancellation(source, keySelector, comparer);
}
#endif
private sealed class DistinctAsyncIterator : AsyncIterator, IAsyncIListProvider
{
private readonly IEqualityComparer? _comparer;
private readonly Func _keySelector;
private readonly IAsyncEnumerable _source;
private IAsyncEnumerator? _enumerator;
private Set? _set;
public DistinctAsyncIterator(IAsyncEnumerable source, Func keySelector, IEqualityComparer? comparer)
{
_source = source;
_keySelector = keySelector;
_comparer = comparer;
}
public async ValueTask ToArrayAsync(CancellationToken cancellationToken)
{
var s = await FillSetAsync(cancellationToken).ConfigureAwait(false);
return s.ToArray();
}
public async ValueTask> ToListAsync(CancellationToken cancellationToken)
{
var s = await FillSetAsync(cancellationToken).ConfigureAwait(false);
return s;
}
public async ValueTask GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
{
if (onlyIfCheap)
{
return -1;
}
var count = 0;
var s = new Set(_comparer);
var enu = _source.GetAsyncEnumerator(cancellationToken);
try
{
while (await enu.MoveNextAsync().ConfigureAwait(false))
{
var item = enu.Current;
if (s.Add(_keySelector(item)))
{
count++;
}
}
}
finally
{
await enu.DisposeAsync().ConfigureAwait(false);
}
return count;
}
public override AsyncIteratorBase Clone()
{
return new DistinctAsyncIterator(_source, _keySelector, _comparer);
}
public override async ValueTask DisposeAsync()
{
if (_enumerator != null)
{
await _enumerator.DisposeAsync().ConfigureAwait(false);
_enumerator = null;
_set = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_enumerator = _source.GetAsyncEnumerator(_cancellationToken);
if (!await _enumerator.MoveNextAsync().ConfigureAwait(false))
{
await DisposeAsync().ConfigureAwait(false);
return false;
}
var element = _enumerator.Current;
_set = new Set(_comparer);
_set.Add(_keySelector(element));
_current = element;
_state = AsyncIteratorState.Iterating;
return true;
case AsyncIteratorState.Iterating:
while (await _enumerator!.MoveNextAsync().ConfigureAwait(false))
{
element = _enumerator.Current;
if (_set!.Add(_keySelector(element)))
{
_current = element;
return true;
}
}
break;
}
await DisposeAsync().ConfigureAwait(false);
return false;
}
private async Task> FillSetAsync(CancellationToken cancellationToken)
{
var s = new Set(_comparer);
var r = new List();
var enu = _source.GetAsyncEnumerator(cancellationToken);
try
{
while (await enu.MoveNextAsync().ConfigureAwait(false))
{
var item = enu.Current;
if (s.Add(_keySelector(item)))
{
r.Add(item);
}
}
}
finally
{
await enu.DisposeAsync().ConfigureAwait(false);
}
return r;
}
}
private sealed class DistinctAsyncIteratorWithTask : AsyncIterator, IAsyncIListProvider
{
private readonly IEqualityComparer? _comparer;
private readonly Func> _keySelector;
private readonly IAsyncEnumerable _source;
private IAsyncEnumerator? _enumerator;
private Set? _set;
public DistinctAsyncIteratorWithTask(IAsyncEnumerable source, Func> keySelector, IEqualityComparer? comparer)
{
_source = source;
_keySelector = keySelector;
_comparer = comparer;
}
public async ValueTask ToArrayAsync(CancellationToken cancellationToken)
{
var s = await FillSetAsync(cancellationToken).ConfigureAwait(false);
return s.ToArray();
}
public async ValueTask> ToListAsync(CancellationToken cancellationToken)
{
var s = await FillSetAsync(cancellationToken).ConfigureAwait(false);
return s;
}
public async ValueTask GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
{
if (onlyIfCheap)
{
return -1;
}
var count = 0;
var s = new Set(_comparer);
var enu = _source.GetAsyncEnumerator(cancellationToken);
try
{
while (await enu.MoveNextAsync().ConfigureAwait(false))
{
var item = enu.Current;
if (s.Add(await _keySelector(item).ConfigureAwait(false)))
{
count++;
}
}
}
finally
{
await enu.DisposeAsync().ConfigureAwait(false);
}
return count;
}
public override AsyncIteratorBase Clone()
{
return new DistinctAsyncIteratorWithTask(_source, _keySelector, _comparer);
}
public override async ValueTask DisposeAsync()
{
if (_enumerator != null)
{
await _enumerator.DisposeAsync().ConfigureAwait(false);
_enumerator = null;
_set = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_enumerator = _source.GetAsyncEnumerator(_cancellationToken);
if (!await _enumerator.MoveNextAsync().ConfigureAwait(false))
{
await DisposeAsync().ConfigureAwait(false);
return false;
}
var element = _enumerator.Current;
_set = new Set(_comparer);
_set.Add(await _keySelector(element).ConfigureAwait(false));
_current = element;
_state = AsyncIteratorState.Iterating;
return true;
case AsyncIteratorState.Iterating:
while (await _enumerator!.MoveNextAsync().ConfigureAwait(false))
{
element = _enumerator.Current;
if (_set!.Add(await _keySelector(element).ConfigureAwait(false)))
{
_current = element;
return true;
}
}
break;
}
await DisposeAsync().ConfigureAwait(false);
return false;
}
private async ValueTask> FillSetAsync(CancellationToken cancellationToken)
{
var s = new Set(_comparer);
var r = new List();
var enu = _source.GetAsyncEnumerator(cancellationToken);
try
{
while (await enu.MoveNextAsync().ConfigureAwait(false))
{
var item = enu.Current;
if (s.Add(await _keySelector(item).ConfigureAwait(false)))
{
r.Add(item);
}
}
}
finally
{
await enu.DisposeAsync().ConfigureAwait(false);
}
return r;
}
}
#if !NO_DEEP_CANCELLATION
private sealed class DistinctAsyncIteratorWithTaskAndCancellation : AsyncIterator, IAsyncIListProvider
{
private readonly IEqualityComparer? _comparer;
private readonly Func> _keySelector;
private readonly IAsyncEnumerable _source;
private IAsyncEnumerator? _enumerator;
private Set? _set;
public DistinctAsyncIteratorWithTaskAndCancellation(IAsyncEnumerable source, Func> keySelector, IEqualityComparer? comparer)
{
_source = source;
_keySelector = keySelector;
_comparer = comparer;
}
public async ValueTask ToArrayAsync(CancellationToken cancellationToken)
{
var s = await FillSetAsync(cancellationToken).ConfigureAwait(false);
return s.ToArray();
}
public async ValueTask> ToListAsync(CancellationToken cancellationToken)
{
var s = await FillSetAsync(cancellationToken).ConfigureAwait(false);
return s;
}
public async ValueTask GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
{
if (onlyIfCheap)
{
return -1;
}
var count = 0;
var s = new Set(_comparer);
var enu = _source.GetAsyncEnumerator(cancellationToken);
try
{
while (await enu.MoveNextAsync().ConfigureAwait(false))
{
var item = enu.Current;
if (s.Add(await _keySelector(item, cancellationToken).ConfigureAwait(false)))
{
count++;
}
}
}
finally
{
await enu.DisposeAsync().ConfigureAwait(false);
}
return count;
}
public override AsyncIteratorBase Clone()
{
return new DistinctAsyncIteratorWithTaskAndCancellation(_source, _keySelector, _comparer);
}
public override async ValueTask DisposeAsync()
{
if (_enumerator != null)
{
await _enumerator.DisposeAsync().ConfigureAwait(false);
_enumerator = null;
_set = null;
}
await base.DisposeAsync().ConfigureAwait(false);
}
protected override async ValueTask MoveNextCore()
{
switch (_state)
{
case AsyncIteratorState.Allocated:
_enumerator = _source.GetAsyncEnumerator(_cancellationToken);
if (!await _enumerator.MoveNextAsync().ConfigureAwait(false))
{
await DisposeAsync().ConfigureAwait(false);
return false;
}
var element = _enumerator.Current;
_set = new Set(_comparer);
_set.Add(await _keySelector(element, _cancellationToken).ConfigureAwait(false));
_current = element;
_state = AsyncIteratorState.Iterating;
return true;
case AsyncIteratorState.Iterating:
while (await _enumerator!.MoveNextAsync().ConfigureAwait(false))
{
element = _enumerator.Current;
if (_set!.Add(await _keySelector(element, _cancellationToken).ConfigureAwait(false)))
{
_current = element;
return true;
}
}
break;
}
await DisposeAsync().ConfigureAwait(false);
return false;
}
private async ValueTask> FillSetAsync(CancellationToken cancellationToken)
{
var s = new Set(_comparer);
var r = new List();
var enu = _source.GetAsyncEnumerator(cancellationToken);
try
{
while (await enu.MoveNextAsync().ConfigureAwait(false))
{
var item = enu.Current;
if (s.Add(await _keySelector(item, cancellationToken).ConfigureAwait(false)))
{
r.Add(item);
}
}
}
finally
{
await enu.DisposeAsync().ConfigureAwait(false);
}
return r;
}
}
#endif
}
}