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

Add LINQ APIs for Index and Range (#28776) #48559

Merged
merged 41 commits into from
Feb 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4f846fe
Implement dotnet/runtime#28776: Implement LINQ APIs for index and range.
Dixin Jan 27, 2021
0825e9a
Implement dotnet#28776: Implement LINQ APIs for index and range.
Dixin Jan 27, 2021
ae75de2
Implement dotnet#28776: LINQ APIs for index and range.
Dixin Jan 27, 2021
2fa8291
Implement dotnet#28776: LINQ APIs for index and range.
Dixin Jan 27, 2021
58ba22c
Implement dotnet#28776: LINQ APIs for index and range.
Dixin Jan 27, 2021
4a0698c
Implement dotnet#28776: LINQ APIs for index and range.
Dixin Feb 2, 2021
6317a99
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 2, 2021
62570c9
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 3, 2021
ee298f7
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 3, 2021
9be2f22
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 3, 2021
1cdf0b8
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 3, 2021
a11a3db
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 3, 2021
4711c9a
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 4, 2021
3d66411
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 4, 2021
fdabe87
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 5, 2021
83c903c
Merge branch 'master' of https://github.com/dotnet/runtime into linq-…
Dixin Feb 5, 2021
683a2ed
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 5, 2021
28aadde
Merge branch 'master' of https://github.com/dotnet/runtime into linq-…
Dixin Feb 5, 2021
ee4dff1
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 6, 2021
fb5d551
Merge branch 'master' of https://github.com/dotnet/runtime into linq-…
Dixin Feb 6, 2021
e9922bf
Implement dotnet/runtime#28776: LINQ APIs for index and range. Code r…
Dixin Feb 6, 2021
52b8617
Implement dotnet/runtime#28776: LINQ APIs for index and range. Code r…
Dixin Feb 7, 2021
60dff47
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 9, 2021
3d839c5
Implement dotnet/runtime#28776: LINQ APIs for index and range. Code r…
Dixin Feb 12, 2021
cb7553d
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 14, 2021
8a69aeb
Merge branch 'master' of https://github.com/dotnet/runtime into linq-…
Dixin Feb 14, 2021
e17dbd9
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 14, 2021
fe61d59
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 14, 2021
adf6121
Merge branch 'master' of https://github.com/dotnet/runtime into linq-…
Dixin Feb 15, 2021
2ca8ca6
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 15, 2021
6374006
Implement dotnet/runtime#28776: LINQ APIs for index and range.
Dixin Feb 16, 2021
36e1f95
Implement dotnet/runtime#28776: LINQ APIs for index and range. Update…
Dixin Feb 17, 2021
0db2ce3
Implement dotnet#28776: LINQ APIs for index and range. Update Element…
Dixin Feb 17, 2021
1ef4569
Implement dotnet/runtime#28776: LINQ APIs for index and range. Update…
Dixin Feb 17, 2021
4f98512
Implement dotnet/runtime#28776: LINQ APIs for index and range. Add un…
Dixin Feb 18, 2021
8727728
Implement dotnet/runtime#28776: LINQ APIs for index and range. Update…
Dixin Feb 19, 2021
9a241b1
Implement dotnet/runtime#28776: LINQ APIs for index and range. Update…
Dixin Feb 19, 2021
a6180dd
Implement dotnet/runtime#28776: LINQ APIs for index and range. Update…
Dixin Feb 19, 2021
417617a
Merge branch 'master' of https://github.com/dotnet/runtime into linq-…
Dixin Feb 19, 2021
5d8279a
Implement dotnet/runtime#28776: LINQ APIs for index and range. Update…
Dixin Feb 19, 2021
88d55cd
Implement #28776: LINQ APIs for index and range. Update unit tests.
Dixin Feb 20, 2021
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 @@ -80,7 +80,9 @@ public static partial class Queryable
public static System.Linq.IQueryable<TSource> Distinct<TSource>(this System.Linq.IQueryable<TSource> source) { throw null; }
public static System.Linq.IQueryable<TSource> Distinct<TSource>(this System.Linq.IQueryable<TSource> source, System.Collections.Generic.IEqualityComparer<TSource>? comparer) { throw null; }
public static TSource? ElementAtOrDefault<TSource>(this System.Linq.IQueryable<TSource> source, int index) { throw null; }
public static TSource? ElementAtOrDefault<TSource>(this System.Linq.IQueryable<TSource> source, System.Index index) { throw null; }
public static TSource ElementAt<TSource>(this System.Linq.IQueryable<TSource> source, int index) { throw null; }
public static TSource ElementAt<TSource>(this System.Linq.IQueryable<TSource> source, System.Index index) { throw null; }
public static System.Linq.IQueryable<TSource> Except<TSource>(this System.Linq.IQueryable<TSource> source1, System.Collections.Generic.IEnumerable<TSource> source2) { throw null; }
public static System.Linq.IQueryable<TSource> Except<TSource>(this System.Linq.IQueryable<TSource> source1, System.Collections.Generic.IEnumerable<TSource> source2, System.Collections.Generic.IEqualityComparer<TSource>? comparer) { throw null; }
public static TSource? FirstOrDefault<TSource>(this System.Linq.IQueryable<TSource> source) { throw null; }
Expand Down Expand Up @@ -158,6 +160,7 @@ public static partial class Queryable
public static System.Linq.IQueryable<TSource> TakeWhile<TSource>(this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, bool>> predicate) { throw null; }
public static System.Linq.IQueryable<TSource> TakeWhile<TSource>(this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, int, bool>> predicate) { throw null; }
public static System.Linq.IQueryable<TSource> Take<TSource>(this System.Linq.IQueryable<TSource> source, int count) { throw null; }
public static System.Linq.IQueryable<TSource> Take<TSource>(this System.Linq.IQueryable<TSource> source, System.Range range) { throw null; }
public static System.Linq.IOrderedQueryable<TSource> ThenByDescending<TSource, TKey>(this System.Linq.IOrderedQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, TKey>> keySelector) { throw null; }
public static System.Linq.IOrderedQueryable<TSource> ThenByDescending<TSource, TKey>(this System.Linq.IOrderedQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, TKey>> keySelector, System.Collections.Generic.IComparer<TKey>? comparer) { throw null; }
public static System.Linq.IOrderedQueryable<TSource> ThenBy<TSource, TKey>(this System.Linq.IOrderedQueryable<TSource> source, System.Linq.Expressions.Expression<System.Func<TSource, TKey>> keySelector) { throw null; }
Expand Down
414 changes: 152 additions & 262 deletions src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs

Large diffs are not rendered by default.

76 changes: 73 additions & 3 deletions src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -388,11 +388,32 @@ public static IQueryable<TSource> Take<TSource>(this IQueryable<TSource> source,
return source.Provider.CreateQuery<TSource>(
Expression.Call(
null,
CachedReflectionInfo.Take_TSource_2(typeof(TSource)),
CachedReflectionInfo.Take_Int32_TSource_2(typeof(TSource)),
source.Expression, Expression.Constant(count)
));
}

/// <summary>Returns a specified range of contiguous elements from a sequence.</summary>
/// <param name="source">The sequence to return elements from.</param>
/// <param name="range">The range of elements to return, which has start and end indexes either from the start or the end.</param>
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
/// <exception cref="ArgumentNullException">
/// <paramref name="source" /> is <see langword="null" />.
/// </exception>
/// <returns>An <see cref="IQueryable{T}" /> that contains the specified <paramref name="range" /> of elements from the <paramref name="source" /> sequence.</returns>
[DynamicDependency("Take`1", typeof(Enumerable))]
public static IQueryable<TSource> Take<TSource>(this IQueryable<TSource> source, Range range)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
return source.Provider.CreateQuery<TSource>(
Expression.Call(
null,
CachedReflectionInfo.Take_Range_TSource_2(typeof(TSource)),
source.Expression, Expression.Constant(range)
));
}

