From 7ba9ceb09fd5479b4855ac636a4e156495e9615b Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 26 Feb 2019 13:38:39 -0500 Subject: [PATCH 1/2] Revert "Revert "Add another Zip IEnumerable extension method (#26582)" (#33709)" This reverts commit 53ae9af48c5c133f4413e76c1469dfe6ccecd926. --- .../ref/System.Linq.Queryable.cs | 1 + .../src/System/Linq/CachedReflection.cs | 9 +- .../src/System/Linq/Queryable.cs | 19 ++ .../tests/System.Linq.Queryable.Tests.csproj | 3 +- src/System.Linq.Queryable/tests/ZipTests.cs | 2 +- .../tests/ZipTests.netcoreapp.cs | 53 ++++ src/System.Linq/ref/System.Linq.cs | 1 + src/System.Linq/src/System/Linq/Zip.cs | 27 +++ .../tests/System.Linq.Tests.csproj | 3 +- src/System.Linq/tests/ZipTests.cs | 4 +- src/System.Linq/tests/ZipTests.netcoreapp.cs | 226 ++++++++++++++++++ 11 files changed, 341 insertions(+), 7 deletions(-) create mode 100644 src/System.Linq.Queryable/tests/ZipTests.netcoreapp.cs create mode 100644 src/System.Linq/tests/ZipTests.netcoreapp.cs diff --git a/src/System.Linq.Queryable/ref/System.Linq.Queryable.cs b/src/System.Linq.Queryable/ref/System.Linq.Queryable.cs index cb1bcde7cd90..6a435a259d48 100644 --- a/src/System.Linq.Queryable/ref/System.Linq.Queryable.cs +++ b/src/System.Linq.Queryable/ref/System.Linq.Queryable.cs @@ -163,6 +163,7 @@ public static partial class Queryable public static System.Linq.IQueryable Union(this System.Linq.IQueryable source1, System.Collections.Generic.IEnumerable source2, System.Collections.Generic.IEqualityComparer comparer) { throw null; } public static System.Linq.IQueryable Where(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } public static System.Linq.IQueryable Where(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } + public static System.Linq.IQueryable<(TFirst First, TSecond Second)> Zip(this System.Linq.IQueryable source1, System.Collections.Generic.IEnumerable source2) { throw null; } public static System.Linq.IQueryable Zip(this System.Linq.IQueryable source1, System.Collections.Generic.IEnumerable source2, System.Linq.Expressions.Expression> resultSelector) { throw null; } } } diff --git a/src/System.Linq.Queryable/src/System/Linq/CachedReflection.cs b/src/System.Linq.Queryable/src/System/Linq/CachedReflection.cs index 00dd5bcd3630..7d120a66998c 100644 --- a/src/System.Linq.Queryable/src/System/Linq/CachedReflection.cs +++ b/src/System.Linq.Queryable/src/System/Linq/CachedReflection.cs @@ -830,6 +830,13 @@ public static MethodInfo Where_Index_TSource_2(Type TSource) => (s_Where_Index_TSource_2 = new Func, Expression>, IQueryable>(Queryable.Where).GetMethodInfo().GetGenericMethodDefinition())) .MakeGenericMethod(TSource); + private static MethodInfo s_Zip_TFirst_TSecond_2; + + public static MethodInfo Zip_TFirst_TSecond_2(Type TFirst, Type TSecond) => + (s_Zip_TFirst_TSecond_2 ?? + (s_Zip_TFirst_TSecond_2 = new Func, IEnumerable, IQueryable>>(Queryable.Zip).GetMethodInfo().GetGenericMethodDefinition())) + .MakeGenericMethod(TFirst, TSecond); + private static MethodInfo s_Zip_TFirst_TSecond_TResult_3; public static MethodInfo Zip_TFirst_TSecond_TResult_3(Type TFirst, Type TSecond, Type TResult) => @@ -866,4 +873,4 @@ public static MethodInfo Prepend_TSource_2(Type TSource) => (s_Prepend_TSource_2 = new Func, object, IQueryable>(Queryable.Prepend).GetMethodInfo().GetGenericMethodDefinition())) .MakeGenericMethod(TSource); } -} \ No newline at end of file +} diff --git a/src/System.Linq.Queryable/src/System/Linq/Queryable.cs b/src/System.Linq.Queryable/src/System/Linq/Queryable.cs index 47a0b6c63c8a..54c7ac4525d7 100644 --- a/src/System.Linq.Queryable/src/System/Linq/Queryable.cs +++ b/src/System.Linq.Queryable/src/System/Linq/Queryable.cs @@ -592,6 +592,25 @@ public static IQueryable Concat(this IQueryable sourc )); } + public static IQueryable<(TFirst First, TSecond Second)> Zip(this IQueryable source1, IEnumerable source2) + { + if (source1 == null) + { + throw Error.ArgumentNull(nameof(source1)); + } + + if (source2 == null) + { + throw Error.ArgumentNull(nameof(source2)); + } + + return source1.Provider.CreateQuery<(TFirst, TSecond)>( + Expression.Call( + null, + CachedReflectionInfo.Zip_TFirst_TSecond_2(typeof(TFirst), typeof(TSecond)), + source1.Expression, GetSourceExpression(source2))); + } + public static IQueryable Zip(this IQueryable source1, IEnumerable source2, Expression> resultSelector) { if (source1 == null) diff --git a/src/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj b/src/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj index b4b08de0e6a7..9044074e9b46 100644 --- a/src/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj +++ b/src/System.Linq.Queryable/tests/System.Linq.Queryable.Tests.csproj @@ -1,4 +1,4 @@ - + {7B88D79B-B799-4116-A7D0-AED572540CD4} netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release @@ -7,6 +7,7 @@ + diff --git a/src/System.Linq.Queryable/tests/ZipTests.cs b/src/System.Linq.Queryable/tests/ZipTests.cs index 924bc50f81eb..6ba48a714e8a 100644 --- a/src/System.Linq.Queryable/tests/ZipTests.cs +++ b/src/System.Linq.Queryable/tests/ZipTests.cs @@ -7,7 +7,7 @@ namespace System.Linq.Tests { - public class ZipTests : EnumerableBasedTests + public partial class ZipTests : EnumerableBasedTests { [Fact] public void CorrectResults() diff --git a/src/System.Linq.Queryable/tests/ZipTests.netcoreapp.cs b/src/System.Linq.Queryable/tests/ZipTests.netcoreapp.cs new file mode 100644 index 000000000000..97c53663a9ab --- /dev/null +++ b/src/System.Linq.Queryable/tests/ZipTests.netcoreapp.cs @@ -0,0 +1,53 @@ +// 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 Xunit; + +namespace System.Linq.Tests +{ + public partial class ZipTests + { + [Fact] + public void Zip2_CorrectResults() + { + int[] first = new int[] { 1, 2, 3 }; + int[] second = new int[] { 2, 5, 9 }; + var expected = new (int, int)[] { (1, 2), (2, 5), (3, 9) }; + Assert.Equal(expected, first.AsQueryable().Zip(second.AsQueryable())); + } + + [Fact] + public void Zip2_FirstIsNull() + { + IQueryable first = null; + int[] second = new int[] { 2, 5, 9 }; + AssertExtensions.Throws("source1", () => first.Zip(second.AsQueryable())); + } + + [Fact] + public void Zip2_SecondIsNull() + { + int[] first = new int[] { 1, 2, 3 }; + IQueryable second = null; + AssertExtensions.Throws("source2", () => first.AsQueryable().Zip(second)); + } + + [Fact] + public void Zip2() + { + int count = (new int[] { 0, 1, 2 }).AsQueryable().Zip((new int[] { 10, 11, 12 }).AsQueryable()).Count(); + Assert.Equal(3, count); + } + + [Fact] + public void TupleNames() + { + int[] first = new int[] { 1 }; + int[] second = new int[] { 2 }; + var tuple = first.AsQueryable().Zip(second.AsQueryable()).First(); + Assert.Equal(tuple.Item1, tuple.First); + Assert.Equal(tuple.Item2, tuple.Second); + } + } +} diff --git a/src/System.Linq/ref/System.Linq.cs b/src/System.Linq/ref/System.Linq.cs index eca16c9c52f5..6f6f8d5ecf6a 100644 --- a/src/System.Linq/ref/System.Linq.cs +++ b/src/System.Linq/ref/System.Linq.cs @@ -190,6 +190,7 @@ public static partial class Enumerable public static System.Collections.Generic.IEnumerable Union(this System.Collections.Generic.IEnumerable first, System.Collections.Generic.IEnumerable second, System.Collections.Generic.IEqualityComparer comparer) { throw null; } public static System.Collections.Generic.IEnumerable Where(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } public static System.Collections.Generic.IEnumerable Where(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } + public static System.Collections.Generic.IEnumerable<(TFirst First,TSecond Second)> Zip(this System.Collections.Generic.IEnumerable first, System.Collections.Generic.IEnumerable second) { throw null; } public static System.Collections.Generic.IEnumerable Zip(this System.Collections.Generic.IEnumerable first, System.Collections.Generic.IEnumerable second, System.Func resultSelector) { throw null; } } public partial interface IGrouping : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable diff --git a/src/System.Linq/src/System/Linq/Zip.cs b/src/System.Linq/src/System/Linq/Zip.cs index 19c4dd4c2537..36861b8aae22 100644 --- a/src/System.Linq/src/System/Linq/Zip.cs +++ b/src/System.Linq/src/System/Linq/Zip.cs @@ -28,6 +28,33 @@ public static IEnumerable Zip(this IEnumerabl return ZipIterator(first, second, resultSelector); } + public static IEnumerable<(TFirst First, TSecond Second)> Zip(this IEnumerable first, IEnumerable second) + { + if (first is null) + { + throw Error.ArgumentNull(nameof(first)); + } + + if (second is null) + { + throw Error.ArgumentNull(nameof(second)); + } + + return ZipIterator(first, second); + } + + private static IEnumerable<(TFirst, TSecond)> ZipIterator(IEnumerable first, IEnumerable second) + { + using (IEnumerator e1 = first.GetEnumerator()) + using (IEnumerator e2 = second.GetEnumerator()) + { + while (e1.MoveNext() && e2.MoveNext()) + { + yield return (e1.Current, e2.Current); + } + } + } + private static IEnumerable ZipIterator(IEnumerable first, IEnumerable second, Func resultSelector) { using (IEnumerator e1 = first.GetEnumerator()) diff --git a/src/System.Linq/tests/System.Linq.Tests.csproj b/src/System.Linq/tests/System.Linq.Tests.csproj index a2584e593726..cc88ba94854c 100644 --- a/src/System.Linq/tests/System.Linq.Tests.csproj +++ b/src/System.Linq/tests/System.Linq.Tests.csproj @@ -1,4 +1,4 @@ - + {7C70BB15-870B-4946-8098-625DACD645A6} netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release;uapaot-Debug;uapaot-Release @@ -10,6 +10,7 @@ + diff --git a/src/System.Linq/tests/ZipTests.cs b/src/System.Linq/tests/ZipTests.cs index 1d94dc155dc6..b3b18edb079e 100644 --- a/src/System.Linq/tests/ZipTests.cs +++ b/src/System.Linq/tests/ZipTests.cs @@ -2,14 +2,12 @@ // 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; -using System.Collections; using System.Collections.Generic; using Xunit; namespace System.Linq.Tests { - public class ZipTests : EnumerableTests + public partial class ZipTests : EnumerableTests { [Fact] public void ImplicitTypeParameters() diff --git a/src/System.Linq/tests/ZipTests.netcoreapp.cs b/src/System.Linq/tests/ZipTests.netcoreapp.cs new file mode 100644 index 000000000000..05811fafa914 --- /dev/null +++ b/src/System.Linq/tests/ZipTests.netcoreapp.cs @@ -0,0 +1,226 @@ +// 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 Xunit; + +namespace System.Linq.Tests +{ + public partial class ZipTests + { + [Fact] + public void Zip2_ImplicitTypeParameters() + { + IEnumerable first = new int[] { 1, 2, 3 }; + IEnumerable second = new int[] { 2, 5, 9 }; + IEnumerable<(int, int)> expected = new (int,int)[] { (1,2), (2,5), (3,9) }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_ExplicitTypeParameters() + { + IEnumerable first = new int[] { 1, 2, 3 }; + IEnumerable second = new int[] { 2, 5, 9 }; + IEnumerable<(int, int)> expected = new (int,int)[] { (1,2), (2,5), (3,9) }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_FirstIsNull() + { + IEnumerable first = null; + IEnumerable second = new int[] { 2, 5, 9 }; + + AssertExtensions.Throws("first", () => first.Zip(second)); + } + + [Fact] + public void Zip2_SecondIsNull() + { + IEnumerable first = new int[] { 1, 2, 3 }; + IEnumerable second = null; + + AssertExtensions.Throws("second", () => first.Zip(second)); + } + + [Fact] + public void Zip2_ExceptionThrownFromFirstsEnumerator() + { + ThrowsOnMatchEnumerable first = new ThrowsOnMatchEnumerable(new int[] { 1, 3, 3 }, 2); + IEnumerable second = new int[] { 2, 4, 6 }; + IEnumerable<(int, int)> expected = new (int,int)[] { (1,2), (3,4), (3,6) }; + + Assert.Equal(expected, first.Zip(second)); + + first = new ThrowsOnMatchEnumerable(new int[] { 1, 2, 3 }, 2); + + IEnumerable<(int, int)> zip = first.Zip(second); + + Assert.Throws(() => zip.ToList()); + } + + [Fact] + public void Zip2_ExceptionThrownFromSecondsEnumerator() + { + ThrowsOnMatchEnumerable second = new ThrowsOnMatchEnumerable(new int[] { 1, 3, 3 }, 2); + IEnumerable first = new int[] { 2, 4, 6 }; + IEnumerable<(int, int)> expected = new (int,int)[] { (2,1), (4,3), (6,3) }; + + Assert.Equal(expected, first.Zip(second)); + + second = new ThrowsOnMatchEnumerable(new int[] { 1, 2, 3 }, 2); + + IEnumerable<(int, int)> zip = first.Zip(second); + + Assert.Throws(() => zip.ToList()); + } + + [Fact] + public void Zip2_FirstAndSecondEmpty() + { + IEnumerable first = new int[] { }; + IEnumerable second = new int[] { }; + IEnumerable<(int, int)> expected = new (int, int)[] { }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_FirstEmptySecondSingle() + { + IEnumerable first = new int[] { }; + IEnumerable second = new int[] { 2 }; + IEnumerable<(int, int)> expected = new (int, int)[] { }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_FirstEmptySecondMany() + { + IEnumerable first = new int[] { }; + IEnumerable second = new int[] { 2, 4, 8 }; + IEnumerable<(int, int)> expected = new (int, int)[] { }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_SecondEmptyFirstSingle() + { + IEnumerable first = new int[] { 1 }; + IEnumerable second = new int[] { }; + IEnumerable<(int, int)> expected = new (int, int)[] { }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_SecondEmptyFirstMany() + { + IEnumerable first = new int[] { 1, 2, 3 }; + IEnumerable second = new int[] { }; + IEnumerable<(int, int)> expected = new (int, int)[] { }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_FirstAndSecondSingle() + { + IEnumerable first = new int[] { 1 }; + IEnumerable second = new int[] { 2 }; + IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2) }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_FirstAndSecondEqualSize() + { + IEnumerable first = new int[] { 1, 2, 3 }; + IEnumerable second = new int[] { 2, 3, 4 }; + IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (2, 3), (3, 4) }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_SecondOneMoreThanFirst() + { + IEnumerable first = new int[] { 1, 2 }; + IEnumerable second = new int[] { 2, 4, 8 }; + IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (2, 4) }; + + Assert.Equal(expected, first.Zip(second)); + } + + + [Fact] + public void Zip2_SecondManyMoreThanFirst() + { + IEnumerable first = new int[] { 1, 2 }; + IEnumerable second = new int[] { 2, 4, 8, 16 }; + IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (2, 4) }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_FirstOneMoreThanSecond() + { + IEnumerable first = new int[] { 1, 2, 3 }; + IEnumerable second = new int[] { 2, 4 }; + IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (2, 4) }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_FirstManyMoreThanSecond() + { + IEnumerable first = new int[] { 1, 2, 3, 4 }; + IEnumerable second = new int[] { 2, 4 }; + IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (2, 4) }; + + Assert.Equal(expected, first.Zip(second)); + } + + [Fact] + public void Zip2_RunOnce() + { + IEnumerable first = new[] { 1, (int?)null, 3 }; + IEnumerable second = new[] { 2, 4, 6, 8 }; + IEnumerable<(int?, int)> expected = new (int?, int)[] { (1, 2), (null, 4), (3, 6) }; + + Assert.Equal(expected, first.RunOnce().Zip(second.RunOnce())); + } + + [Fact] + public void Zip2_NestedTuple() + { + IEnumerable first = new[] { 1, 3, 5 }; + IEnumerable second = new[] { 2, 4, 6 }; + IEnumerable<(int, int)> third = new[] { (1, 2), (3, 4), (5, 6) }; + + Assert.Equal(third, first.Zip(second)); + + IEnumerable fourth = new[] { "one", "two", "three" }; + + IEnumerable<((int, int), string)> final = new[] { ((1, 2), "one"), ((3, 4), "two"), ((5, 6), "three") }; + Assert.Equal(final, third.Zip(fourth)); + } + + [Fact] + public void Zip2_TupleNames() + { + var t = new[] { 1, 2, 3 }.Zip(new[] { 2, 4, 6 }).First(); + Assert.Equal(t.Item1, t.First); + Assert.Equal(t.Item2, t.Second); + } + } +} From 05a970da535f09fd05caa675ded6a2edd0963739 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 26 Feb 2019 14:37:10 -0500 Subject: [PATCH 2/2] Adapt to ThrowHelper changes --- src/System.Linq/src/System/Linq/Zip.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Linq/src/System/Linq/Zip.cs b/src/System.Linq/src/System/Linq/Zip.cs index 36861b8aae22..b87bea19bdc5 100644 --- a/src/System.Linq/src/System/Linq/Zip.cs +++ b/src/System.Linq/src/System/Linq/Zip.cs @@ -32,12 +32,12 @@ public static IEnumerable Zip(this IEnumerabl { if (first is null) { - throw Error.ArgumentNull(nameof(first)); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.first); } if (second is null) { - throw Error.ArgumentNull(nameof(second)); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.second); } return ZipIterator(first, second);