浏览代码

Merge branch 'IxAsyncCSharp8' into AsyncIxCS8FixesPart2

Bart J.F. De Smet 7 年之前
父节点
当前提交
9f3d78e9fd
共有 41 个文件被更改,包括 4713 次插入695 次删除
  1. 3 0
      .gitignore
  2. 5 1
      Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/Throw.cs
  3. 1 1
      Ix.NET/Source/System.Linq.Async.Tests/System/Linq/AsyncEnumerableTests.cs
  4. 2 0
      Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/CreateEnumerable.cs
  5. 4 4
      Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/ToObservable.cs
  6. 1 1
      Ix.NET/Source/System.Linq.Async/System/Linq/AsyncEnumerable.cs
  7. 1 1
      Ix.NET/Source/System.Linq.Async/System/Linq/AsyncEnumerator.cs
  8. 1 1
      Ix.NET/Source/System.Linq.Async/System/Linq/Maybe.cs
  9. 29 8
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Aggregate.cs
  10. 2 2
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/All.cs
  11. 9 4
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Any.cs
  12. 5 6
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/AppendPrepend.cs
  13. 10 10
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Average.cs
  14. 55 21
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Contains.cs
  15. 108 24
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Count.cs
  16. 1 1
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/DefaultIfEmpty.cs
  17. 1 1
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Distinct.cs
  18. 1 1
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ElementAt.cs
  19. 1 1
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ElementAtOrDefault.cs
  20. 22 39
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/First.cs
  21. 89 18
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/FirstOrDefault.cs
  22. 22 49
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Last.cs
  23. 108 24
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/LastOrDefault.cs
  24. 92 14
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/LongCount.cs
  25. 208 0
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Max.Generic.cs
  26. 1372 0
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Max.Primitive.cs
  27. 6 36
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Max.cs
  28. 208 0
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Min.Generic.cs
  29. 1186 0
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Min.Primitive.cs
  30. 6 36
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Min.cs
  31. 212 212
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/MinMax.Generated.cs
  32. 9 73
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/MinMax.Generated.tt
  33. 69 5
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Single.cs
  34. 70 5
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SingleOrDefault.cs
  35. 620 48
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Sum.Generated.cs
  36. 131 16
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Sum.Generated.tt
  37. 7 2
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ToAsyncEnumerable.cs
  38. 33 28
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ToObservable.cs
  39. 1 1
      Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Union.cs
  40. 1 1
      Ix.NET/Source/System.Linq.Async/System/Linq/Set.cs
  41. 1 0
      azure-pipelines.ix.yml

+ 3 - 0
.gitignore

@@ -185,3 +185,6 @@ nuget.exe
 
 # dotnet local cache
 .dotnet
+
+# JetBrains Rider adds these
+.idea/

+ 5 - 1
Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/Throw.cs

@@ -23,8 +23,12 @@ namespace System.Linq
             var moveNextThrows = new ValueTask<bool>(Task.FromException<bool>(exception));
 #endif
 
