Skip to content

Commit

Permalink
More arities for tuple returning zip extension method (#47147)
Browse files Browse the repository at this point in the history
* Implementation.

* ref source

* Tests for Zip3.

* Implement queryable.zip3

* ref source for Queryable.Zip3

* Test for Queryable.Zip3

* xml doc

* Add to ILLink supression.

* Fix argument name in copied tests.

* PR feedbacks

Co-authored-by: Stephen Toub <[email protected]>
Co-authored-by: Eirik Tsarpalis <[email protected]>

Co-authored-by: Stephen Toub <[email protected]>
Co-authored-by: Eirik Tsarpalis <[email protected]>
  • Loading branch information
3 people authored Jan 19, 2021
1 parent f685ce5 commit 6fb9985
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ public static partial class Queryable
public static System.Linq.IQueryable<TSource> Where<TSource>(this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, bool>> predicate) { throw null; }
public static System.Linq.IQueryable<TSource> Where<TSource>(this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, int, bool>> predicate) { throw null; }
public static System.Linq.IQueryable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(this System.Linq.IQueryable<TFirst> source1, System.Collections.Generic.IEnumerable<TSecond> source2) { throw null; }
public static System.Linq.IQueryable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(this System.Linq.IQueryable<TFirst> source1, System.Collections.Generic.IEnumerable<TSecond> source2, System.Collections.Generic.IEnumerable<TThird> source3) { throw null; }
public static System.Linq.IQueryable<TResult> Zip<TFirst, TSecond, TResult>(this System.Linq.IQueryable<TFirst> source1, System.Collections.Generic.IEnumerable<TSecond> source2, System.Linq.Expressions.Expression<System.Func<TFirst, TSecond, TResult>> resultSelector) { throw null; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,12 @@
<property name="Scope">member</property>
<property name="Target">M:System.Linq.CachedReflectionInfo.Zip_TFirst_TSecond_TResult_3(System.Type,System.Type,System.Type)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2060</argument>
<property name="Scope">member</property>
<property name="Target">M:System.Linq.CachedReflectionInfo.Zip_TFirst_TSecond_TThird_3(System.Type,System.Type,System.Type)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2060</argument>
Expand All @@ -662,4 +668,4 @@
<property name="Target">M:System.Linq.TypeHelper.GetStaticMethods(System.Type)</property>
</attribute>
</assembly>
</linker>
</linker>
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,13 @@ public static MethodInfo Zip_TFirst_TSecond_TResult_3(Type TFirst, Type TSecond,
(s_Zip_TFirst_TSecond_TResult_3 = new Func<IQueryable<object>, IEnumerable<object>, Expression<Func<object, object, object>>, IQueryable<object>>(Queryable.Zip).GetMethodInfo().GetGenericMethodDefinition()))
.MakeGenericMethod(TFirst, TSecond, TResult);

private static MethodInfo? s_Zip_TFirst_TSecond_TThird_3;

public static MethodInfo Zip_TFirst_TSecond_TThird_3(Type TFirst, Type TSecond, Type TThird) =>
(s_Zip_TFirst_TSecond_TThird_3 ??
(s_Zip_TFirst_TSecond_TThird_3 = new Func<IQueryable<object>, IEnumerable<object>, IEnumerable<object>, IQueryable<(object, object, object)>>(Queryable.Zip).GetMethodInfo().GetGenericMethodDefinition()))
.MakeGenericMethod(TFirst, TSecond, TThird);


private static MethodInfo? s_SkipLast_TSource_2;

Expand Down
27 changes: 27 additions & 0 deletions src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,33 @@ public static IQueryable<TResult> Zip<TFirst, TSecond, TResult>(this IQueryable<
));
}