[DynamicDependency("TakeWhile`1", typeof(Enumerable))]
public static IQueryable<TSource> TakeWhile<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
{
Expand Down Expand Up @@ -972,7 +993,33 @@ public static TSource ElementAt<TSource>(this IQueryable<TSource> source, int in
return source.Provider.Execute<TSource>(
Expression.Call(
null,
CachedReflectionInfo.ElementAt_TSource_2(typeof(TSource)),
CachedReflectionInfo.ElementAt_Int32_TSource_2(typeof(TSource)),
source.Expression, Expression.Constant(index)
));
}

/// <summary>Returns the element at a specified index in a sequence.</summary>
/// <param name="source">An <see cref="IQueryable{T}" /> to return an element from.</param>
/// <param name="index">The index of the element to retrieve, which is either from the start or the end.</param>
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
/// <exception cref="ArgumentNullException">
/// <paramref name="source" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index" /> is outside the bounds of the <paramref name="source" /> sequence.
/// </exception>
/// <returns>The element at the specified position in the <paramref name="source" /> sequence.</returns>
[DynamicDependency("ElementAt`1", typeof(Enumerable))]
public static TSource ElementAt<TSource>(this IQueryable<TSource> source, Index index)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
if (index.IsFromEnd && index.Value == 0)
throw Error.ArgumentOutOfRange(nameof(index));
return source.Provider.Execute<TSource>(
Expression.Call(
null,
CachedReflectionInfo.ElementAt_Index_TSource_2(typeof(TSource)),
source.Expression, Expression.Constant(index)
));
}
Expand All @@ -985,7 +1032,30 @@ public static TSource ElementAt<TSource>(this IQueryable<TSource> source, int in
return source.Provider.Execute<TSource>(
Expression.Call(
null,
CachedReflectionInfo.ElementAtOrDefault_TSource_2(typeof(TSource)),
CachedReflectionInfo.ElementAtOrDefault_Int32_TSource_2(typeof(TSource)),
source.Expression, Expression.Constant(index)
));
}