+            //
+            // REVIEW: Honor cancellation using conditional expression in MoveNextAsync?
+            //
+
             return AsyncEnumerable.CreateEnumerable(
-                () => AsyncEnumerable.CreateEnumerator<TValue>(
+                _ => AsyncEnumerable.CreateEnumerator<TValue>(
                     () => moveNextThrows,
                     current: null,
                     dispose: null)

+ 1 - 1
Ix.NET/Source/System.Linq.Async.Tests/System/Linq/AsyncEnumerableTests.cs

@@ -94,7 +94,7 @@ namespace Tests
 #endif
 
             return AsyncEnumerable.CreateEnumerable(
-                () => AsyncEnumerable.CreateEnumerator<TValue>(
+                _ => AsyncEnumerable.CreateEnumerator<TValue>(
                     () => moveNextThrows,
                     current: null,
                     dispose: null)

+ 2 - 0
Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/CreateEnumerable.cs

@@ -5,6 +5,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading;
 using Xunit;
 
 namespace Tests
@@ -15,6 +16,7 @@ namespace Tests
         public void CreateEnumerable_Null()
         {
             AssertThrows<ArgumentNullException>(() => AsyncEnumerable.CreateEnumerable<int>(default(Func<IAsyncEnumerator<int>>)));
+            AssertThrows<ArgumentNullException>(() => AsyncEnumerable.CreateEnumerable<int>(default(Func<CancellationToken, IAsyncEnumerator<int>>)));
         }
     }
 }

+ 4 - 4
Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/ToObservable.cs

@@ -132,7 +132,7 @@ namespace Tests
 
             evt.WaitOne();
             Assert.False(fail);
-            Assert.Equal(ex1, ((AggregateException)ex_).InnerExceptions.Single());
+            Assert.Equal(ex1, ex_);
         }
 
         [Fact]
@@ -142,7 +142,7 @@ namespace Tests
             var evt = new ManualResetEvent(false);
 
             var ae = AsyncEnumerable.CreateEnumerable(
-                () => AsyncEnumerable.CreateEnumerator<int>(
+                _ => AsyncEnumerable.CreateEnumerator<int>(
                     () => TaskExt.False,
                     () => { throw new InvalidOperationException(); },
                     () => { evt.Set(); return TaskExt.CompletedTask; }));
@@ -176,7 +176,7 @@ namespace Tests
             var subscriptionAssignedTcs = new TaskCompletionSource<object>();
 
             var ae = AsyncEnumerable.CreateEnumerable(
-                () => AsyncEnumerable.CreateEnumerator(
+                _ => AsyncEnumerable.CreateEnumerator(
                     async () =>
                     {
                         await subscriptionAssignedTcs.Task;
@@ -222,7 +222,7 @@ namespace Tests
             var subscriptionAssignedTcs = new TaskCompletionSource<object>();
 
             var ae = AsyncEnumerable.CreateEnumerable(
-                () => AsyncEnumerable.CreateEnumerator(
+                _ => AsyncEnumerable.CreateEnumerator(
                     async () =>
                     {
                         await subscriptionAssignedTcs.Task;

+ 1 - 1
Ix.NET/Source/System.Linq.Async/System/Linq/AsyncEnumerable.cs

@@ -31,7 +31,7 @@ namespace System.Linq
 #endif
 
             return CreateEnumerable(
-                () => CreateEnumerator<TValue>(
+                _ => CreateEnumerator<TValue>(
                     () => moveNextThrows,
                     current: null,
                     dispose: null)

+ 1 - 1
Ix.NET/Source/System.Linq.Async/System/Linq/AsyncEnumerator.cs

@@ -87,7 +87,7 @@ namespace System.Collections.Generic
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return AsyncEnumerable.CreateEnumerable<T>(() => source);
+            return AsyncEnumerable.CreateEnumerable<T>(_ => source);
         }
 
         internal static IAsyncEnumerator<T> Create<T>(Func<TaskCompletionSource<bool>, ValueTask<bool>> moveNext, Func<T> current, Func<ValueTask> dispose)

+ 1 - 1
Ix.NET/Source/System.Linq.Async/System/Linq/Maybe.cs

@@ -10,7 +10,7 @@ namespace System.Linq
     {
         public Maybe(T value)
         {
-            HasValue = false;
+            HasValue = true;
             Value = value;
         }
 

+ 29 - 8
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Aggregate.cs

@@ -17,7 +17,7 @@ namespace System.Linq
             if (accumulator == null)
                 throw new ArgumentNullException(nameof(accumulator));
 
-            return Aggregate(source, accumulator, CancellationToken.None);
+            return AggregateCore(source, accumulator, CancellationToken.None);
         }
 
         public static Task<TSource> Aggregate<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, TSource, TSource> accumulator, CancellationToken cancellationToken)
@@ -37,7 +37,7 @@ namespace System.Linq
             if (accumulator == null)
                 throw new ArgumentNullException(nameof(accumulator));
 
-            return Aggregate(source, accumulator, CancellationToken.None);
+            return AggregateCore(source, accumulator, CancellationToken.None);
         }
 
         public static Task<TSource> Aggregate<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, TSource, Task<TSource>> accumulator, CancellationToken cancellationToken)
@@ -57,7 +57,7 @@ namespace System.Linq
             if (accumulator == null)
                 throw new ArgumentNullException(nameof(accumulator));
 
-            return Aggregate(source, seed, accumulator, CancellationToken.None);
+            return AggregateCore(source, seed, accumulator, x => x, CancellationToken.None);
         }
 
         public static Task<TAccumulate> Aggregate<TSource, TAccumulate>(this IAsyncEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> accumulator, CancellationToken cancellationToken)
@@ -67,7 +67,7 @@ namespace System.Linq
             if (accumulator == null)
                 throw new ArgumentNullException(nameof(accumulator));
 
-            return source.Aggregate(seed, accumulator, x => x, cancellationToken);
+            return AggregateCore(source, seed, accumulator, x => x, cancellationToken);
         }
 
         public static Task<TAccumulate> Aggregate<TSource, TAccumulate>(this IAsyncEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, Task<TAccumulate>> accumulator)
@@ -77,7 +77,7 @@ namespace System.Linq
             if (accumulator == null)
                 throw new ArgumentNullException(nameof(accumulator));
 
-            return Aggregate(source, seed, accumulator, CancellationToken.None);
+            return AggregateCore(source, seed, accumulator, CancellationToken.None);
         }
 
         public static Task<TAccumulate> Aggregate<TSource, TAccumulate>(this IAsyncEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, Task<TAccumulate>> accumulator, CancellationToken cancellationToken)
@@ -87,7 +87,7 @@ namespace System.Linq
             if (accumulator == null)
                 throw new ArgumentNullException(nameof(accumulator));
 
-            return source.Aggregate(seed, accumulator, x => Task.FromResult(x), cancellationToken);
+            return AggregateCore(source, seed, accumulator, cancellationToken);
         }
 
         public static Task<TResult> Aggregate<TSource, TAccumulate, TResult>(this IAsyncEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> accumulator, Func<TAccumulate, TResult> resultSelector)
@@ -99,7 +99,7 @@ namespace System.Linq
             if (resultSelector == null)
                 throw new ArgumentNullException(nameof(resultSelector));
 
-            return Aggregate(source, seed, accumulator, resultSelector, CancellationToken.None);
+            return AggregateCore(source, seed, accumulator, resultSelector, CancellationToken.None);
         }
 
         public static Task<TResult> Aggregate<TSource, TAccumulate, TResult>(this IAsyncEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> accumulator, Func<TAccumulate, TResult> resultSelector, CancellationToken cancellationToken)
@@ -123,7 +123,7 @@ namespace System.Linq
             if (resultSelector == null)
                 throw new ArgumentNullException(nameof(resultSelector));
 
-            return Aggregate(source, seed, accumulator, resultSelector, CancellationToken.None);
+            return AggregateCore(source, seed, accumulator, resultSelector, CancellationToken.None);
         }
 
         public static Task<TResult> Aggregate<TSource, TAccumulate, TResult>(this IAsyncEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, Task<TAccumulate>> accumulator, Func<TAccumulate, Task<TResult>> resultSelector, CancellationToken cancellationToken)
@@ -185,6 +185,27 @@ namespace System.Linq
             return acc;
         }
 
+        private static async Task<TResult> AggregateCore<TSource, TResult>(IAsyncEnumerable<TSource> source, TResult seed, Func<TResult, TSource, Task<TResult>> accumulator, CancellationToken cancellationToken)
+        {
+            var acc = seed;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    acc = await accumulator(acc, e.Current).ConfigureAwait(false);
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return acc;
+        }
+
         private static async Task<TResult> AggregateCore<TSource, TAccumulate, TResult>(IAsyncEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, Task<TAccumulate>> accumulator, Func<TAccumulate, Task<TResult>> resultSelector, CancellationToken cancellationToken)
         {
             var acc = seed;

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

@@ -17,7 +17,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return All(source, predicate, CancellationToken.None);
+            return AllCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<bool> All<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
@@ -27,7 +27,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return All(source, predicate, CancellationToken.None);
+            return AllCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<bool> All<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)

+ 9 - 4
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Any.cs

@@ -17,7 +17,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return Any(source, predicate, CancellationToken.None);
+            return AnyCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<bool> Any<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
@@ -27,7 +27,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return Any(source, predicate, CancellationToken.None);
+            return AnyCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<bool> Any<TSource>(this IAsyncEnumerable<TSource> source)
@@ -35,7 +35,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return Any(source, CancellationToken.None);
+            return AnyCore(source, CancellationToken.None);
         }
 
         public static Task<bool> Any<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
@@ -58,11 +58,16 @@ namespace System.Linq
             return AnyCore(source, predicate, cancellationToken);
         }
 