/// <summary>
/// Produces a sequence of tuples with elements from the three specified sequences.
/// </summary>
/// <typeparam name="TFirst">The type of the elements of the first input sequence.</typeparam>
/// <typeparam name="TSecond">The type of the elements of the second input sequence.</typeparam>
/// <typeparam name="TThird">The type of the elements of the third input sequence.</typeparam>
/// <param name="source1">The first sequence to merge.</param>
/// <param name="source2">The second sequence to merge.</param>
/// <param name="source3">The third sequence to merge.</param>
/// <returns>A sequence of tuples with elements taken from the first, second and third sequences, in that order.</returns>
[DynamicDependency("Zip`3", typeof(Enumerable))]
public static IQueryable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(this IQueryable<TFirst> source1, IEnumerable<TSecond> source2, IEnumerable<TThird> source3)
{
if (source1 == null)
throw Error.ArgumentNull(nameof(source1));
if (source2 == null)
throw Error.ArgumentNull(nameof(source2));
if (source3 == null)
throw Error.ArgumentNull(nameof(source3));
return source1.Provider.CreateQuery<(TFirst, TSecond, TThird)>(
Expression.Call(
null,
CachedReflectionInfo.Zip_TFirst_TSecond_TThird_3(typeof(TFirst), typeof(TSecond), typeof(TThird)),
source1.Expression, GetSourceExpression(source2), GetSourceExpression(source3)
));
}

[DynamicDependency("Union`1", typeof(Enumerable))]
public static IQueryable<TSource> Union<TSource>(this IQueryable<TSource> source1, IEnumerable<TSource> source2)
{
Expand Down
50 changes: 50 additions & 0 deletions src/libraries/System.Linq.Queryable/tests/ZipTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,55 @@ public void TupleNames()
Assert.Equal(tuple.Item1, tuple.First);
Assert.Equal(tuple.Item2, tuple.Second);
}

[Fact]
public void Zip3_CorrectResults()
{
int[] first = new int[] { 1, 3, 5 };
int[] second = new int[] { 2, 6, 8 };
int[] third = new int[] { 1, 7, 2 };
var expected = new (int, int, int)[] { (1, 2, 1), (3, 6, 7), (5, 8, 2) };
Assert.Equal(expected, first.AsQueryable().Zip(second.AsQueryable(), third.AsQueryable()));
}


[Fact]
public void Zip3_FirstIsNull()
{
IQueryable<int> first = null;
int[] second = new int[] { 2, 6, 8 };
int[] third = new int[] { 1, 7, 2 };
AssertExtensions.Throws<ArgumentNullException>("source1", () => first.Zip(second.AsQueryable(), third.AsQueryable()));
}

[Fact]
public void Zip3_SecondIsNull()
{
int[] first = new int[] { 1, 3, 5 };
IQueryable<int> second = null;
int[] third = new int[] { 1, 7, 2 };
AssertExtensions.Throws<ArgumentNullException>("source2", () => first.AsQueryable().Zip(second, third.AsQueryable()));
}

[Fact]
public void Zip3_ThirdIsNull()
{
int[] first = new int[] { 1, 3, 5 };
int[] second = new int[] { 2, 6, 8 };
IQueryable<int> third = null;
AssertExtensions.Throws<ArgumentNullException>("source3", () => first.AsQueryable().Zip(second.AsQueryable(), third));
}

[Fact]
public void Zip3_TupleNames()
{
int[] first = new int[] { 1 };
int[] second = new int[] { 2 };
int[] third = new int[] { 3 };
var tuple = first.AsQueryable().Zip(second.AsQueryable(), third.AsQueryable()).First();
Assert.Equal(tuple.Item1, tuple.First);
Assert.Equal(tuple.Item2, tuple.Second);
Assert.Equal(tuple.Item3, tuple.Third);
}
}
}
1 change: 1 addition & 0 deletions src/libraries/System.Linq/ref/System.Linq.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ public static System.Collections.Generic.IEnumerable<
public static System.Collections.Generic.IEnumerable<TSource> Where<TSource>(this System.Collections.Generic.IEnumerable<TSource> source, System.Func<TSource, bool> predicate) { throw null; }
public static System.Collections.Generic.IEnumerable<TSource> Where<TSource>(this System.Collections.Generic.IEnumerable<TSource> source, System.Func<TSource, int, bool> predicate) { throw null; }
public static System.Collections.Generic.IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(this System.Collections.Generic.IEnumerable<TFirst> first, System.Collections.Generic.IEnumerable<TSecond> second) { throw null; }
public static System.Collections.Generic.IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(this System.Collections.Generic.IEnumerable<TFirst> first, System.Collections.Generic.IEnumerable<TSecond> second, System.Collections.Generic.IEnumerable<TThird> third) { throw null; }
public static System.Collections.Generic.IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this System.Collections.Generic.IEnumerable<TFirst> first, System.Collections.Generic.IEnumerable<TSecond> second, System.Func<TFirst, TSecond, TResult> resultSelector) { throw null; }
}
public partial interface IGrouping<out TKey, out TElement> : System.Collections.Generic.IEnumerable<TElement>, System.Collections.IEnumerable
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Linq/src/System/Linq/ThrowHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ private static string GetArgumentString(ExceptionArgument argument)
case ExceptionArgument.second: return nameof(ExceptionArgument.second);
case ExceptionArgument.selector: return nameof(ExceptionArgument.selector);
case ExceptionArgument.source: return nameof(ExceptionArgument.source);
case ExceptionArgument.third: return nameof(ExceptionArgument.third);
default:
Debug.Fail("The ExceptionArgument value is not defined.");
return string.Empty;
Expand All @@ -76,5 +77,6 @@ internal enum ExceptionArgument
second,
selector,
source,
third,
}
}
43 changes: 43 additions & 0 deletions src/libraries/System.Linq/src/System/Linq/Zip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,36 @@ public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerabl
return ZipIterator(first, second);
}