/// <summary>Returns the element at a specified index in a sequence or a default value if the index is out of range.</summary>
/// <param name="source">An <see cref="IQueryable{T}" /> to return an element from.</param>
/// <param name="index">The index of the element to retrieve, which is either from the start or the end.</param>
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
/// <exception cref="ArgumentNullException">
/// <paramref name="source" /> is <see langword="null" />.
/// </exception>
/// <returns>
/// <see langword="default" /> if index is outside the bounds of the <paramref name="source" /> sequence; otherwise, the element at the specified position in the <paramref name="source" /> sequence.
/// </returns>
[DynamicDependency("ElementAtOrDefault`1", typeof(Enumerable))]
public static TSource? ElementAtOrDefault<TSource>(this IQueryable<TSource> source, Index index)
{
if (source == null)
throw Error.ArgumentNull(nameof(source));
return source.Provider.Execute<TSource>(
Expression.Call(
null,
CachedReflectionInfo.ElementAtOrDefault_Index_TSource_2(typeof(TSource)),
source.Expression, Expression.Constant(index)
));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,39 @@ namespace System.Linq.Tests
public class ElementAtOrDefaultTests : EnumerableBasedTests
{
[Fact]
public void IndexNegative()
public void IndexInvalid()
{
int?[] source = { 9, 8 };

Assert.Null(source.AsQueryable().ElementAtOrDefault(-1));
Assert.Null(source.AsQueryable().ElementAtOrDefault(int.MinValue));
Assert.Null(source.AsQueryable().ElementAtOrDefault(3));
Assert.Null(source.AsQueryable().ElementAtOrDefault(int.MaxValue));

Assert.Null(source.AsQueryable().ElementAtOrDefault(^3));
Assert.Null(source.AsQueryable().ElementAtOrDefault(^int.MaxValue));
Assert.Null(source.AsQueryable().ElementAtOrDefault(new Index(3)));
Assert.Null(source.AsQueryable().ElementAtOrDefault(new Index(int.MaxValue)));
}

[Fact]
public void IndexEqualsCount()
{
int[] source = { 1, 2, 3, 4 };

Assert.Equal(default(int), source.AsQueryable().ElementAtOrDefault(source.Length));
Assert.Equal(default, source.AsQueryable().ElementAtOrDefault(source.Length));
Assert.Equal(default, source.AsQueryable().ElementAtOrDefault(new Index(source.Length)));
Assert.Equal(default, source.AsQueryable().ElementAtOrDefault(^0));
}

[Fact]
public void EmptyIndexZero()
{
int[] source = { };

Assert.Equal(default(int), source.AsQueryable().ElementAtOrDefault(0));
Assert.Equal(default, source.AsQueryable().ElementAtOrDefault(0));
Assert.Equal(default, source.AsQueryable().ElementAtOrDefault(new Index(0)));
Assert.Equal(default, source.AsQueryable().ElementAtOrDefault(^0));
}

[Fact]
Expand All @@ -37,6 +49,8 @@ public void SingleElementIndexZero()
int[] source = { -4 };

Assert.Equal(-4, source.ElementAtOrDefault(0));
Assert.Equal(-4, source.ElementAtOrDefault(new Index(0)));
Assert.Equal(-4, source.ElementAtOrDefault(^1));
}

[Fact]
Expand All @@ -45,19 +59,29 @@ public void ManyElementsIndexTargetsLast()
int[] source = { 9, 8, 0, -5, 10 };

Assert.Equal(10, source.AsQueryable().ElementAtOrDefault(source.Length - 1));
Assert.Equal(10, source.AsQueryable().ElementAtOrDefault(new Index(source.Length - 1)));
Assert.Equal(10, source.AsQueryable().ElementAtOrDefault(^1));
}