-        public static async Task<bool> Any<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
+        public static Task<bool> Any<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
+            return AnyCore(source, cancellationToken);
+        }
+
+        private static async Task<bool> AnyCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
+        {
             var e = source.GetAsyncEnumerator(cancellationToken);
 
             try

+ 5 - 6
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/AppendPrepend.cs

@@ -134,8 +134,7 @@ namespace System.Linq
 
                         if (_enumerator != null)
                         {
-                            if (await LoadFromEnumeratorAsync()
-                                .ConfigureAwait(false))
+                            if (await LoadFromEnumeratorAsync().ConfigureAwait(false))
                             {
                                 return true;
                             }
@@ -180,7 +179,7 @@ namespace System.Linq
 
             public override async Task<TSource[]> ToArrayAsync(CancellationToken cancellationToken)
             {
-                var count = await GetCountAsync(onlyIfCheap: true, cancellationToken: cancellationToken).ConfigureAwait(false);
+                var count = await GetCountAsync(onlyIfCheap: true, cancellationToken).ConfigureAwait(false);
                 if (count == -1)
                 {
                     return await AsyncEnumerableHelpers.ToArray(this, cancellationToken).ConfigureAwait(false);
@@ -230,7 +229,7 @@ namespace System.Linq
 
             public override async Task<List<TSource>> ToListAsync(CancellationToken cancellationToken)
             {
-                var count = await GetCountAsync(onlyIfCheap: true, cancellationToken: cancellationToken).ConfigureAwait(false);
+                var count = await GetCountAsync(onlyIfCheap: true, cancellationToken).ConfigureAwait(false);
                 var list = count == -1 ? new List<TSource>() : new List<TSource>(count);
 
                 if (!_appending)
@@ -389,7 +388,7 @@ namespace System.Linq
 
             public override async Task<TSource[]> ToArrayAsync(CancellationToken cancellationToken)
             {
-                var count = await GetCountAsync(onlyIfCheap: true, cancellationToken: cancellationToken).ConfigureAwait(false);
+                var count = await GetCountAsync(onlyIfCheap: true, cancellationToken).ConfigureAwait(false);
                 if (count == -1)
                 {
                     return await AsyncEnumerableHelpers.ToArray(this, cancellationToken).ConfigureAwait(false);
@@ -437,7 +436,7 @@ namespace System.Linq
 
             public override async Task<List<TSource>> ToListAsync(CancellationToken cancellationToken)
             {
-                var count = await GetCountAsync(onlyIfCheap: true, cancellationToken: cancellationToken).ConfigureAwait(false);
+                var count = await GetCountAsync(onlyIfCheap: true, cancellationToken).ConfigureAwait(false);
                 var list = count == -1 ? new List<TSource>() : new List<TSource>(count);
                 for (var n = _prepended; n != null; n = n.Linked)
                 {

+ 10 - 10
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Average.cs

@@ -32,7 +32,7 @@ namespace System.Linq
                     }
                 }
 
-                return (double)sum/count;
+                return (double)sum / count;
             }
             finally
             {
@@ -66,7 +66,7 @@ namespace System.Linq
                             }
                         }
 
-                        return (double)sum/count;
+                        return (double)sum / count;
                     }
                 }
             }
@@ -100,7 +100,7 @@ namespace System.Linq
                     }
                 }
 
-                return (double)sum/count;
+                return (double)sum / count;
             }
             finally
             {
@@ -134,7 +134,7 @@ namespace System.Linq
                             }
                         }
 
-                        return (double)sum/count;
+                        return (double)sum / count;
                     }
                 }
             }
@@ -168,7 +168,7 @@ namespace System.Linq
                     ++count;
                 }
 
-                return sum/count;
+                return sum / count;
             }
             finally
             {
@@ -202,7 +202,7 @@ namespace System.Linq
                             }
                         }
 
-                        return sum/count;
+                        return sum / count;
                     }
                 }
             }
@@ -233,7 +233,7 @@ namespace System.Linq
                     ++count;
                 }
 
-                return (float)(sum/count);
+                return (float)(sum / count);
             }
             finally
             {
@@ -267,7 +267,7 @@ namespace System.Linq
                             }
                         }
 
-                        return (float)(sum/count);
+                        return (float)(sum / count);
                     }
                 }
             }
@@ -298,7 +298,7 @@ namespace System.Linq
                     ++count;
                 }
 
-                return sum/count;
+                return sum / count;
             }
             finally
             {
@@ -329,7 +329,7 @@ namespace System.Linq
                             }
                         }
 
-                        return sum/count;
+                        return sum / count;
                     }
                 }
             }

+ 55 - 21
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Contains.cs

@@ -10,54 +10,88 @@ namespace System.Linq
 {
     public static partial class AsyncEnumerable
     {
-        public static Task<bool> Contains<TSource>(this IAsyncEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer)
+        public static Task<bool> Contains<TSource>(this IAsyncEnumerable<TSource> source, TSource value)
         {
             if (source == null)
-            {
                 throw new ArgumentNullException(nameof(source));
-            }
 
-            if (comparer == null)
-            {
-                throw new ArgumentNullException(nameof(comparer));
-            }
+            return ContainsCore(source, value, CancellationToken.None);
+        }
 
-            return Contains(source, value, comparer, CancellationToken.None);
+        public static Task<bool> Contains<TSource>(this IAsyncEnumerable<TSource> source, TSource value, CancellationToken cancellationToken)
+        {
+            if (source == null)
+                throw new ArgumentNullException(nameof(source));
+
+            return ContainsCore(source, value, cancellationToken);
         }
 
-        public static Task<bool> Contains<TSource>(this IAsyncEnumerable<TSource> source, TSource value)
+        public static Task<bool> Contains<TSource>(this IAsyncEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer)
         {
             if (source == null)
-            {
                 throw new ArgumentNullException(nameof(source));
-            }
+            if (comparer == null)
+                throw new ArgumentNullException(nameof(comparer));
 
-            return Contains(source, value, CancellationToken.None);
+            return ContainsCore(source, value, comparer, CancellationToken.None);
         }
 
         public static Task<bool> Contains<TSource>(this IAsyncEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer, CancellationToken cancellationToken)
         {
             if (source == null)
-            {
                 throw new ArgumentNullException(nameof(source));
-            }
-
             if (comparer == null)
-            {
                 throw new ArgumentNullException(nameof(comparer));
+
+            return ContainsCore(source, value, comparer, cancellationToken);
+        }
+
+        private static Task<bool> ContainsCore<TSource>(IAsyncEnumerable<TSource> source, TSource value, CancellationToken cancellationToken)
+        {
+            if (source is ICollection<TSource> collection)
+            {
+                return Task.FromResult(collection.Contains(value));
             }
 
-            return source.Any(x => comparer.Equals(x, value), cancellationToken);
+            return ContainsCore(source, value, comparer: null, cancellationToken);
         }
 
-        public static Task<bool> Contains<TSource>(this IAsyncEnumerable<TSource> source, TSource value, CancellationToken cancellationToken)
+        private static async Task<bool> ContainsCore<TSource>(IAsyncEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer, CancellationToken cancellationToken)
         {
-            if (source == null)
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
             {
-                throw new ArgumentNullException(nameof(source));
+                //
+                // See https://github.com/dotnet/corefx/pull/25097 for the optimization here.
+                //
+                if (comparer == null)
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        if (EqualityComparer<TSource>.Default.Equals(e.Current, value))
+                        {
+                            return true;
+                        }
+                    }
+                }
+                else
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        if (comparer.Equals(e.Current, value))
+                        {
+                            return true;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
             }
 
-            return source.Contains(value, EqualityComparer<TSource>.Default, cancellationToken);
+            return false;
         }
     }
 }

+ 108 - 24
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Count.cs

@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the Apache 2.0 License.
 // See the LICENSE file in the project root for more information. 
 
+using System.Collections;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
@@ -10,70 +11,153 @@ namespace System.Linq
 {
     public static partial class AsyncEnumerable
     {
-        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
+        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            if (source is ICollection<TSource> collection)
-            {
-                return Task.FromResult(collection.Count);
-            }
-
-            if (source is IAsyncIListProvider<TSource> listProv)
-            {
-                return listProv.GetCountAsync(false, cancellationToken);
-            }
-
-            return source.Aggregate(0, (c, _) => checked(c + 1), cancellationToken);
+            return CountCore(source, CancellationToken.None);
         }
 
-        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
-            if (predicate == null)
-                throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).Aggregate(0, (c, _) => checked(c + 1), cancellationToken);
+            return CountCore(source, cancellationToken);
         }
 
