Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
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 @@ -1669,7 +1669,9 @@ public static void MatchSequencePattern()
"AsEnumerable",
"ToList",
"Fold",
"LeftJoin"
"LeftJoin",
"Append",
"Prepend",
}
).ToList();

Expand Down
2 changes: 2 additions & 0 deletions src/System.Linq/ref/System.Linq.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public static partial class Enumerable
public static float Average<TSource>(this System.Collections.Generic.IEnumerable<TSource> source, System.Func<TSource, float> selector) { return default(float); }
public static System.Collections.Generic.IEnumerable<TResult> Cast<TResult>(this System.Collections.IEnumerable source) { return default(System.Collections.Generic.IEnumerable<TResult>); }
public static System.Collections.Generic.IEnumerable<TSource> Concat<TSource>(this System.Collections.Generic.IEnumerable<TSource> first, System.Collections.Generic.IEnumerable<TSource> second) { return default(System.Collections.Generic.IEnumerable<TSource>); }
public static System.Collections.Generic.IEnumerable<TSource> Append<TSource>(this System.Collections.Generic.IEnumerable<TSource> source, TSource element) { return default(System.Collections.Generic.IEnumerable<TSource>); }
public static System.Collections.Generic.IEnumerable<TSource> Prepend<TSource>(this System.Collections.Generic.IEnumerable<TSource> source, TSource element) { return default(System.Collections.Generic.IEnumerable<TSource>); }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need to bump a version number in a .csproj somewhere?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I am not sure if it comes from .csproj
is AssemblyVersion honored by the build?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is now 4.0.2.0
I can make it 4.0.3.0 or 4.1.0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think for new APIs we bump minor version. @ericstj, correct?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

will make it 4.1.0 since it is a compatible addition.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes it should be 4.1, probably should have already be updated to 4.1. Note that when you do this you are going to break our internal builds. I see you have already merged so please make sure to handle the internal build issues.

public static bool Contains<TSource>(this System.Collections.Generic.IEnumerable<TSource> source, TSource value) { return default(bool); }
public static bool Contains<TSource>(this System.Collections.Generic.IEnumerable<TSource> source, TSource value, System.Collections.Generic.IEqualityComparer<TSource> comparer) { return default(bool); }
public static int Count<TSource>(this System.Collections.Generic.IEnumerable<TSource> source) { return default(int); }
Expand Down
2 changes: 1 addition & 1 deletion src/System.Linq/ref/System.Linq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<AssemblyVersion>4.0.2.0</AssemblyVersion>
<AssemblyVersion>4.1.0.0</AssemblyVersion>
<OutputType>Library</OutputType>
<PackageTargetFramework>dotnet5.1</PackageTargetFramework>
<NuGetTargetMoniker>.NETPlatform,Version=v5.1</NuGetTargetMoniker>
Expand Down
2 changes: 1 addition & 1 deletion src/System.Linq/src/System.Linq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<PropertyGroup>
<ProjectGuid>{CA488507-3B6E-4494-B7BE-7B4EEEB2C4D1}</ProjectGuid>
<AssemblyName>System.Linq</AssemblyName>
<AssemblyVersion>4.0.2.0</AssemblyVersion>
<AssemblyVersion>4.1.0.0</AssemblyVersion>
<RootNamespace>System.Linq</RootNamespace>
<PackageTargetFramework>dotnet5.4</PackageTargetFramework>
</PropertyGroup>
Expand Down
24 changes: 24 additions & 0 deletions src/System.Linq/src/System/Linq/Enumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,30 @@ private static IEnumerable<TSource> ConcatIterator<TSource>(IEnumerable<TSource>
foreach (TSource element in second) yield return element;
}

public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> source, TSource element)
{
if (source == null) throw Error.ArgumentNull("source");
return AppendIterator<TSource>(source, element);
}

private static IEnumerable<TSource> AppendIterator<TSource>(IEnumerable<TSource> source, TSource element)
{
foreach (TSource e1 in source) yield return e1;
yield return element;
}

public static IEnumerable<TSource> Prepend<TSource>(this IEnumerable<TSource> source, TSource element)
{
if (source == null) throw Error.ArgumentNull("source");
return PrependIterator<TSource>(source, element);
}

private static IEnumerable<TSource> PrependIterator<TSource>(IEnumerable<TSource> source, TSource element)
{
yield return element;
foreach (TSource e1 in source) yield return e1;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: it's fine, but why e1 as the variable name?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

because "element" is already taken by the parameter :-)
I saw other methods use e1, e2 ... in such situations.

}

