Browse Source

Replace Min and Max implementations.

Bart De Smet 7 years ago
parent
commit
bb54d8d00c

+ 208 - 0
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Max.Generic.cs

@@ -0,0 +1,208 @@
+// 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
+    {
+        private static async Task<TSource> MaxCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
+        {
+            var comparer = Comparer<TSource>.Default;
+            var value = default(TSource);
+            if (value == null)
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    do
+                    {
+                        if (!await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            return value;
+                        }
+
+                        value = e.Current;
+                    }
+                    while (value == null);
+
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = e.Current;
+                        if (x != null && comparer.Compare(x, value) > 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+            else
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                    }
+
+                    value = e.Current;
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = e.Current;
+                        if (comparer.Compare(x, value) > 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+
+            return value;
+        }
+
+        private static async Task<TResult> MaxCore<TSource, TResult>(IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector, CancellationToken cancellationToken)
+        {
+            var comparer = Comparer<TResult>.Default;
+            var value = default(TResult);
+            if (value == null)
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    do
+                    {
+                        if (!await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            return value;
+                        }
+
+                        value = selector(e.Current);
+                    }
+                    while (value == null);
+
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = selector(e.Current);
+                        if (x != null && comparer.Compare(x, value) > 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+            else
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                    }
+
+                    value = selector(e.Current);
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = selector(e.Current);
+                        if (comparer.Compare(x, value) > 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+
+            return value;
+        }
+
+        private static async Task<TResult> MaxCore<TSource, TResult>(IAsyncEnumerable<TSource> source, Func<TSource, Task<TResult>> selector, CancellationToken cancellationToken)
+        {
+            var comparer = Comparer<TResult>.Default;
+            var value = default(TResult);
+            if (value == null)
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    do
+                    {
+                        if (!await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            return value;
+                        }
+
+                        value = await selector(e.Current).ConfigureAwait(false);
+                    }
+                    while (value == null);
+
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = await selector(e.Current).ConfigureAwait(false);
+                        if (x != null && comparer.Compare(x, value) > 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+            else
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = await selector(e.Current).ConfigureAwait(false);
+                        if (comparer.Compare(x, value) > 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+
+            return value;
+        }
+    }
+}

+ 1372 - 0
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Max.Primitive.cs

@@ -0,0 +1,1372 @@
+// 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
+    {
+        private static async Task<int> MaxCore(IAsyncEnumerable<int> source, CancellationToken cancellationToken)
+        {
+            int value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = e.Current;
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = e.Current;
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<int?> MaxCore(IAsyncEnumerable<int?> source, CancellationToken cancellationToken)
+        {
+            int? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                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 in this direction for int? and long?.
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var cur = e.Current;
+                        var x = cur.GetValueOrDefault();
+                        if (x > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+                else
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        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 > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long> MaxCore(IAsyncEnumerable<long> source, CancellationToken cancellationToken)
+        {
+            long value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = e.Current;
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = e.Current;
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long?> MaxCore(IAsyncEnumerable<long?> source, CancellationToken cancellationToken)
+        {
+            long? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                if (valueVal >= 0)
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var cur = e.Current;
+                        var x = cur.GetValueOrDefault();
+                        if (x > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+                else
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        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 > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double> MaxCore(IAsyncEnumerable<double> source, CancellationToken cancellationToken)
+        {
+            double value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = e.Current;
+
+                // As described in a comment on Min(IAsyncEnumerable<double>) 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 (double.IsNaN(value))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = e.Current;
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double?> MaxCore(IAsyncEnumerable<double?> source, CancellationToken cancellationToken)
+        {
+            double? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (double.IsNaN(valueVal))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    var cur = e.Current;
+                    if (cur.HasValue)
+                    {
+                        valueVal = (value = cur).GetValueOrDefault();
+                    }
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    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 > valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float> MaxCore(IAsyncEnumerable<float> source, CancellationToken cancellationToken)
+        {
+            float value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = e.Current;
+                while (float.IsNaN(value))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = e.Current;
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float?> MaxCore(IAsyncEnumerable<float?> source, CancellationToken cancellationToken)
+        {
+            float? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (float.IsNaN(valueVal))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    var cur = e.Current;
+                    if (cur.HasValue)
+                    {
+                        valueVal = (value = cur).GetValueOrDefault();
+                    }
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    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 > valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal> MaxCore(IAsyncEnumerable<decimal> source, CancellationToken cancellationToken)
+        {
+            decimal value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = e.Current;
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = e.Current;
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal?> MaxCore(IAsyncEnumerable<decimal?> source, CancellationToken cancellationToken)
+        {
+            decimal? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = e.Current;
+                    var x = cur.GetValueOrDefault();
+                    if (cur.HasValue && x > valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<int> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, int> selector, CancellationToken cancellationToken)
+        {
+            int value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = selector(e.Current);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = selector(e.Current);
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<int?> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, int?> selector, CancellationToken cancellationToken)
+        {
+            int? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                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 in this direction for int? and long?.
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var cur = selector(e.Current);
+                        var x = cur.GetValueOrDefault();
+                        if (x > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+                else
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var cur = selector(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 > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, long> selector, CancellationToken cancellationToken)
+        {
+            long value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = selector(e.Current);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = selector(e.Current);
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long?> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, long?> selector, CancellationToken cancellationToken)
+        {
+            long? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                if (valueVal >= 0)
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var cur = selector(e.Current);
+                        var x = cur.GetValueOrDefault();
+                        if (x > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+                else
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var cur = selector(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 > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, float> selector, CancellationToken cancellationToken)
+        {
+            float value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = selector(e.Current);
+                while (float.IsNaN(value))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = selector(e.Current);
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float?> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, float?> selector, CancellationToken cancellationToken)
+        {
+            float? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (float.IsNaN(valueVal))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    var cur = selector(e.Current);
+                    if (cur.HasValue)
+                    {
+                        valueVal = (value = cur).GetValueOrDefault();
+                    }
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = selector(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 > valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, double> selector, CancellationToken cancellationToken)
+        {
+            double value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = selector(e.Current);
+
+                // As described in a comment on Min(IAsyncEnumerable<double>) 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 (double.IsNaN(value))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = selector(e.Current);
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double?> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, double?> selector, CancellationToken cancellationToken)
+        {
+            double? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (double.IsNaN(valueVal))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    var cur = selector(e.Current);
+                    if (cur.HasValue)
+                    {
+                        valueVal = (value = cur).GetValueOrDefault();
+                    }
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = selector(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 > valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, decimal> selector, CancellationToken cancellationToken)
+        {
+            decimal value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = selector(e.Current);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = selector(e.Current);
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal?> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, decimal?> selector, CancellationToken cancellationToken)
+        {
+            decimal? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = selector(e.Current);
+                    var x = cur.GetValueOrDefault();
+                    if (cur.HasValue && x > valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<int> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<int>> selector, CancellationToken cancellationToken)
+        {
+            int value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = await selector(e.Current).ConfigureAwait(false);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = await selector(e.Current).ConfigureAwait(false);
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<int?> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<int?>> selector, CancellationToken cancellationToken)
+        {
+            int? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                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 in this direction for int? and long?.
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var cur = await selector(e.Current).ConfigureAwait(false);
+                        var x = cur.GetValueOrDefault();
+                        if (x > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+                else
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var cur = await selector(e.Current).ConfigureAwait(false);
+                        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 > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<long>> selector, CancellationToken cancellationToken)
+        {
+            long value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = await selector(e.Current).ConfigureAwait(false);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = await selector(e.Current).ConfigureAwait(false);
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long?> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<long?>> selector, CancellationToken cancellationToken)
+        {
+            long? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                if (valueVal >= 0)
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var cur = await selector(e.Current).ConfigureAwait(false);
+                        var x = cur.GetValueOrDefault();
+                        if (x > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+                else
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var cur = await selector(e.Current).ConfigureAwait(false);
+                        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 > valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<float>> selector, CancellationToken cancellationToken)
+        {
+            float value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = await selector(e.Current).ConfigureAwait(false);
+                while (float.IsNaN(value))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = await selector(e.Current).ConfigureAwait(false);
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float?> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<float?>> selector, CancellationToken cancellationToken)
+        {
+            float? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (float.IsNaN(valueVal))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    var cur = await selector(e.Current).ConfigureAwait(false);
+                    if (cur.HasValue)
+                    {
+                        valueVal = (value = cur).GetValueOrDefault();
+                    }
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = await selector(e.Current).ConfigureAwait(false);
+                    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 > valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<double>> selector, CancellationToken cancellationToken)
+        {
+            double value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = await selector(e.Current).ConfigureAwait(false);
+
+                // As described in a comment on Min(IAsyncEnumerable<double>) 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 (double.IsNaN(value))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = await selector(e.Current).ConfigureAwait(false);
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double?> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<double?>> selector, CancellationToken cancellationToken)
+        {
+            double? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (double.IsNaN(valueVal))
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    var cur = await selector(e.Current).ConfigureAwait(false);
+                    if (cur.HasValue)
+                    {
+                        valueVal = (value = cur).GetValueOrDefault();
+                    }
+                }
+
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = await selector(e.Current).ConfigureAwait(false);
+                    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 > valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<decimal>> selector, CancellationToken cancellationToken)
+        {
+            decimal value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = await selector(e.Current).ConfigureAwait(false);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = await selector(e.Current).ConfigureAwait(false);
+                    if (x > value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal?> MaxCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<decimal?>> selector, CancellationToken cancellationToken)
+        {
+            decimal? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = await selector(e.Current).ConfigureAwait(false);
+                    var x = cur.GetValueOrDefault();
+                    if (cur.HasValue && x > valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+    }
+}

+ 2 - 41
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Max.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
                 throw new ArgumentNullException(nameof(source));
 
 
-            return MaxCore(source, Comparer<TSource>.Default, CancellationToken.None);
+            return MaxCore(source, CancellationToken.None);
         }
         }
 
 
         public static Task<TSource> Max<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
         public static Task<TSource> Max<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -23,7 +23,7 @@ namespace System.Linq
             if (source == null)
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
                 throw new ArgumentNullException(nameof(source));
 
 
-            return MaxCore(source, Comparer<TSource>.Default, cancellationToken);
+            return MaxCore(source, cancellationToken);
         }
         }
 
 
         public static Task<TResult> Max<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector)
         public static Task<TResult> Max<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector)
@@ -65,44 +65,5 @@ namespace System.Linq
 
 
             return MaxCore(source, selector, cancellationToken);
             return MaxCore(source, selector, cancellationToken);
         }
         }
-
-        private static async Task<TSource> MaxCore<TSource>(IAsyncEnumerable<TSource> source, IComparer<TSource> comparer, CancellationToken cancellationToken)
-        {
-            var e = source.GetAsyncEnumerator(cancellationToken);
-
-            try
-            {
-                if (!await e.MoveNextAsync().ConfigureAwait(false))
-                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
-
-                var max = e.Current;
-
-                while (await e.MoveNextAsync().ConfigureAwait(false))
-                {
-                    var cur = e.Current;
-
-                    if (comparer.Compare(cur, max) > 0)
-                    {
-                        max = cur;
-                    }
-                }
-
-                return max;
-            }
-            finally
-            {
-                await e.DisposeAsync().ConfigureAwait(false);
-            }
-        }
-
-        private static Task<TResult> MaxCore<TSource, TResult>(IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector, CancellationToken cancellationToken)
-        {
-            return MaxCore(source.Select(selector), Comparer<TResult>.Default, cancellationToken);
-        }
-
-        private static Task<TResult> MaxCore<TSource, TResult>(IAsyncEnumerable<TSource> source, Func<TSource, Task<TResult>> selector, CancellationToken cancellationToken)
-        {
-            return MaxCore(source.Select(selector), Comparer<TResult>.Default, cancellationToken);
-        }
     }
     }
 }
 }

+ 208 - 0
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Min.Generic.cs

@@ -0,0 +1,208 @@
+// 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
+    {
+        private static async Task<TSource> MinCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
+        {
+            var comparer = Comparer<TSource>.Default;
+            var value = default(TSource);
+            if (value == null)
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    do
+                    {
+                        if (!await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            return value;
+                        }
+
+                        value = e.Current;
+                    }
+                    while (value == null);
+
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = e.Current;
+                        if (x != null && comparer.Compare(x, value) < 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+            else
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                    }
+
+                    value = e.Current;
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = e.Current;
+                        if (comparer.Compare(x, value) < 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+
+            return value;
+        }
+
+        private static async Task<TResult> MinCore<TSource, TResult>(IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector, CancellationToken cancellationToken)
+        {
+            var comparer = Comparer<TResult>.Default;
+            var value = default(TResult);
+            if (value == null)
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    do
+                    {
+                        if (!await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            return value;
+                        }
+
+                        value = selector(e.Current);
+                    }
+                    while (value == null);
+
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = selector(e.Current);
+                        if (x != null && comparer.Compare(x, value) < 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+            else
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                    }
+
+                    value = selector(e.Current);
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = selector(e.Current);
+                        if (comparer.Compare(x, value) < 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+
+            return value;
+        }
+
+        private static async Task<TResult> MinCore<TSource, TResult>(IAsyncEnumerable<TSource> source, Func<TSource, Task<TResult>> selector, CancellationToken cancellationToken)
+        {
+            var comparer = Comparer<TResult>.Default;
+            var value = default(TResult);
+            if (value == null)
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    do
+                    {
+                        if (!await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            return value;
+                        }
+
+                        value = await selector(e.Current).ConfigureAwait(false);
+                    }
+                    while (value == null);
+
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = await selector(e.Current).ConfigureAwait(false);
+                        if (x != null && comparer.Compare(x, value) < 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+            else
+            {
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        var x = await selector(e.Current).ConfigureAwait(false);
+                        if (comparer.Compare(x, value) < 0)
+                        {
+                            value = x;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+            }
+
+            return value;
+        }
+    }
+}

+ 1186 - 0
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Min.Primitive.cs

@@ -0,0 +1,1186 @@
+// 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
+    {
+        private static async Task<int> MinCore(IAsyncEnumerable<int> source, CancellationToken cancellationToken)
+        {
+            int value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = e.Current;
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = e.Current;
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<int?> MinCore(IAsyncEnumerable<int?> source, CancellationToken cancellationToken)
+        {
+            int? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                // 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().ConfigureAwait(false))
+                    {
+                        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();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    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 < valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long> MinCore(IAsyncEnumerable<long> source, CancellationToken cancellationToken)
+        {
+            long value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = e.Current;
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = e.Current;
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long?> MinCore(IAsyncEnumerable<long?> source, CancellationToken cancellationToken)
+        {
+            long? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    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 < valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float> MinCore(IAsyncEnumerable<float> source, CancellationToken cancellationToken)
+        {
+            float value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = e.Current;
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = e.Current;
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+
+                    // 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.
+                    else if (float.IsNaN(x))
+                    {
+                        return x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float?> MinCore(IAsyncEnumerable<float?> source, CancellationToken cancellationToken)
+        {
+            float? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = e.Current;
+                    if (cur.HasValue)
+                    {
+                        var x = cur.GetValueOrDefault();
+                        if (x < valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                        else if (float.IsNaN(x))
+                        {
+                            return cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double> MinCore(IAsyncEnumerable<double> source, CancellationToken cancellationToken)
+        {
+            double value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = e.Current;
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = e.Current;
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                    else if (double.IsNaN(x))
+                    {
+                        return x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double?> MinCore(IAsyncEnumerable<double?> source, CancellationToken cancellationToken)
+        {
+            double? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = e.Current;
+                    if (cur.HasValue)
+                    {
+                        var x = cur.GetValueOrDefault();
+                        if (x < valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                        else if (double.IsNaN(x))
+                        {
+                            return cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal> MinCore(IAsyncEnumerable<decimal> source, CancellationToken cancellationToken)
+        {
+            decimal value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = e.Current;
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = e.Current;
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal?> MinCore(IAsyncEnumerable<decimal?> source, CancellationToken cancellationToken)
+        {
+            decimal? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = e.Current;
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = e.Current;
+                    var x = cur.GetValueOrDefault();
+                    if (cur.HasValue && x < valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<int> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, int> selector, CancellationToken cancellationToken)
+        {
+            int value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = selector(e.Current);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = selector(e.Current);
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<int?> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, int?> selector, CancellationToken cancellationToken)
+        {
+            int? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                // 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().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(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();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = selector(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 < valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, long> selector, CancellationToken cancellationToken)
+        {
+            long value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = selector(e.Current);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = selector(e.Current);
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long?> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, long?> selector, CancellationToken cancellationToken)
+        {
+            long? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = selector(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 < valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, float> selector, CancellationToken cancellationToken)
+        {
+            float value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = selector(e.Current);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = selector(e.Current);
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+
+                    // 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.
+                    else if (float.IsNaN(x))
+                    {
+                        return x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float?> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, float?> selector, CancellationToken cancellationToken)
+        {
+            float? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = selector(e.Current);
+                    if (cur.HasValue)
+                    {
+                        var x = cur.GetValueOrDefault();
+                        if (x < valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                        else if (float.IsNaN(x))
+                        {
+                            return cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, double> selector, CancellationToken cancellationToken)
+        {
+            double value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = selector(e.Current);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = selector(e.Current);
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                    else if (double.IsNaN(x))
+                    {
+                        return x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double?> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, double?> selector, CancellationToken cancellationToken)
+        {
+            double? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = selector(e.Current);
+                    if (cur.HasValue)
+                    {
+                        var x = cur.GetValueOrDefault();
+                        if (x < valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                        else if (double.IsNaN(x))
+                        {
+                            return cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, decimal> selector, CancellationToken cancellationToken)
+        {
+            decimal value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = selector(e.Current);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = selector(e.Current);
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal?> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, decimal?> selector, CancellationToken cancellationToken)
+        {
+            decimal? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = selector(e.Current);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = selector(e.Current);
+                    var x = cur.GetValueOrDefault();
+                    if (cur.HasValue && x < valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<int> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<int>> selector, CancellationToken cancellationToken)
+        {
+            int value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = await selector(e.Current).ConfigureAwait(false);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = await selector(e.Current).ConfigureAwait(false);
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<int?> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<int?>> selector, CancellationToken cancellationToken)
+        {
+            int? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                // 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().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+                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();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = await selector(e.Current).ConfigureAwait(false);
+                    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 < valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<long>> selector, CancellationToken cancellationToken)
+        {
+            long value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = await selector(e.Current).ConfigureAwait(false);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = await selector(e.Current).ConfigureAwait(false);
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<long?> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<long?>> selector, CancellationToken cancellationToken)
+        {
+            long? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = await selector(e.Current).ConfigureAwait(false);
+                    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 < valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<float>> selector, CancellationToken cancellationToken)
+        {
+            float value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = await selector(e.Current).ConfigureAwait(false);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = await selector(e.Current).ConfigureAwait(false);
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+
+                    // 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.
+                    else if (float.IsNaN(x))
+                    {
+                        return x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<float?> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<float?>> selector, CancellationToken cancellationToken)
+        {
+            float? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = await selector(e.Current).ConfigureAwait(false);
+                    if (cur.HasValue)
+                    {
+                        var x = cur.GetValueOrDefault();
+                        if (x < valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                        else if (float.IsNaN(x))
+                        {
+                            return cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<double>> selector, CancellationToken cancellationToken)
+        {
+            double value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = await selector(e.Current).ConfigureAwait(false);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = await selector(e.Current).ConfigureAwait(false);
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                    else if (double.IsNaN(x))
+                    {
+                        return x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<double?> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<double?>> selector, CancellationToken cancellationToken)
+        {
+            double? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = await selector(e.Current).ConfigureAwait(false);
+                    if (cur.HasValue)
+                    {
+                        var x = cur.GetValueOrDefault();
+                        if (x < valueVal)
+                        {
+                            valueVal = x;
+                            value = cur;
+                        }
+                        else if (double.IsNaN(x))
+                        {
+                            return cur;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<decimal>> selector, CancellationToken cancellationToken)
+        {
+            decimal value;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                if (!await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
+                }
+
+                value = await selector(e.Current).ConfigureAwait(false);
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var x = await selector(e.Current).ConfigureAwait(false);
+                    if (x < value)
+                    {
+                        value = x;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+
+        private static async Task<decimal?> MinCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<decimal?>> selector, CancellationToken cancellationToken)
+        {
+            decimal? value = null;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                do
+                {
+                    if (!await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        return value;
+                    }
+
+                    value = await selector(e.Current).ConfigureAwait(false);
+                }
+                while (!value.HasValue);
+
+                var valueVal = value.GetValueOrDefault();
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var cur = await selector(e.Current).ConfigureAwait(false);
+                    var x = cur.GetValueOrDefault();
+                    if (cur.HasValue && x < valueVal)
+                    {
+                        valueVal = x;
+                        value = cur;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return value;
+        }
+    }
+}

+ 2 - 41
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Min.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
                 throw new ArgumentNullException(nameof(source));
 
 
-            return MinCore(source, Comparer<TSource>.Default, CancellationToken.None);
+            return MinCore(source, CancellationToken.None);
         }
         }
 
 
         public static Task<TSource> Min<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
         public static Task<TSource> Min<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -23,7 +23,7 @@ namespace System.Linq
             if (source == null)
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
                 throw new ArgumentNullException(nameof(source));
 
 
-            return MinCore(source, Comparer<TSource>.Default, cancellationToken);
+            return MinCore(source, cancellationToken);
         }
         }
 
 
         public static Task<TResult> Min<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector)
         public static Task<TResult> Min<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector)
@@ -65,44 +65,5 @@ namespace System.Linq
 
 
             return MinCore(source, selector, cancellationToken);
             return MinCore(source, selector, cancellationToken);
         }
         }
-
-        private static async Task<TSource> MinCore<TSource>(IAsyncEnumerable<TSource> source, IComparer<TSource> comparer, CancellationToken cancellationToken)
-        {
-            var e = source.GetAsyncEnumerator(cancellationToken);
-
-            try
-            {
-                if (!await e.MoveNextAsync().ConfigureAwait(false))
-                    throw new InvalidOperationException(Strings.NO_ELEMENTS);
-
-                var min = e.Current;
-
-                while (await e.MoveNextAsync().ConfigureAwait(false))
-                {
-                    var cur = e.Current;
-
-                    if (comparer.Compare(cur, min) < 0)
-                    {
-                        min = cur;
-                    }
-                }
-
-                return min;
-            }
-            finally
-            {
-                await e.DisposeAsync().ConfigureAwait(false);
-            }
-        }
-
-        private static Task<TResult> MinCore<TSource, TResult>(IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector, CancellationToken cancellationToken)
-        {
-            return MinCore(source.Select(selector), Comparer<TResult>.Default, cancellationToken);
-        }
-
-        private static Task<TResult> MinCore<TSource, TResult>(IAsyncEnumerable<TSource> source, Func<TSource, Task<TResult>> selector, CancellationToken cancellationToken)
-        {
-            return MinCore(source.Select(selector), Comparer<TResult>.Default, cancellationToken);
-        }
     }
     }
 }
 }

File diff suppressed because it is too large
+ 212 - 212
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/MinMax.Generated.cs


+ 9 - 73
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/MinMax.Generated.tt

@@ -5,8 +5,8 @@
 <#@ import namespace="System.Collections.Generic" #>
 <#@ import namespace="System.Collections.Generic" #>
 <#@ output extension=".cs" #>
 <#@ output extension=".cs" #>
 // Licensed to the .NET Foundation under one or more agreements.
 // 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. 
+// 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.Collections.Generic;
 using System.Threading;
 using System.Threading;
@@ -17,45 +17,17 @@ namespace System.Linq
     public static partial class AsyncEnumerable
     public static partial class AsyncEnumerable
     {
     {
 <#
 <#
-var ts = new[]
-{
-    "int",
-    "long",
-    "float",
-    "double",
-    "decimal",
-    "int?",
-    "long?",
-    "float?",
-    "double?",
-    "decimal?",
-};
-
 foreach (var m in new[] { "Max", "Min" })
 foreach (var m in new[] { "Max", "Min" })
 {
 {
-    foreach (var t in ts)
+    foreach (var t in new[] { "int", "int?", "long", "long?", "float", "float?", "double", "double?", "decimal", "decimal?" })
     {
     {
-        var n = t.EndsWith("?");
 #>
 #>
         public static Task<<#=t#>> <#=m#>(this IAsyncEnumerable<<#=t#>> source)
         public static Task<<#=t#>> <#=m#>(this IAsyncEnumerable<<#=t#>> source)
         {
         {
             if (source == null)
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
                 throw new ArgumentNullException(nameof(source));
 
 
-<#
-if (n)
-{
-#>
-            return source.Aggregate(default(<#=t#>), new Func<<#=t#>, <#=t#>, <#=t#>>(Nullable<#=m#>), CancellationToken.None);
-<#
-}
-else
-{
-#>
-            return source.Aggregate(new Func<<#=t#>, <#=t#>, <#=t#>>(Math.<#=m#>), CancellationToken.None);
-<#
-}
-#>
+            return <#=m#>Core(source, CancellationToken.None);
         }
         }
 
 
         public static Task<<#=t#>> <#=m#>(this IAsyncEnumerable<<#=t#>> source, CancellationToken cancellationToken)
         public static Task<<#=t#>> <#=m#>(this IAsyncEnumerable<<#=t#>> source, CancellationToken cancellationToken)
@@ -63,20 +35,7 @@ else
             if (source == null)
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
                 throw new ArgumentNullException(nameof(source));
 
 
-<#
-if (n)
-{
-#>
-            return source.Aggregate(default(<#=t#>), new Func<<#=t#>, <#=t#>, <#=t#>>(Nullable<#=m#>), cancellationToken);
-<#
-}
-else
-{
-#>
-            return source.Aggregate(new Func<<#=t#>, <#=t#>, <#=t#>>(Math.<#=m#>), cancellationToken);
-<#
-}
-#>
+            return <#=m#>Core(source, cancellationToken);
         }
         }
 
 
         public static Task<<#=t#>> <#=m#><TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, <#=t#>> selector)
         public static Task<<#=t#>> <#=m#><TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, <#=t#>> selector)
@@ -86,7 +45,7 @@ else
             if (selector == null)
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
                 throw new ArgumentNullException(nameof(selector));
 
 
-            return source.Select(selector).<#=m#>(CancellationToken.None);
+            return <#=m#>Core(source, selector, CancellationToken.None);
         }
         }
 
 
         public static Task<<#=t#>> <#=m#><TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, <#=t#>> selector, CancellationToken cancellationToken)
         public static Task<<#=t#>> <#=m#><TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, <#=t#>> selector, CancellationToken cancellationToken)
@@ -96,7 +55,7 @@ else
             if (selector == null)
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
                 throw new ArgumentNullException(nameof(selector));
 
 
-            return source.Select(selector).<#=m#>(cancellationToken);
+            return <#=m#>Core(source, selector, cancellationToken);
         }
         }
 
 
         public static Task<<#=t#>> <#=m#><TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<<#=t#>>> selector)
         public static Task<<#=t#>> <#=m#><TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<<#=t#>>> selector)
@@ -106,7 +65,7 @@ else
             if (selector == null)
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
                 throw new ArgumentNullException(nameof(selector));
 
 
-            return source.Select(selector).<#=m#>(CancellationToken.None);
+            return <#=m#>Core(source, selector, CancellationToken.None);
         }
         }
 
 
         public static Task<<#=t#>> <#=m#><TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<<#=t#>>> selector, CancellationToken cancellationToken)
         public static Task<<#=t#>> <#=m#><TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<<#=t#>>> selector, CancellationToken cancellationToken)
@@ -116,35 +75,12 @@ else
             if (selector == null)
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
                 throw new ArgumentNullException(nameof(selector));
 
 
-            return source.Select(selector).<#=m#>(cancellationToken);
+            return <#=m#>Core(source, selector, cancellationToken);
         }
         }
 
 
 <#
 <#
     }
     }
 }
 }
 #>
 #>
-        private static T? NullableMax<T>(T? x, T? y)
-            where T : struct, IComparable<T>
-        {
-            if (!x.HasValue)
-                return y;
-            if (!y.HasValue)
-                return x;
-            if (x.Value.CompareTo(y.Value) >= 0)
-                return x;
-            return y;
-        }
-
-        private static T? NullableMin<T>(T? x, T? y)
-            where T : struct, IComparable<T>
-        {
-            if (!x.HasValue)
-                return y;
-            if (!y.HasValue)
-                return x;
-            if (x.Value.CompareTo(y.Value) <= 0)
-                return x;
-            return y;
-        }
     }
     }
 }
 }

Some files were not shown because too many files changed in this diff