-        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).Aggregate(0, (c, _) => checked(c + 1), cancellationToken);
+            return CountCore(source, predicate, CancellationToken.None);
         }
 
-        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source)
+        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
+            if (predicate == null)
+                throw new ArgumentNullException(nameof(predicate));
 
-            return Count(source, CancellationToken.None);
+            return CountCore(source, predicate, cancellationToken);
         }
 
-        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate)
+        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return Count(source, predicate, CancellationToken.None);
+            return CountCore(source, predicate, CancellationToken.None);
         }
 
-        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
+        public static Task<int> Count<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return Count(source, predicate, CancellationToken.None);
+            return CountCore(source, predicate, cancellationToken);
+        }
+
+        private static Task<int> CountCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
+        {
+            switch (source)
+            {
+                case ICollection<TSource> collection:
+                    return Task.FromResult(collection.Count);
+                case IAsyncIListProvider<TSource> listProv:
+                    return listProv.GetCountAsync(onlyIfCheap: false, cancellationToken);
+                case ICollection collection:
+                    return Task.FromResult(collection.Count);
+            }
+
+            return Core();
+
+            async Task<int> Core()
+            {
+                var count = 0;
+
+                var e = source.GetAsyncEnumerator(cancellationToken);
+
+                try
+                {
+                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    {
+                        checked
+                        {
+                            count++;
+                        }
+                    }
+                }
+                finally
+                {
+                    await e.DisposeAsync().ConfigureAwait(false);
+                }
+
+                return count;
+            }
+        }
+
+        private static async Task<int> CountCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        {
+            var count = 0;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    if (predicate(e.Current))
+                    {
+                        checked
+                        {
+                            count++;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return count;
+        }
+
+        private static async Task<int> CountCore<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        {
+            var count = 0;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    if (await predicate(e.Current).ConfigureAwait(false))
+                    {
+                        checked
+                        {
+                            count++;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return count;
         }
     }
 }

+ 1 - 1
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/DefaultIfEmpty.cs

@@ -124,7 +124,7 @@ namespace System.Linq
                 else
                 {
                     var listProv = _source as IAsyncIListProvider<TSource>;
-                    count = listProv == null ? -1 : await listProv.GetCountAsync(onlyIfCheap: true, cancellationToken: cancellationToken).ConfigureAwait(false);
+                    count = listProv == null ? -1 : await listProv.GetCountAsync(onlyIfCheap: true, cancellationToken).ConfigureAwait(false);
                 }
 
                 return count == 0 ? 1 : count;

+ 1 - 1
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Distinct.cs

@@ -121,7 +121,7 @@ namespace System.Linq
             {
                 var s = new Set<TSource>(_comparer);
 
-                await s.UnionWithAsync(_source, cancellationToken);
+                await s.UnionWithAsync(_source, cancellationToken).ConfigureAwait(false);
 
                 return s;
             }

+ 1 - 1
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ElementAt.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return ElementAt(source, index, CancellationToken.None);
+            return ElementAtCore(source, index, CancellationToken.None);
         }
 
         public static Task<TSource> ElementAt<TSource>(this IAsyncEnumerable<TSource> source, int index, CancellationToken cancellationToken)

+ 1 - 1
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ElementAtOrDefault.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return ElementAtOrDefault(source, index, CancellationToken.None);
+            return ElementAtOrDefaultCore(source, index, CancellationToken.None);
         }
 
         public static Task<TSource> ElementAtOrDefault<TSource>(this IAsyncEnumerable<TSource> source, int index, CancellationToken cancellationToken)

+ 22 - 39
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/First.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return First(source, CancellationToken.None);
+            return FirstCore(source, CancellationToken.None);
         }
 
         public static Task<TSource> First<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -33,7 +33,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return First(source, predicate, CancellationToken.None);
+            return FirstCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> First<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
@@ -43,7 +43,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).First(cancellationToken);
+            return FirstCore(source, predicate, cancellationToken);
         }
 
         public static Task<TSource> First<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
@@ -53,7 +53,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return First(source, predicate, CancellationToken.None);
+            return FirstCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> First<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
@@ -63,45 +63,28 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).First(cancellationToken);
+            return FirstCore(source, predicate, cancellationToken);
         }
 
         private static async Task<TSource> FirstCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
         {
-            if (source is IList<TSource> list)
-            {
-                if (list.Count > 0)
-                {
-                    return list[0];
-                }
-            }
-            else if (source is IAsyncPartition<TSource> p)
-            {
-                var first = await p.TryGetFirstAsync(cancellationToken).ConfigureAwait(false);
-
-                if (first.HasValue)
-                {
-                    return first.Value;
-                }
-            }
-            else
-            {
-                var e = source.GetAsyncEnumerator(cancellationToken);
-
-                try
-                {
-                    if (await e.MoveNextAsync().ConfigureAwait(false))
-                    {
-                        return e.Current;
-                    }
-                }
-                finally
-                {
-                    await e.DisposeAsync().ConfigureAwait(false);
-                }
-            }
-
-            throw new InvalidOperationException(Strings.NO_ELEMENTS);
+            var first = await TryGetFirst(source, cancellationToken).ConfigureAwait(false);
+
+            return first.HasValue ? first.Value : throw new InvalidOperationException(Strings.NO_ELEMENTS);
+        }
+
+        private static async Task<TSource> FirstCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        {
+            var first = await TryGetFirst(source, predicate, cancellationToken).ConfigureAwait(false);
+
+            return first.HasValue ? first.Value : throw new InvalidOperationException(Strings.NO_ELEMENTS);
+        }
+
+        private static async Task<TSource> FirstCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        {
+            var first = await TryGetFirst(source, predicate, cancellationToken).ConfigureAwait(false);
+
+            return first.HasValue ? first.Value : throw new InvalidOperationException(Strings.NO_ELEMENTS);
         }
     }
 }

+ 89 - 18
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/FirstOrDefault.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return FirstOrDefault(source, CancellationToken.None);
+            return FirstOrDefaultCore(source, CancellationToken.None);
         }
 
         public static Task<TSource> FirstOrDefault<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -33,7 +33,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return FirstOrDefault(source, predicate, CancellationToken.None);
+            return FirstOrDefaultCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> FirstOrDefault<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
@@ -43,7 +43,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).FirstOrDefault(cancellationToken);
+            return FirstOrDefaultCore(source, predicate, cancellationToken);
         }
 
         public static Task<TSource> FirstOrDefault<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
@@ -53,7 +53,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return FirstOrDefault(source, predicate, CancellationToken.None);
+            return FirstOrDefaultCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> FirstOrDefault<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
@@ -63,45 +63,116 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).FirstOrDefault(cancellationToken);
+            return FirstOrDefaultCore(source, predicate, cancellationToken);
         }
 
         private static async Task<TSource> FirstOrDefaultCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
