#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
// 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
{
<#
foreach (var m in new[] { "Max", "Min" })
{
var comparison = m == "Max" ? ">" : "<";
var extremum = m == "Max" ? "maximum" : "minimum";
foreach (var t in new[] { "int", "int?", "long", "long?", "float", "float?", "double", "double?", "decimal", "decimal?" })
{
var isFloatingPoint = t.StartsWith("float") || t.StartsWith("double");
var isInteger = t.StartsWith("int") || t.StartsWith("long");
var isNullable = t.EndsWith("?");
var shortCircuit = t.StartsWith("decimal");
var typeStr = t;
if (isNullable) {
typeStr = "Nullable{" + t.Substring(0, 1).ToUpper() + t.Substring(1, t.Length - 2) + "}";
}
#>
///
/// Returns the <#=extremum#> value in an async-enumerable sequence of values.
///
/// A sequence of values to determine the <#=extremum#> value of.
/// The optional cancellation token to be used for cancelling the sequence at any time.
/// A ValueTask containing a single element with the <#=extremum#> value in the source sequence.
/// is null.
public static ValueTask<<#=t#>> <#=m#>Async(this IAsyncEnumerable<<#=t#>> source, CancellationToken cancellationToken = default)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
return Core(source, cancellationToken);
static async ValueTask<<#=t#>> Core(IAsyncEnumerable<<#=t#>> source, CancellationToken cancellationToken)
{
<#
if (!isNullable)
{
#>
<#=t#> value;
await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
{
if (!await e.MoveNextAsync())
{
throw Error.NoElements();
}
value = e.Current;
<#
if (isFloatingPoint && m == "Max")
{
#>
// NaN is ordered less than all other values. We need to do explicit checks
// to ensure this, but once we've found a value that is not NaN we need no
// longer worry about it, so first loop until such a value is found (or not,
// as the case may be).
while (<#=t#>.IsNaN(value))
{
if (!await e.MoveNextAsync())
{
return value;
}
value = e.Current;
}
<#
}
#>
while (await e.MoveNextAsync())
{
var x = e.Current;
if (x <#=comparison#> value)
{
value = x;
}
<#
if (isFloatingPoint && m == "Min")
{
#>
else
{
// Normally NaN < anything is false, as is anything < NaN
// However, this leads to some irksome outcomes in Min and Max.
// If we use those semantics then Min(NaN, 5.0) is NaN, but
// Min(5.0, NaN) is 5.0! To fix this, we impose a total
// ordering where NaN is smaller than every value, including
// negative infinity.
// Not testing for NaN therefore isn't an option, but since we
// can't find a smaller value, we can short-circuit.
if (<#=t#>.IsNaN(x))
{
return x;
}
}
<#
}
#>
}
}
return value;
<#
}
else
{
#>
<#=t#> value = null;
await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
{
// Start off knowing that we've a non-null value (or exit here, knowing we don't)
// so we don't have to keep testing for nullity.
do
{
if (!await e.MoveNextAsync())
{
return value;
}
value = e.Current;
}
while (!value.HasValue);
// Keep hold of the wrapped value, and do comparisons on that, rather than
// using the lifted operation each time.
var valueVal = value.GetValueOrDefault();
<#
if (isInteger && m == "Max")
{
#>
if (valueVal >= 0)
{
// We can fast-path this case where we know HasValue will
// never affect the outcome, without constantly checking
// if we're in such a state. Similar fast-paths could
// be done for other cases, but as all-positive or mostly-
// positive integer values are quite common in real-world
// uses, it's only been done for int? and long?.
while (await e.MoveNextAsync())
{
var cur = e.Current;
var x = cur.GetValueOrDefault();
if (x <#=comparison#> valueVal)
{
valueVal = x;
value = cur;
}
}
}
else
{
while (await e.MoveNextAsync())
{
var cur = e.Current;
var x = cur.GetValueOrDefault();
// Do not replace & with &&. The branch prediction cost outweighs the extra operation
// unless nulls either never happen or always happen.
if (cur.HasValue & x <#=comparison#> valueVal)
{
valueVal = x;
value = cur;
}
}
}
<#
}
else if (isFloatingPoint && m == "Min")
{
#>
while (await e.MoveNextAsync())
{
var cur = e.Current;
if (cur.HasValue)
{
var x = cur.GetValueOrDefault();
if (x <#=comparison#> valueVal)
{
valueVal = x;
value = cur;
}
else
{
// Normally NaN < anything is false, as is anything < NaN
// However, this leads to some irksome outcomes in Min and Max.
// If we use those semantics then Min(NaN, 5.0) is NaN, but
// Min(5.0, NaN) is 5.0! To fix this, we impose a total
// ordering where NaN is smaller than every value, including
// negative infinity.
// Not testing for NaN therefore isn't an option, but since we
// can't find a smaller value, we can short-circuit.
if (<#=t.TrimEnd('?')#>.IsNaN(x))
{
return cur;
}
}
}
}
<#
}
else
{
if (isFloatingPoint && m == "Max")
{
#>
// NaN is ordered less than all other values. We need to do explicit checks
// to ensure this, but once we've found a value that is not NaN we need no
// longer worry about it, so first loop until such a value is found (or not,
// as the case may be).
while (<#=t.TrimEnd('?')#>.IsNaN(valueVal))
{
if (!await e.MoveNextAsync())
{
return value;
}
var cur = e.Current;
if (cur.HasValue)
{
valueVal = (value = cur).GetValueOrDefault();
}
}
<#
}
#>
while (await e.MoveNextAsync())
{
var cur = e.Current;
var x = cur.GetValueOrDefault();
<#
if (shortCircuit)
{
#>
if (cur.HasValue && x <#=comparison#> valueVal)
<#
}
else
{
#>
// Do not replace & with &&. The branch prediction cost outweighs the extra operation
// unless nulls either never happen or always happen.
if (cur.HasValue & x <#=comparison#> valueVal)
<#
}
#>
{
valueVal = x;
value = cur;
}
}
<#
}
#>
}
return value;
<#
}
#>
}
}
<#
foreach (var overload in new[] {
new { selector = "Func", invoke = "selector(e.Current)" },
new { selector = "Func>", invoke = "await selector(e.Current).ConfigureAwait(false)" },
new { selector = "Func>", invoke = "await selector(e.Current, cancellationToken).ConfigureAwait(false)" },
})
{
var isAsync = overload.invoke.StartsWith("await");
var isDeepCancellation = overload.selector.Contains("CancellationToken");
var suffix = isAsync ? "Await" : "";
var visibility = isAsync ? "internal" : "public";
var core = isAsync ? "Core" : "";
if (isDeepCancellation)
{
suffix += "WithCancellation";
#>
#if !NO_DEEP_CANCELLATION
<#
}
#>
<#=visibility#> static ValueTask<<#=t#>> <#=m#><#=suffix#>Async<#=core#>(this IAsyncEnumerable source, <#=overload.selector#> selector, CancellationToken cancellationToken = default)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (selector == null)
throw Error.ArgumentNull(nameof(selector));
return Core(source, selector, cancellationToken);
static async ValueTask<<#=t#>> Core(IAsyncEnumerable source, <#=overload.selector#> selector, CancellationToken cancellationToken)
{
<#
if (!isNullable)
{
#>
<#=t#> value;
await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
{
if (!await e.MoveNextAsync())
{
throw Error.NoElements();
}
value = <#=overload.invoke#>;
<#
if (isFloatingPoint && m == "Max")
{
#>
// NaN is ordered less than all other values. We need to do explicit checks
// to ensure this, but once we've found a value that is not NaN we need no
// longer worry about it, so first loop until such a value is found (or not,
// as the case may be).
while (<#=t#>.IsNaN(value))
{
if (!await e.MoveNextAsync())
{
return value;
}
value = <#=overload.invoke#>;
}
<#
}
#>
while (await e.MoveNextAsync())
{
var x = <#=overload.invoke#>;
if (x <#=comparison#> value)
{
value = x;
}
<#
if (isFloatingPoint && m == "Min")
{
#>
else
{
// Normally NaN < anything is false, as is anything < NaN
// However, this leads to some irksome outcomes in Min and Max.
// If we use those semantics then Min(NaN, 5.0) is NaN, but
// Min(5.0, NaN) is 5.0! To fix this, we impose a total
// ordering where NaN is smaller than every value, including
// negative infinity.
// Not testing for NaN therefore isn't an option, but since we
// can't find a smaller value, we can short-circuit.
if (<#=t#>.IsNaN(x))
{
return x;
}
}
<#
}
#>
}
}
return value;
<#
}
else
{
#>
<#=t#> value = null;
await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
{
// Start off knowing that we've a non-null value (or exit here, knowing we don't)
// so we don't have to keep testing for nullity.
do
{
if (!await e.MoveNextAsync())
{
return value;
}
value = <#=overload.invoke#>;
}
while (!value.HasValue);
// Keep hold of the wrapped value, and do comparisons on that, rather than
// using the lifted operation each time.
var valueVal = value.GetValueOrDefault();
<#
if (isInteger && m == "Max")
{
#>
if (valueVal >= 0)
{
// We can fast-path this case where we know HasValue will
// never affect the outcome, without constantly checking
// if we're in such a state. Similar fast-paths could
// be done for other cases, but as all-positive or mostly-
// positive integer values are quite common in real-world
// uses, it's only been done for int? and long?.
while (await e.MoveNextAsync())
{
var cur = <#=overload.invoke#>;
var x = cur.GetValueOrDefault();
if (x <#=comparison#> valueVal)
{
valueVal = x;
value = cur;
}
}
}
else
{
while (await e.MoveNextAsync())
{
var cur = <#=overload.invoke#>;
var x = cur.GetValueOrDefault();
// Do not replace & with &&. The branch prediction cost outweighs the extra operation
// unless nulls either never happen or always happen.
if (cur.HasValue & x <#=comparison#> valueVal)
{
valueVal = x;
value = cur;
}
}
}
<#
}
else if (isFloatingPoint && m == "Min")
{
#>
while (await e.MoveNextAsync())
{
var cur = <#=overload.invoke#>;
if (cur.HasValue)
{
var x = cur.GetValueOrDefault();
if (x <#=comparison#> valueVal)
{
valueVal = x;
value = cur;
}
else
{
// Normally NaN < anything is false, as is anything < NaN
// However, this leads to some irksome outcomes in Min and Max.
// If we use those semantics then Min(NaN, 5.0) is NaN, but
// Min(5.0, NaN) is 5.0! To fix this, we impose a total
// ordering where NaN is smaller than every value, including
// negative infinity.
// Not testing for NaN therefore isn't an option, but since we
// can't find a smaller value, we can short-circuit.
if (<#=t.TrimEnd('?')#>.IsNaN(x))
{
return cur;
}
}
}
}
<#
}
else
{
if (isFloatingPoint && m == "Max")
{
#>
// NaN is ordered less than all other values. We need to do explicit checks
// to ensure this, but once we've found a value that is not NaN we need no
// longer worry about it, so first loop until such a value is found (or not,
// as the case may be).
while (<#=t.TrimEnd('?')#>.IsNaN(valueVal))
{
if (!await e.MoveNextAsync())
{
return value;
}
var cur = <#=overload.invoke#>;
if (cur.HasValue)
{
valueVal = (value = cur).GetValueOrDefault();
}
}
<#
}
#>
while (await e.MoveNextAsync())
{
var cur = <#=overload.invoke#>;
var x = cur.GetValueOrDefault();
<#
if (shortCircuit)
{
#>
if (cur.HasValue && x <#=comparison#> valueVal)
<#
}
else
{
#>
// Do not replace & with &&. The branch prediction cost outweighs the extra operation
// unless nulls either never happen or always happen.
if (cur.HasValue & x <#=comparison#> valueVal)
<#
}
#>
{
valueVal = x;
value = cur;
}
}
<#
}
#>
}
return value;
<#
}
#>
}
}
<#
if (isDeepCancellation)
{
#>
#endif
<#
}
}
#>
<#
}
}
#>
}
}