/// <summary>
/// Produces a sequence of tuples with elements from the three specified sequences.
/// </summary>
/// <typeparam name="TFirst">The type of the elements of the first input sequence.</typeparam>
/// <typeparam name="TSecond">The type of the elements of the second input sequence.</typeparam>
/// <typeparam name="TThird">The type of the elements of the third input sequence.</typeparam>
/// <param name="first">The first sequence to merge.</param>
/// <param name="second">The second sequence to merge.</param>
/// <param name="third">The third sequence to merge.</param>
/// <returns>A sequence of tuples with elements taken from the first, second, and third sequences, in that order.</returns>
public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third)
{
if (first is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.first);
}

if (second is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.second);
}

if (third is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.third);
}

return ZipIterator(first, second, third);
}

private static IEnumerable<(TFirst First, TSecond Second)> ZipIterator<TFirst, TSecond>(IEnumerable<TFirst> first, IEnumerable<TSecond> second)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
Expand All @@ -65,5 +95,18 @@ private static IEnumerable<TResult> ZipIterator<TFirst, TSecond, TResult>(IEnume
}
}
}

private static IEnumerable<(TFirst First, TSecond Second, TThird Third)> ZipIterator<TFirst, TSecond, TThird>(IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
using (IEnumerator<TThird> e3 = third.GetEnumerator())
{
while (e1.MoveNext() && e2.MoveNext() && e3.MoveNext())
{
yield return (e1.Current, e2.Current, e3.Current);
}
}
}
}
}
126 changes: 122 additions & 4 deletions src/libraries/System.Linq/tests/ZipTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ public void Zip2_ImplicitTypeParameters()
{
IEnumerable<int> first = new int[] { 1, 2, 3 };
IEnumerable<int> second = new int[] { 2, 5, 9 };
IEnumerable<(int, int)> expected = new (int,int)[] { (1,2), (2,5), (3,9) };
IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (2, 5), (3, 9) };

Assert.Equal(expected, first.Zip(second));
}
Expand All @@ -403,7 +403,7 @@ public void Zip2_ExplicitTypeParameters()
{
IEnumerable<int> first = new int[] { 1, 2, 3 };
IEnumerable<int> second = new int[] { 2, 5, 9 };
IEnumerable<(int, int)> expected = new (int,int)[] { (1,2), (2,5), (3,9) };
IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (2, 5), (3, 9) };

Assert.Equal(expected, first.Zip<int, int>(second));
}
Expand Down Expand Up @@ -431,7 +431,7 @@ public void Zip2_ExceptionThrownFromFirstsEnumerator()
{
ThrowsOnMatchEnumerable<int> first = new ThrowsOnMatchEnumerable<int>(new int[] { 1, 3, 3 }, 2);
IEnumerable<int> second = new int[] { 2, 4, 6 };
IEnumerable<(int, int)> expected = new (int,int)[] { (1,2), (3,4), (3,6) };
IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (3, 4), (3, 6) };

Assert.Equal(expected, first.Zip(second));

