Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More arities for tuple returning zip extension method #47147

Merged
merged 10 commits into from
Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to comment saying ??= could be used to simplify this, but I see you're just following the pattern used in a bunch of other cases above. We can clean it up later.



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()));
}
}
}