[Fact]
public void NullSource()
{
AssertExtensions.Throws<ArgumentNullException>("source", () => ((IQueryable<int>)null).ElementAtOrDefault(2));
AssertExtensions.Throws<ArgumentNullException>("source", () => ((IQueryable<int>)null).ElementAtOrDefault(new Index(2)));
AssertExtensions.Throws<ArgumentNullException>("source", () => ((IQueryable<int>)null).ElementAtOrDefault(^2));
}

[Fact]
public void ElementAtOrDefault()
{
var val = (new int[] { 0, 2, 1 }).AsQueryable().ElementAtOrDefault(1);
Assert.Equal(2, val);
var val1 = new[] { 0, 2, 1 }.AsQueryable().ElementAtOrDefault(1);
Assert.Equal(2, val1);

var val2 = new[] { 0, 2, 1 }.AsQueryable().ElementAtOrDefault(new Index(1));
Assert.Equal(2, val2);

var val3 = new[] { 0, 2, 1 }.AsQueryable().ElementAtOrDefault(^2);
Assert.Equal(2, val3);
}
}
}
22 changes: 19 additions & 3 deletions src/libraries/System.Linq.Queryable/tests/ElementAtTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Xunit;

namespace System.Linq.Tests
Expand All @@ -14,6 +13,7 @@ public void IndexNegative()
int?[] source = { 9, 8 };

AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => source.AsQueryable().ElementAt(-1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => source.AsQueryable().ElementAt(^3));
}

[Fact]
Expand All @@ -22,6 +22,8 @@ public void IndexEqualsCount()
int[] source = { 1, 2, 3, 4 };

AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => source.AsQueryable().ElementAt(source.Length));
AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => source.AsQueryable().ElementAt(new Index(source.Length)));
AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => source.AsQueryable().ElementAt(^0));
}

[Fact]
Expand All @@ -30,6 +32,8 @@ public void EmptyIndexZero()
int[] source = { };

AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => source.AsQueryable().ElementAt(0));
AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => source.AsQueryable().ElementAt(new Index(0)));
AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => source.AsQueryable().ElementAt(^0));
}

[Fact]
Expand All @@ -38,6 +42,8 @@ public void SingleElementIndexZero()
int[] source = { -4 };

Assert.Equal(-4, source.AsQueryable().ElementAt(0));
Assert.Equal(-4, source.AsQueryable().ElementAt(new Index(0)));
Assert.Equal(-4, source.AsQueryable().ElementAt(^1));
}

[Fact]
Expand All @@ -46,19 +52,29 @@ public void ManyElementsIndexTargetsLast()
int[] source = { 9, 8, 0, -5, 10 };