public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
{
if (first == null) throw Error.ArgumentNull("first");
Expand Down
119 changes: 119 additions & 0 deletions src/System.Linq/tests/AppendPrependTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// 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;
using System.Collections.Generic;
using Xunit;

namespace System.Linq.Tests
{
public class AppendPrependTests : EnumerableTests
{
[Fact]
public void SameResultsRepeatCallsIntQueryAppend()
{
var q1 = from x1 in new int?[] { 2, 3, null, 2, null, 4, 5 }
select x1;

Assert.Equal(q1.Append(42), q1.Append(42));
Assert.Equal(q1.Append(42), q1.Concat(new int?[] { 42 }));
}

[Fact]
public void SameResultsRepeatCallsIntQueryPrepend()
{
var q1 = from x1 in new int?[] { 2, 3, null, 2, null, 4, 5 }
select x1;

Assert.Equal(q1.Prepend(42), q1.Prepend(42));
Assert.Equal(q1.Prepend(42), (new int?[] { 42 }).Concat(q1));
}

[Fact]
public void SameResultsRepeatCallsStringQueryAppend()
{
var q1 = from x1 in new[] { "AAA", String.Empty, "q", "C", "#", "!@#$%^", "0987654321", "Calling Twice" }
select x1;

Assert.Equal(q1.Append("hi"), q1.Append("hi"));
Assert.Equal(q1.Append("hi"), q1.Concat(new string[] { "hi" }));
}

[Fact]
public void SameResultsRepeatCallsStringQueryPrepend()
{
var q1 = from x1 in new[] { "AAA", String.Empty, "q", "C", "#", "!@#$%^", "0987654321", "Calling Twice" }
select x1;

Assert.Equal(q1.Prepend("hi"), q1.Prepend("hi"));
Assert.Equal(q1.Prepend("hi"), (new string[] { "hi" }).Concat(q1));
}

[Fact]
public void EmptyAppend()
{
int[] first = { };
Assert.Single(first.Append(42), 42);
}

[Fact]
public void EmptyPrepend()
{
string[] first = { };
Assert.Single(first.Prepend("aa"), "aa");
}

[Fact]
public void PrependNoIteratingSourceBeforeFirstItem()
{
var ie = new List<int>();
var prepended = (from i in ie select i).Prepend(4);

ie.Add(42);

Assert.Equal(prepended, ie.Prepend(4));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe this test should assert that GetEnumerator() wasn't called prior to the second element being iterated. I can't see someone finding a way that iterating it had any advantage (quite the opposite), but eagerly calling GetEnumerator() might possibly profile as a slight improvement (or simply an implementation short-cut with less typing!), would have worse potential problems than this, and would cover everything this covers too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The idea was primarily to catch attempts to "probe" the source for emptiness or something like that before yielding the prepended element. It could be a problem if the whole thing is used in TakeWhile and if fetching elements from the source is somehow observable.

Considering how simple this operator is, this kind of changes are unlikely to happen unnoticed. I just added a testcase since I thought about the scenario while making changes.
Overall, I think we should have enough tests here already.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, that's throwing out ideas, not an objection. I'm still confused by MatchSequencePattern not failing, though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm still confused by MatchSequencePattern not failing, though.

Probably because System.Linq.Expressions doesn't have a P2P reference to System.Linq and is building against the bits in the package rather than what's live in the repo.
cc: @weshaggard

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah. So it won't fail until after this is merged? I was pretty sure it failed before under similar circumstances.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I will add the new operators to the exclusion list.
We can consider adding them to IQueryable, but not in this change. It is not 100% match anyways.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No, I don't think they're a match, being akin to Repeat and Range in producing elements.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Modulo the query operators that are not extension methods on IEnumerable<T>, as far as I know, the IQueryable<T> surface is complete, so we should likely bring it on par for these operators (which are extension methods) in a separate change as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should probably open an issue to discuss that, but I'm inclined to think that there's no good way to universally do that across providers except by casting back to IEnumerable<T> and calling into the methods here anyway, which this already provides for given the inheritance order.

}

[Fact]
public void ForcedToEnumeratorDoesntEnumeratePrepend()
{
var iterator = NumberRangeGuaranteedNotCollectionType(0, 3).Prepend(4);
// Don't insist on this behaviour, but check it's correct if it happens
var en = iterator as IEnumerator<int>;
Assert.False(en != null && en.MoveNext());
}

[Fact]
public void ForcedToEnumeratorDoesntEnumerateAppend()
{
var iterator = NumberRangeGuaranteedNotCollectionType(0, 3).Append(4);
// Don't insist on this behaviour, but check it's correct if it happens
var en = iterator as IEnumerator<int>;
Assert.False(en != null && en.MoveNext());
}

[Fact]
public void SourceNull()
{
Assert.Throws<ArgumentNullException>("source", () => ((IEnumerable<int>)null).Append(1));
Assert.Throws<ArgumentNullException>("source", () => ((IEnumerable<int>)null).Prepend(1));
}

[Fact]
public void Combined()
{
var v = "foo".Append('1').Append('2').Prepend('3').Concat("qq".Append('Q').Prepend('W'));

Assert.Equal(v.ToArray(), "3foo12WqqQ".ToArray());

var v1 = "a".Append('b').Append('c').Append('d');

Assert.Equal(v1.ToArray(), "abcd".ToArray());

var v2 = "a".Prepend('b').Prepend('c').Prepend('d');

Assert.Equal(v2.ToArray(), "dcba".ToArray());
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Consider adding a few tests that use Append and Prepend multiple times, e.g. e.Append(1).Append(2).Append(3), as well as intermixed, e.g. e.Append(2).Prepend(1).Append(3), etc. You might also inject some Concats in there. I'm imaging at some point we might want to create some optimized paths for when Append, Prepend, and Concat are used repetitively.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Will do.
I mostly replicated test coverage of Concat, but couple more tests like this will not hurt.

5 changes: 3 additions & 2 deletions src/System.Linq/tests/System.Linq.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
Expand All @@ -20,6 +20,7 @@
<Compile Include="AsEnumerableTests.cs" />
<Compile Include="AverageTests.cs" />
<Compile Include="CastTests.cs" />
<Compile Include="AppendPrependTests.cs" />
<Compile Include="ConcatTests.cs" />
<Compile Include="ContainsTests.cs" />
<Compile Include="CountTests.cs" />
Expand Down Expand Up @@ -89,4 +90,4 @@
</ProjectReference>
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
</Project>