Expand All @@ -447,7 +447,7 @@ public void Zip2_ExceptionThrownFromSecondsEnumerator()
{
ThrowsOnMatchEnumerable<int> second = new ThrowsOnMatchEnumerable<int>(new int[] { 1, 3, 3 }, 2);
IEnumerable<int> first = new int[] { 2, 4, 6 };
IEnumerable<(int, int)> expected = new (int,int)[] { (2,1), (4,3), (6,3) };
IEnumerable<(int, int)> expected = new (int, int)[] { (2, 1), (4, 3), (6, 3) };

Assert.Equal(expected, first.Zip(second));

Expand Down Expand Up @@ -601,5 +601,123 @@ public void Zip2_TupleNames()
Assert.Equal(t.Item1, t.First);
Assert.Equal(t.Item2, t.Second);
}

[Fact]
public void Zip3_FirstIsNull()
{
IEnumerable<int> first = null;
IEnumerable<int> second = new[] { 4, 5, 6 };
IEnumerable<int> third = new[] { 7, 8, 9 };

AssertExtensions.Throws<ArgumentNullException>("first", () => first.Zip(second, third));
}

[Fact]
public void Zip3_SecondIsNull()
{
IEnumerable<int> first = new[] { 1, 2, 3 };
IEnumerable<int> second = null;
IEnumerable<int> third = new[] { 4, 5, 6 };

AssertExtensions.Throws<ArgumentNullException>("second", () => first.Zip(second, third));
}

[Fact]
public void Zip3_ThirdIsNull()
{
IEnumerable<int> first = new[] { 1, 2, 3 };
IEnumerable<int> second = new[] { 4, 5, 6 };
IEnumerable<int> third = null;

AssertExtensions.Throws<ArgumentNullException>("third", () => first.Zip(second, third));
}

[Fact]
public void Zip3_ThirdEmpty()
{
IEnumerable<int> first = new[] { 1, 2, 3 };
IEnumerable<int> second = new[] { 4, 5, 6 };
IEnumerable<int> third = new int[] { };
IEnumerable<(int, int, int)> expected = new (int, int, int)[] { };

Assert.Equal(expected, first.Zip(second, third));
}

[Fact]
public void Zip3_ImplicitTypeParameters()
{
IEnumerable<int> first = new[] { 1, 2 };
IEnumerable<int> second = new[] { 3, 4 };
IEnumerable<int> third = new[] { 5, 6 };
IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5), (2, 4, 6) };

Assert.Equal(expected, first.Zip(second, third));
}

[Fact]
public void Zip3_ExplicitTypeParameters()
{
IEnumerable<int> first = new[] { 1, 2 };
IEnumerable<int> second = new[] { 3, 4 };
IEnumerable<int> third = new[] { 5, 6 };
IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5), (2, 4, 6) };

Assert.Equal(expected, first.Zip<int, int, int>(second, third));
}

[Fact]
public void Zip3_ThirdOneMore()
{
IEnumerable<int> first = new[] { 1, 2 };
IEnumerable<int> second = new[] { 3, 4 };
IEnumerable<int> third = new[] { 5, 6, 7 };
IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5), (2, 4, 6) };

Assert.Equal(expected, first.Zip(second, third));
}

[Fact]
public void Zip3_ThirdManyMore()
{
IEnumerable<int> first = new[] { 1, 2 };
IEnumerable<int> second = new[] { 3, 4 };
IEnumerable<int> third = new[] { 5, 6, 7, 8 };
IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5), (2, 4, 6) };

Assert.Equal(expected, first.Zip(second, third));
}

[Fact]
public void Zip3_ThirdOneLess()
{
IEnumerable<int> first = new[] { 1, 2 };
IEnumerable<int> second = new[] { 3, 4 };
IEnumerable<int> third = new[] { 5 };
IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5) };

Assert.Equal(expected, first.Zip(second, third));
}

[Fact]
public void Zip3_ThirdManyLess()
{
IEnumerable<int> first = new[] { 1, 2, 3 };
IEnumerable<int> second = new[] { 3, 4, 5 };
IEnumerable<int> third = new[] { 5 };
IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5) };

Assert.Equal(expected, first.Zip(second, third));
}

[Fact]
public void Zip3_RunOnce()
{
IEnumerable<int> first = new[] { 1, 2 };
IEnumerable<int> second = new[] { 3, 4 };
IEnumerable<int> third = new[] { 5, 6 };
IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5), (2, 4, 6) };

Assert.Equal(expected, first.RunOnce().Zip(second.RunOnce(), third.RunOnce()));
}
}
}

0 comments on commit 6fb9985

Please sign in to comment.