Assert.Equal(10, source.AsQueryable().ElementAt(source.Length - 1));
Assert.Equal(10, source.AsQueryable().ElementAt(source.Length - 1));
Assert.Equal(10, source.AsQueryable().ElementAt(^1));
}

[Fact]
public void NullSource()
{
AssertExtensions.Throws<ArgumentNullException>("source", () => ((IQueryable<int>)null).ElementAt(2));
AssertExtensions.Throws<ArgumentNullException>("source", () => ((IQueryable<int>)null).ElementAt(new Index(2)));
AssertExtensions.Throws<ArgumentNullException>("source", () => ((IQueryable<int>)null).ElementAt(^2));
}

[Fact]
public void ElementAt()
{
var val = (new int[] { 0, 2, 1 }).AsQueryable().ElementAt(1);
Assert.Equal(2, val);
var val1 = new[] { 0, 2, 1 }.AsQueryable().ElementAt(1);
Assert.Equal(2, val1);

var val2 = new[] { 0, 2, 1 }.AsQueryable().ElementAt(new Index(1));
Assert.Equal(2, val2);

var val3 = new[] { 0, 2, 1 }.AsQueryable().ElementAt(^2);
Assert.Equal(2, val3);
}
}
}
36 changes: 34 additions & 2 deletions src/libraries/System.Linq.Queryable/tests/TakeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,52 @@ public void SourceNonEmptyTakeAllButOne()
int[] expected = { 2, 5, 9 };

Assert.Equal(expected, source.AsQueryable().Take(3));
Assert.Equal(expected, source.AsQueryable().Take(0..3));
Assert.Equal(expected, source.AsQueryable().Take(^4..3));
Assert.Equal(expected, source.AsQueryable().Take(0..^1));
Assert.Equal(expected, source.AsQueryable().Take(^4..^1));
}

[Fact]
public void ThrowsOnNullSource()
{
IQueryable<int> source = null;
AssertExtensions.Throws<ArgumentNullException>("source", () => source.Take(5));
AssertExtensions.Throws<ArgumentNullException>("source", () => source.Take(0..5));
AssertExtensions.Throws<ArgumentNullException>("source", () => source.Take(^5..5));
AssertExtensions.Throws<ArgumentNullException>("source", () => source.Take(0..^0));
AssertExtensions.Throws<ArgumentNullException>("source", () => source.Take(^5..^0));
}

[Fact]
public void Take()
{
var count = (new int[] { 0, 1, 2 }).AsQueryable().Take(2).Count();
Assert.Equal(2, count);
var count1 = new[] { 0, 1, 2 }.AsQueryable().Take(2).Count();
Assert.Equal(2, count1);

var count2 = new[] { 0, 1, 2 }.AsQueryable().Take(0..2).Count();
Assert.Equal(2, count2);

var count3 = new[] { 0, 1, 2 }.AsQueryable().Take(^3..2).Count();
Assert.Equal(2, count3);

var count4 = new[] { 0, 1, 2 }.AsQueryable().Take(0..^1).Count();
Assert.Equal(2, count4);

var count5 = new[] { 0, 1, 2 }.AsQueryable().Take(^3..^1).Count();
Assert.Equal(2, count5);

var count6 = new[] { 0, 1, 2 }.AsQueryable().Take(1..3).Count();
Assert.Equal(2, count6);

var count7 = new[] { 0, 1, 2 }.AsQueryable().Take(^2..3).Count();
Assert.Equal(2, count7);

var count8 = new[] { 0, 1, 2 }.AsQueryable().Take(1..^0).Count();
Assert.Equal(2, count8);

var count9 = new[] { 0, 1, 2 }.AsQueryable().Take(^2..^0).Count();
Assert.Equal(2, count9);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public static void CachedReflectionInfoMethodsNoAnnotations()
.Where(m => m.GetParameters().Length > 0);

// If you are adding a new method to this class, ensure the method meets these requirements
Assert.Equal(108, methods.Count());
Assert.Equal(111, methods.Count());
foreach (MethodInfo method in methods)
{
ParameterInfo[] parameters = method.GetParameters();
Expand Down
Loading