+        {
+            var first = await TryGetFirst(source, cancellationToken).ConfigureAwait(false);
+
+            return first.HasValue ? first.Value : default;
+        }
+
+        private static async Task<TSource> FirstOrDefaultCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        {
+            var first = await TryGetFirst(source, predicate, cancellationToken).ConfigureAwait(false);
+
+            return first.HasValue ? first.Value : default;
+        }
+
+        private static async Task<TSource> FirstOrDefaultCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        {
+            var first = await TryGetFirst(source, predicate, cancellationToken).ConfigureAwait(false);
+
+            return first.HasValue ? first.Value : default;
+        }
+
+        private static Task<Maybe<TSource>> TryGetFirst<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
         {
             if (source is IList<TSource> list)
             {
                 if (list.Count > 0)
                 {
-                    return list[0];
+                    return Task.FromResult(new Maybe<TSource>(list[0]));
                 }
             }
             else if (source is IAsyncPartition<TSource> p)
             {
-                var first = await p.TryGetFirstAsync(cancellationToken).ConfigureAwait(false);
+                return p.TryGetFirstAsync(cancellationToken);
+            }
+            else
+            {
+                return Core();
 
-                if (first.HasValue)
+                async Task<Maybe<TSource>> Core()
                 {
-                    return first.Value;
+                    var e = source.GetAsyncEnumerator(cancellationToken);
+
+                    try
+                    {
+                        if (await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            return new Maybe<TSource>(e.Current);
+                        }
+                    }
+                    finally
+                    {
+                        await e.DisposeAsync().ConfigureAwait(false);
+                    }
+
+                    return new Maybe<TSource>();
                 }
             }
-            else
-            {
-                var e = source.GetAsyncEnumerator(cancellationToken);
 
-                try
+            return Task.FromResult(new Maybe<TSource>());
+        }
+
+        private static async Task<Maybe<TSource>> TryGetFirst<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        {
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
                 {
-                    if (await e.MoveNextAsync().ConfigureAwait(false))
+                    var value = e.Current;
+
+                    if (predicate(value))
                     {
-                        return e.Current;
+                        return new Maybe<TSource>(value);
                     }
                 }
-                finally
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return new Maybe<TSource>();
+        }
+
+        private static async Task<Maybe<TSource>> TryGetFirst<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        {
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
                 {
-                    await e.DisposeAsync().ConfigureAwait(false);
+                    var value = e.Current;
+
+                    if (await predicate(value).ConfigureAwait(false))
+                    {
+                        return new Maybe<TSource>(value);
+                    }
                 }
             }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
 
-            return default;
+            return new Maybe<TSource>();
         }
     }
 }

+ 22 - 49
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Last.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return Last(source, CancellationToken.None);
+            return LastCore(source, CancellationToken.None);
         }
 
         public static Task<TSource> Last<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -33,7 +33,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return Last(source, predicate, CancellationToken.None);
+            return LastCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> Last<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
@@ -43,7 +43,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).Last(cancellationToken);
+            return LastCore(source, predicate, cancellationToken);
         }
 
         public static Task<TSource> Last<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
@@ -53,7 +53,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return Last(source, predicate, CancellationToken.None);
+            return LastCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> Last<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
@@ -63,55 +63,28 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).Last(cancellationToken);
+            return LastCore(source, predicate, cancellationToken);
         }
 
         private static async Task<TSource> LastCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
         {
-            if (source is IList<TSource> list)
-            {
-                var count = list.Count;
-                if (count > 0)
-                {
-                    return list[count - 1];
-                }
-            }
-            else if (source is IAsyncPartition<TSource> p)
-            {
-                var first = await p.TryGetLastAsync(cancellationToken).ConfigureAwait(false);
-
-                if (first.HasValue)
-                {
-                    return first.Value;
-                }
-            }
-            else
-            {
-                var last = default(TSource);
-                var hasLast = false;
-
-                var e = source.GetAsyncEnumerator(cancellationToken);
-
-                try
-                {
-                    while (await e.MoveNextAsync().ConfigureAwait(false))
-                    {
-                        hasLast = true;
-                        last = e.Current;
-                    }
-                }
-                finally
-                {
-                    await e.DisposeAsync().ConfigureAwait(false);
-                }
-
-                if (hasLast)
-                {
-                    return last;
-                }
-            }
-
-            throw new InvalidOperationException(Strings.NO_ELEMENTS);
+            var last = await TryGetLast(source, cancellationToken).ConfigureAwait(false);
+
+            return last.HasValue ? last.Value : throw new InvalidOperationException(Strings.NO_ELEMENTS);
+        }
+
+        private static async Task<TSource> LastCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        {
+            var last = await TryGetLast(source, predicate, cancellationToken).ConfigureAwait(false);
+
+            return last.HasValue ? last.Value : throw new InvalidOperationException(Strings.NO_ELEMENTS);
+        }
+
+        private static async Task<TSource> LastCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        {
+            var last = await TryGetLast(source, predicate, cancellationToken).ConfigureAwait(false);
+
+            return last.HasValue ? last.Value : throw new InvalidOperationException(Strings.NO_ELEMENTS);
         }
     }
 }

+ 108 - 24
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/LastOrDefault.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return LastOrDefault(source, CancellationToken.None);
+            return LastOrDefaultCore(source, CancellationToken.None);
         }
 
         public static Task<TSource> LastOrDefault<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -33,7 +33,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return LastOrDefault(source, predicate, CancellationToken.None);
+            return LastOrDefaultCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> LastOrDefault<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
@@ -43,7 +43,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).LastOrDefault(cancellationToken);
+            return LastOrDefaultCore(source, predicate, cancellationToken);
         }
 
         public static Task<TSource> LastOrDefault<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
@@ -53,7 +53,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return LastOrDefault(source, predicate, CancellationToken.None);
+            return LastOrDefaultCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> LastOrDefault<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
@@ -63,55 +63,139 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).LastOrDefault(cancellationToken);
+            return LastOrDefaultCore(source, predicate, cancellationToken);
         }
 
         private static async Task<TSource> LastOrDefaultCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
