// 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.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
namespace System.Linq
{
public static partial class AsyncEnumerableEx
{
// REVIEW: All Catch operators may catch OperationCanceledException due to cancellation of the enumeration
// of the source. Should we explicitly avoid handling this? E.g. as follows:
//
// catch (TException ex) when(!(ex is OperationCanceledException oce && oce.CancellationToken == cancellationToken))
///
/// Continues an async-enumerable sequence that is terminated by an exception of the specified type with the async-enumerable sequence produced by the handler.
///
/// The type of the elements in the source sequence and sequences returned by the exception handler function.
/// The type of the exception to catch and handle. Needs to derive from .
/// Source sequence.
/// Exception handler function, producing another async-enumerable sequence.
/// An async-enumerable sequence containing the source sequence's elements, followed by the elements produced by the handler's resulting async-enumerable sequence in case an exception occurred.
/// or is null.
public static IAsyncEnumerable Catch(this IAsyncEnumerable source, Func> handler)
where TException : Exception
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (handler == null)
throw Error.ArgumentNull(nameof(handler));
return Core(source, handler);
static async IAsyncEnumerable Core(IAsyncEnumerable source, Func> handler, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// REVIEW: This implementation mirrors the Ix implementation, which does not protect GetEnumerator
// using the try statement either. A more trivial implementation would use await foreach
// and protect the entire loop using a try statement, with two breaking changes:
//
// - Also protecting the call to GetAsyncEnumerator by the try statement.
// - Invocation of the handler after disposal of the failed first sequence.
var err = default(IAsyncEnumerable);
await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
{
while (true)
{
TSource c;
try
{
if (!await e.MoveNextAsync())
break;
c = e.Current;
}
catch (TException ex)
{
err = handler(ex);
break;
}
yield return c;
}
}
if (err != null)
{
await foreach (var item in err.WithCancellation(cancellationToken).ConfigureAwait(false))
{
yield return item;
}
}
}
}
///
/// Continues an async-enumerable sequence that is terminated by an exception of the specified type with the async-enumerable sequence produced asynchronously by the handler.
///
/// The type of the elements in the source sequence and sequences returned by the exception handler function.
/// The type of the exception to catch and handle. Needs to derive from .
/// Source sequence.
/// Exception handler function, producing another async-enumerable sequence asynchronously.
/// An async-enumerable sequence containing the source sequence's elements, followed by the elements produced by the handler's resulting async-enumerable sequence in case an exception occurred.
/// or is null.
public static IAsyncEnumerable Catch(this IAsyncEnumerable source, Func>> handler)
where TException : Exception
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (handler == null)
throw Error.ArgumentNull(nameof(handler));
return Core(source, handler);
static async IAsyncEnumerable Core(IAsyncEnumerable source, Func>> handler, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// REVIEW: This implementation mirrors the Ix implementation, which does not protect GetEnumerator
// using the try statement either. A more trivial implementation would use await foreach
// and protect the entire loop using a try statement, with two breaking changes:
//
// - Also protecting the call to GetAsyncEnumerator by the try statement.
// - Invocation of the handler after disposal of the failed first sequence.
var err = default(IAsyncEnumerable);
await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
{
while (true)
{
TSource c;
try
{
if (!await e.MoveNextAsync())
break;
c = e.Current;
}
catch (TException ex)
{
err = await handler(ex).ConfigureAwait(false);
break;
}
yield return c;
}
}
if (err != null)
{
await foreach (var item in err.WithCancellation(cancellationToken).ConfigureAwait(false))
{
yield return item;
}
}
}
}
#if !NO_DEEP_CANCELLATION
///
/// Continues an async-enumerable sequence that is terminated by an exception of the specified type with the async-enumerable sequence produced asynchronously (cancellable) by the handler.
///
/// The type of the elements in the source sequence and sequences returned by the exception handler function.
/// The type of the exception to catch and handle. Needs to derive from .
/// Source sequence.
/// Exception handler function, producing another async-enumerable sequence asynchronously while supporting cancellation.
/// An async-enumerable sequence containing the source sequence's elements, followed by the elements produced by the handler's resulting async-enumerable sequence in case an exception occurred.
/// or is null.
public static IAsyncEnumerable Catch(this IAsyncEnumerable source, Func>> handler)
where TException : Exception
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (handler == null)
throw Error.ArgumentNull(nameof(handler));
return Core(source, handler);
static async IAsyncEnumerable Core(IAsyncEnumerable source, Func>> handler, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// REVIEW: This implementation mirrors the Ix implementation, which does not protect GetEnumerator
// using the try statement either. A more trivial implementation would use await foreach
// and protect the entire loop using a try statement, with two breaking changes:
//
// - Also protecting the call to GetAsyncEnumerator by the try statement.
// - Invocation of the handler after disposal of the failed first sequence.
var err = default(IAsyncEnumerable);
await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
{
while (true)
{
TSource c;
try
{
if (!await e.MoveNextAsync())
break;
c = e.Current;
}
catch (TException ex)
{
err = await handler(ex, cancellationToken).ConfigureAwait(false);
break;
}
yield return c;
}
}
if (err != null)
{
await foreach (var item in err.WithCancellation(cancellationToken).ConfigureAwait(false))
{
yield return item;
}
}
}
}
#endif
///
/// Continues an async-enumerable sequence that is terminated by an exception with the next async-enumerable sequence.
///
/// The type of the elements in the source and handler sequences.
/// Observable sequences to catch exceptions for.
/// An async-enumerable sequence containing elements from consecutive source sequences until a source sequence terminates successfully.
/// is null.
public static IAsyncEnumerable Catch(this IEnumerable> sources)
{
if (sources == null)
throw Error.ArgumentNull(nameof(sources));
return CatchCore(sources);
}
///
/// Continues an async-enumerable sequence that is terminated by an exception with the next async-enumerable sequence.
///
/// The type of the elements in the source and handler sequences.
/// Observable sequences to catch exceptions for.
/// An async-enumerable sequence containing elements from consecutive source sequences until a source sequence terminates successfully.
/// is null.
public static IAsyncEnumerable Catch(params IAsyncEnumerable[] sources)
{
if (sources == null)
throw Error.ArgumentNull(nameof(sources));
return CatchCore(sources);
}
///
/// Continues an async-enumerable sequence that is terminated by an exception with the next async-enumerable sequence.
///
/// The type of the elements in the source sequence and handler sequence.
/// First async-enumerable sequence whose exception (if any) is caught.
/// Second async-enumerable sequence used to produce results when an error occurred in the first sequence.
/// An async-enumerable sequence containing the first sequence's elements, followed by the elements of the second sequence in case an exception occurred.
/// or is null.
public static IAsyncEnumerable Catch(this IAsyncEnumerable first, IAsyncEnumerable second)
{
if (first == null)
throw Error.ArgumentNull(nameof(first));
if (second == null)
throw Error.ArgumentNull(nameof(second));
return CatchCore(new[] { first, second });
}
private static async IAsyncEnumerable CatchCore(IEnumerable> sources, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var error = default(ExceptionDispatchInfo);
foreach (var source in sources)
{
await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false);
error = null;
while (true)
{
TSource c;
try
{
if (!await e.MoveNextAsync())
break;
c = e.Current;
}
catch (Exception ex)
{
error = ExceptionDispatchInfo.Capture(ex);
break;
}
yield return c;
}
if (error == null)
break;
}
error?.Throw();
}
}
}