+        {
+            var last = await TryGetLast(source, cancellationToken).ConfigureAwait(false);
+
+            return last.HasValue ? last.Value : default;
+        }
+
+        private static async Task<TSource> LastOrDefaultCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        {
+            var last = await TryGetLast(source, predicate, cancellationToken).ConfigureAwait(false);
+
+            return last.HasValue ? last.Value : default;
+        }
+
+        private static async Task<TSource> LastOrDefaultCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        {
+            var last = await TryGetLast(source, predicate, cancellationToken).ConfigureAwait(false);
+
+            return last.HasValue ? last.Value : default;
+        }
+
+        private static Task<Maybe<TSource>> TryGetLast<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
         {
             if (source is IList<TSource> list)
             {
                 var count = list.Count;
                 if (count > 0)
                 {
-                    return list[count - 1];
+                    return Task.FromResult(new Maybe<TSource>(list[count - 1]));
                 }
             }
             else if (source is IAsyncPartition<TSource> p)
             {
-                var first = await p.TryGetLastAsync(cancellationToken).ConfigureAwait(false);
+                return p.TryGetLastAsync(cancellationToken);
+            }
+            else
+            {
+                return Core();
 
-                if (first.HasValue)
+                async Task<Maybe<TSource>> Core()
                 {
-                    return first.Value;
+                    var last = default(TSource);
+                    var hasLast = false;
+
+                    var e = source.GetAsyncEnumerator(cancellationToken);
+
+                    try
+                    {
+                        while (await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            hasLast = true;
+                            last = e.Current;
+                        }
+                    }
+                    finally
+                    {
+                        await e.DisposeAsync().ConfigureAwait(false);
+                    }
+
+                    return hasLast ? new Maybe<TSource>(last) : new Maybe<TSource>();
                 }
             }
-            else
-            {
-                var last = default(TSource);
-                var hasLast = false;
 
-                var e = source.GetAsyncEnumerator(cancellationToken);
+            return Task.FromResult(new Maybe<TSource>());
+        }
+
+        private static async Task<Maybe<TSource>> TryGetLast<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        {
+            var last = default(TSource);
+            var hasLast = false;
 
-                try
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
                 {
-                    while (await e.MoveNextAsync().ConfigureAwait(false))
+                    var value = e.Current;
+
+                    if (predicate(value))
                     {
                         hasLast = true;
-                        last = e.Current;
+                        last = value;
                     }
                 }
-                finally
-                {
-                    await e.DisposeAsync().ConfigureAwait(false);
-                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            if (hasLast)
+            {
+                return new Maybe<TSource>(last);
+            }
+
+            return new Maybe<TSource>();
+        }
 
-                if (hasLast)
+        private static async Task<Maybe<TSource>> TryGetLast<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        {
+            var last = default(TSource);
+            var hasLast = false;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
                 {
-                    return last;
+                    var value = e.Current;
+
+                    if (await predicate(value).ConfigureAwait(false))
+                    {
+                        hasLast = true;
+                        last = value;
+                    }
                 }
             }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            if (hasLast)
+            {
+                return new Maybe<TSource>(last);
+            }
 
-            return default;
+            return new Maybe<TSource>();
         }
     }
 }

+ 92 - 14
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/LongCount.cs

@@ -10,60 +10,138 @@ namespace System.Linq
 {
     public static partial class AsyncEnumerable
     {
-        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
+        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return source.Aggregate(0L, (c, _) => checked(c + 1), cancellationToken);
+            return LongCountCore(source, CancellationToken.None);
         }
 
-        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
-            if (predicate == null)
-                throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).Aggregate(0L, (c, _) => checked(c + 1), cancellationToken);
+            return LongCountCore(source, cancellationToken);
         }
 
-        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).Aggregate(0L, (c, _) => checked(c + 1), cancellationToken);
+            return LongCountCore(source, predicate, CancellationToken.None);
         }
 
-        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source)
+        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
+            if (predicate == null)
+                throw new ArgumentNullException(nameof(predicate));
 
-            return LongCount(source, CancellationToken.None);
+            return LongCountCore(source, predicate, cancellationToken);
         }
 
-        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate)
+        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return LongCount(source, predicate, CancellationToken.None);
+            return LongCountCore(source, predicate, CancellationToken.None);
         }
 
-        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
+        public static Task<long> LongCount<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
         {
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return LongCount(source, predicate, CancellationToken.None);
+            return LongCountCore(source, predicate, cancellationToken);
+        }
+
+        private static async Task<long> LongCountCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
+        {
+            var count = 0L;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    checked
+                    {
+                        count++;
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return count;
+        }
+
+        private static async Task<long> LongCountCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        {
+            var count = 0L;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    if (predicate(e.Current))
+                    {
+                        checked
+                        {
+                            count++;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return count;
+        }
+
+        private static async Task<long> LongCountCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        {
+            var count = 0L;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    if (await predicate(e.Current).ConfigureAwait(false))
+                    {
+                        checked
+                        {
+                            count++;
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return count;
         }
     }
 }

+ 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;
+        }
+    }
+}

+ 6 - 36
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Max.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return Max(source, CancellationToken.None);
+            return MaxCore(source, CancellationToken.None);
         }
 
         public static Task<TSource> Max<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -23,8 +23,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            var comparer = Comparer<TSource>.Default;
-            return source.Aggregate((x, y) => comparer.Compare(x, y) >= 0 ? x : y, cancellationToken);
+            return MaxCore(source, cancellationToken);
         }
 
         public static Task<TResult> Max<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector)
@@ -34,7 +33,7 @@ namespace System.Linq
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return Max(source, selector, CancellationToken.None);
+            return MaxCore(source, selector, CancellationToken.None);
         }
 
         public static Task<TResult> Max<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector, CancellationToken cancellationToken)
@@ -44,7 +43,7 @@ namespace System.Linq
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return source.Select(selector).Max(cancellationToken);
+            return MaxCore(source, selector, cancellationToken);
         }
 
         public static Task<TResult> Max<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<TResult>> selector)
@@ -54,7 +53,7 @@ namespace System.Linq
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return Max(source, selector, CancellationToken.None);
+            return MaxCore(source, selector, CancellationToken.None);
         }
 
         public static Task<TResult> Max<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<TResult>> selector, CancellationToken cancellationToken)
@@ -64,36 +63,7 @@ namespace System.Linq
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return source.Select(selector).Max(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);
-            }
+            return MaxCore(source, selector, 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;
+        }
+    }
+}

+ 6 - 36
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Min.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return Min(source, CancellationToken.None);
+            return MinCore(source, CancellationToken.None);
         }
 
         public static Task<TSource> Min<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -23,8 +23,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            var comparer = Comparer<TSource>.Default;
-            return source.Aggregate((x, y) => comparer.Compare(x, y) <= 0 ? x : y, cancellationToken);
+            return MinCore(source, cancellationToken);
         }
 
         public static Task<TResult> Min<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector)
@@ -34,7 +33,7 @@ namespace System.Linq
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return Min(source, selector, CancellationToken.None);
+            return MinCore(source, selector, CancellationToken.None);
         }
 
         public static Task<TResult> Min<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TResult> selector, CancellationToken cancellationToken)
@@ -44,7 +43,7 @@ namespace System.Linq
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return source.Select(selector).Min(cancellationToken);
+            return MinCore(source, selector, cancellationToken);
         }
 
         public static Task<TResult> Min<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<TResult>> selector)
@@ -54,7 +53,7 @@ namespace System.Linq
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return Min(source, selector, CancellationToken.None);
+            return MinCore(source, selector, CancellationToken.None);
         }
 
         public static Task<TResult> Min<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<TResult>> selector, CancellationToken cancellationToken)
@@ -64,36 +63,7 @@ namespace System.Linq
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return source.Select(selector).Min(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);
-            }
+            return MinCore(source, selector, cancellationToken);
         }
     }
 }

文件差异内容过多而无法显示
+ 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" #>
 <#@ output extension=".cs" #>
 // 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.Threading;
@@ -17,45 +17,17 @@ namespace System.Linq
     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 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)
         {
             if (source == null)
                 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)
@@ -63,20 +35,7 @@ else
             if (source == null)
                 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)
@@ -86,7 +45,7 @@ else
             if (selector == null)
                 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)
@@ -96,7 +55,7 @@ else
             if (selector == null)
                 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)
@@ -106,7 +65,7 @@ else
             if (selector == null)
                 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)
@@ -116,35 +75,12 @@ else
             if (selector == null)
                 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;
-        }
     }
 }

+ 69 - 5
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Single.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return Single(source, CancellationToken.None);
+            return SingleCore(source, CancellationToken.None);
         }
 
         public static Task<TSource> Single<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -33,7 +33,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return Single(source, predicate, CancellationToken.None);
+            return SingleCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> Single<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
@@ -43,7 +43,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).Single(cancellationToken);
+            return SingleCore(source, predicate, cancellationToken);
         }
 
         public static Task<TSource> Single<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
@@ -53,7 +53,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return Single(source, predicate, CancellationToken.None);
+            return SingleCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> Single<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
@@ -63,7 +63,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).Single(cancellationToken);
+            return SingleCore(source, predicate, cancellationToken);
         }
 
         private static async Task<TSource> SingleCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -101,5 +101,69 @@ namespace System.Linq
                 await e.DisposeAsync().ConfigureAwait(false);
             }
         }
+
+        private static async Task<TSource> SingleCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        {
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var result = e.Current;
+
+                    if (predicate(result))
+                    {
+                        while (await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            if (predicate(e.Current))
+                            {
+                                throw new InvalidOperationException(Strings.MORE_THAN_ONE_ELEMENT);
+                            }
+                        }
+
+                        return result;
+                    }
+                }
+
+                throw new InvalidOperationException(Strings.NO_ELEMENTS);
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+        }
+
+        private static async Task<TSource> SingleCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        {
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var result = e.Current;
+
+                    if (await predicate(result).ConfigureAwait(false))
+                    {
+                        while (await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            if (await predicate(e.Current).ConfigureAwait(false))
+                            {
+                                throw new InvalidOperationException(Strings.MORE_THAN_ONE_ELEMENT);
+                            }
+                        }
+
+                        return result;
+                    }
+                }
+
+                throw new InvalidOperationException(Strings.NO_ELEMENTS);
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+        }
     }
 }

+ 70 - 5
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SingleOrDefault.cs

@@ -15,7 +15,7 @@ namespace System.Linq
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return SingleOrDefault(source, CancellationToken.None);
+            return SingleOrDefaultCore(source, CancellationToken.None);
         }
 
         public static Task<TSource> SingleOrDefault<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -33,7 +33,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return SingleOrDefault(source, predicate, CancellationToken.None);
+            return SingleOrDefaultCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> SingleOrDefault<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
@@ -43,7 +43,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).SingleOrDefault(cancellationToken);
+            return SingleOrDefaultCore(source, predicate, cancellationToken);
         }
 
         public static Task<TSource> SingleOrDefault<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate)
@@ -53,7 +53,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return SingleOrDefault(source, predicate, CancellationToken.None);
+            return SingleOrDefaultCore(source, predicate, CancellationToken.None);
         }
 
         public static Task<TSource> SingleOrDefault<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
@@ -63,7 +63,7 @@ namespace System.Linq
             if (predicate == null)
                 throw new ArgumentNullException(nameof(predicate));
 
-            return source.Where(predicate).SingleOrDefault(cancellationToken);
+            return SingleOrDefaultCore(source, predicate, cancellationToken);
         }
 
         private static async Task<TSource> SingleOrDefaultCore<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
@@ -89,6 +89,7 @@ namespace System.Linq
                 }
 
                 var result = e.Current;
+
                 if (!await e.MoveNextAsync().ConfigureAwait(false))
                 {
                     return result;
@@ -101,5 +102,69 @@ namespace System.Linq
 
             throw new InvalidOperationException(Strings.MORE_THAN_ONE_ELEMENT);
         }
+
+        private static async Task<TSource> SingleOrDefaultCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
+        {
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var result = e.Current;
+
+                    if (predicate(result))
+                    {
+                        while (await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            if (predicate(e.Current))
+                            {
+                                throw new InvalidOperationException(Strings.MORE_THAN_ONE_ELEMENT);
+                            }
+                        }
+
+                        return result;
+                    }
+                }
+
+                return default;
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+        }
+
+        private static async Task<TSource> SingleOrDefaultCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<bool>> predicate, CancellationToken cancellationToken)
+        {
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var result = e.Current;
+
+                    if (await predicate(result).ConfigureAwait(false))
+                    {
+                        while (await e.MoveNextAsync().ConfigureAwait(false))
+                        {
+                            if (await predicate(e.Current).ConfigureAwait(false))
+                            {
+                                throw new InvalidOperationException(Strings.MORE_THAN_ONE_ELEMENT);
+                            }
+                        }
+
+                        return result;
+                    }
+                }
+
+                return default;
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+        }
     }
 }

文件差异内容过多而无法显示
+ 620 - 48
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Sum.Generated.cs


+ 131 - 16
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Sum.Generated.tt

@@ -19,16 +19,16 @@ namespace System.Linq
 <#
 var os = new[]
 {
-    new { type = "int", zero = "0" },
-    new { type = "long", zero = "0L" },
-    new { type = "float", zero = "0.0f" },
-    new { type = "double", zero = "0.0" },
-    new { type = "decimal", zero = "0m" },
-    new { type = "int?", zero = "(int?)0" },
-    new { type = "long?", zero = "(long?)0L" },
-    new { type = "float?", zero = "(float?)0.0f" },
-    new { type = "double?", zero = "(double?)0.0" },
-    new { type = "decimal?", zero = "(decimal?)0m" },
+    new { type = "int", zero = "0", @checked = true },
+    new { type = "long", zero = "0L", @checked = true },
+    new { type = "float", zero = "0.0f", @checked = false },
+    new { type = "double", zero = "0.0", @checked = false },
+    new { type = "decimal", zero = "0m", @checked = false },
+    new { type = "int?", zero = "0", @checked = true },
+    new { type = "long?", zero = "0L", @checked = true },
+    new { type = "float?", zero = "0.0f", @checked = false },
+    new { type = "double?", zero = "0.0", @checked = false },
+    new { type = "decimal?", zero = "0m", @checked = false },
 };
 
 foreach (var o in os)
@@ -40,7 +40,7 @@ foreach (var o in os)
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return source.Aggregate(<#=o.zero#>, (x, y) => x + y<#=n#>, CancellationToken.None);
+            return SumCore(source, CancellationToken.None);
         }
 
         public static Task<<#=o.type#>> Sum(this IAsyncEnumerable<<#=o.type#>> source, CancellationToken cancellationToken)
@@ -48,7 +48,7 @@ foreach (var o in os)
             if (source == null)
                 throw new ArgumentNullException(nameof(source));
 
-            return source.Aggregate(<#=o.zero#>, (x, y) => x + y<#=n#>, cancellationToken);
+            return SumCore(source, cancellationToken);
         }
 
         public static Task<<#=o.type#>> Sum<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, <#=o.type#>> selector)
@@ -58,7 +58,7 @@ foreach (var o in os)
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return source.Select(selector).Sum(CancellationToken.None);
+            return SumCore(source, selector, CancellationToken.None);
         }
 
         public static Task<<#=o.type#>> Sum<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, <#=o.type#>> selector, CancellationToken cancellationToken)
@@ -68,7 +68,7 @@ foreach (var o in os)
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return source.Select(selector).Sum(cancellationToken);
+            return SumCore(source, selector, cancellationToken);
         }
 
         public static Task<<#=o.type#>> Sum<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<<#=o.type#>>> selector)
@@ -78,7 +78,7 @@ foreach (var o in os)
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return source.Select(selector).Sum(CancellationToken.None);
+            return SumCore(source, selector, CancellationToken.None);
         }
 
         public static Task<<#=o.type#>> Sum<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, Task<<#=o.type#>>> selector, CancellationToken cancellationToken)
@@ -88,7 +88,122 @@ foreach (var o in os)
             if (selector == null)
                 throw new ArgumentNullException(nameof(selector));
 
-            return source.Select(selector).Sum(cancellationToken);
+            return SumCore(source, selector, cancellationToken);
+        }
+
+        private static async Task<<#=o.type#>> SumCore(IAsyncEnumerable<<#=o.type#>> source, CancellationToken cancellationToken)
+        {
+            var sum = <#=o.zero#>;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+<#
+if (o.@checked)
+{
+#>
+                    checked
+                    {
+                        sum += e.Current<#=n#>;
+                    }
+<#
+}
+else
+{
+#>
+                    sum += e.Current<#=n#>;
+<#
+}
+#>
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return sum;
+        }
+
+        private static async Task<<#=o.type#>> SumCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, <#=o.type#>> selector, CancellationToken cancellationToken)
+        {
+            var sum = <#=o.zero#>;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var value = selector(e.Current);
+
+<#
+if (o.@checked)
+{
+#>
+                    checked
+                    {
+                        sum += value<#=n#>;
+                    }
+<#
+}
+else
+{
+#>
+                    sum += value<#=n#>;
+<#
+}
+#>
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return sum;
+        }
+
+        private static async Task<<#=o.type#>> SumCore<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, Task<<#=o.type#>>> selector, CancellationToken cancellationToken)
+        {
+            var sum = <#=o.zero#>;
+
+            var e = source.GetAsyncEnumerator(cancellationToken);
+
+            try
+            {
+                while (await e.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var value = await selector(e.Current).ConfigureAwait(false);
+
+<#
+if (o.@checked)
+{
+#>
+                    checked
+                    {
+                        sum += value<#=n#>;
+                    }
+<#
+}
+else
+{
+#>
+                    sum += value<#=n#>;
+<#
+}
+#>
+                }
+            }
+            finally
+            {
+                await e.DisposeAsync().ConfigureAwait(false);
+            }
+
+            return sum;
         }
 
 <#

+ 7 - 2
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ToAsyncEnumerable.cs

@@ -33,7 +33,7 @@ namespace System.Linq
                 throw new ArgumentNullException(nameof(task));
 
             return CreateEnumerable(
-                () =>
+                _ =>
                 {
                     var called = 0;
 
@@ -59,12 +59,16 @@ namespace System.Linq
                 throw new ArgumentNullException(nameof(source));
 
             return CreateEnumerable(
-                () =>
+                ct =>
                 {
                     var observer = new ToAsyncEnumerableObserver<TSource>();
 
                     var subscription = source.Subscribe(observer);
 
+                    // REVIEW: Review possible concurrency issues with Dispose calls.
+
+                    var ctr = ct.Register(subscription.Dispose);
+
                     return CreateEnumerator(
                         tcs =>
                         {
@@ -111,6 +115,7 @@ namespace System.Linq
                         () => observer.Current,
                         () =>
                         {
+                            ctr.Dispose();
                             subscription.Dispose();
                             // Should we cancel in-flight operations somehow?
                             return TaskExt.CompletedTask;

+ 33 - 28
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ToObservable.cs

@@ -30,44 +30,49 @@ namespace System.Linq
                 var ctd = new CancellationTokenDisposable();
                 var e = _source.GetAsyncEnumerator(ctd.Token);
 
-                void Core() => e.MoveNextAsync().AsTask().ContinueWith(
-                    async t =>
+                async void Core()
+                {
+                    bool hasNext;
+                    try
                     {
-                        if (t.IsFaulted)
-                        {
-                            observer.OnError(t.Exception);
-                            await e.DisposeAsync().ConfigureAwait(false);
-                        }
-                        else if (t.IsCanceled)
+                        hasNext = await e.MoveNextAsync().ConfigureAwait(false);
+                    }
+                    catch (Exception ex)
+                    {
+                        if (!ctd.Token.IsCancellationRequested)
                         {
+                            observer.OnError(ex);
                             await e.DisposeAsync().ConfigureAwait(false);
                         }
-                        else if (t.IsCompleted)
-                        {
-                            if (t.Result)
-                            {
-                                observer.OnNext(e.Current);
 
-                                if (!ctd.Token.IsCancellationRequested)
-                                {
-                                    Core();
-                                }
+                        return;
+                    }
 
-                                // In case cancellation is requested, this could only have happened
-                                // by disposing the returned composite disposable (see below).
-                                // In that case, e will be disposed too, so there is no need to dispose e here.
-                            }
-                            else
-                            {
-                                observer.OnCompleted();
-                                await e.DisposeAsync().ConfigureAwait(false);
-                            }
+                    if (hasNext)
+                    {
+                        observer.OnNext(e.Current);
+
+                        if (!ctd.Token.IsCancellationRequested)
+                        {
+                            Core();
                         }
-                    }, ctd.Token);
+
+                        // In case cancellation is requested, this could only have happened
+                        // by disposing the returned composite disposable (see below).
+                        // In that case, e will be disposed too, so there is no need to dispose e here.
+                    }
+                    else
+                    {
+                        observer.OnCompleted();
+                        await e.DisposeAsync().ConfigureAwait(false);
+                    }
+                }
 
                 Core();
 
-                return Disposable.Create(ctd, Disposable.Create(() => { e.DisposeAsync(); /* REVIEW: fire-and-forget? */ }));
+                // REVIEW: Safety of concurrent dispose operation; fire-and-forget nature of dispose?
+
+                return Disposable.Create(ctd, Disposable.Create(() => { e.DisposeAsync(); }));
             }
         }
     }

+ 1 - 1
Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Union.cs

@@ -169,7 +169,7 @@ namespace System.Linq
                         return set;
                     }
 
-                    await set.UnionWithAsync(enumerable, cancellationToken);
+                    await set.UnionWithAsync(enumerable, cancellationToken).ConfigureAwait(false);
                 }
             }
 

+ 1 - 1
Ix.NET/Source/System.Linq.Async/System/Linq/Set.cs

@@ -145,7 +145,7 @@ namespace System.Linq
             _slots = newSlots;
         }
 
-        public async Task UnionWithAsync(IAsyncEnumerable<TElement> other, CancellationToken cancellationToken)
+        public async ValueTask UnionWithAsync(IAsyncEnumerable<TElement> other, CancellationToken cancellationToken)
         {
             var enu = other.GetAsyncEnumerator(cancellationToken);
 

+ 1 - 0
azure-pipelines.ix.yml

@@ -14,6 +14,7 @@ pr:
     include:
       - master
       - rel/*
+      - IxAsyncCSharp8
   paths:
     include:
       - Ix.NET/Source/*

部分文件因为文件数量过多而无法显示