Skip to content
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
30 changes: 11 additions & 19 deletions src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ public override TSource[] ToArray()
public override List<TSource> ToList()
{
int count = GetCount(onlyIfCheap: true);

if (count == 1)
{
// If GetCount returns 1, then _source is empty and only _item should be returned
return new List<TSource>(1) { _item };
Comment thread
eiriktsarpalis marked this conversation as resolved.
}

List<TSource> list = count == -1 ? new List<TSource>() : new List<TSource>(count);
if (!_appending)
{
Expand Down Expand Up @@ -122,17 +129,8 @@ private TSource[] LazyToArray()

TSource[] array = builder.ToArray();

int index = 0;
for (SingleLinkedNode<TSource>? node = _prepended; node != null; node = node.Linked)
{
array[index++] = node.Item;
}

index = array.Length - 1;
for (SingleLinkedNode<TSource>? node = _appended; node != null; node = node.Linked)
{
array[index--] = node.Item;
}
_prepended?.Fill(array);
_appended?.FillReversed(array);

return array;
}
Expand Down Expand Up @@ -181,17 +179,11 @@ public override List<TSource> ToList()
int count = GetCount(onlyIfCheap: true);
List<TSource> list = count == -1 ? new List<TSource>() : new List<TSource>(count);

for (SingleLinkedNode<TSource>? node = _prepended; node != null; node = node.Linked)
{
list.Add(node.Item);
}
_prepended?.Fill(SetCountAndGetSpan(list, _prependCount));

list.AddRange(_source);

if (_appended != null)
{
list.AddRange(_appended.ToArray(_appendCount));
}
_appended?.FillReversed(SetCountAndGetSpan(list, list.Count + _appendCount));

return list;
}
Expand Down
44 changes: 25 additions & 19 deletions src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,16 @@ public partial class Lookup<TKey, TElement> : IIListProvider<IGrouping<TKey, TEl
{
IGrouping<TKey, TElement>[] IIListProvider<IGrouping<TKey, TElement>>.ToArray()
{
IGrouping<TKey, TElement>[] array = new IGrouping<TKey, TElement>[_count];
int index = 0;
Grouping<TKey, TElement>? g = _lastGrouping;
if (g != null)
IGrouping<TKey, TElement>[] array;
if (_count > 0)
{
do
{
g = g._next;
Debug.Assert(g != null);

array[index] = g;
++index;
}
while (g != _lastGrouping);
array = new IGrouping<TKey, TElement>[_count];
Fill(_lastGrouping, array);
}
else
{
array = Array.Empty<IGrouping<TKey, TElement>>();
}

return array;
}

Expand Down Expand Up @@ -53,21 +47,33 @@ internal TResult[] ToArray<TResult>(Func<TKey, IEnumerable<TElement>, TResult> r

List<IGrouping<TKey, TElement>> IIListProvider<IGrouping<TKey, TElement>>.ToList()
{
List<IGrouping<TKey, TElement>> list = new List<IGrouping<TKey, TElement>>(_count);
Grouping<TKey, TElement>? g = _lastGrouping;
var list = new List<IGrouping<TKey, TElement>>(_count);
if (_count > 0)
{
Fill(_lastGrouping, Enumerable.SetCountAndGetSpan(list, _count));
}

return list;
}

private static void Fill(Grouping<TKey, TElement>? lastGrouping, Span<IGrouping<TKey, TElement>> results)
{
int index = 0;
Grouping<TKey, TElement>? g = lastGrouping;
if (g != null)
{
do
{
g = g._next;
Debug.Assert(g != null);

list.Add(g);
results[index] = g;
++index;
}
while (g != _lastGrouping);
while (g != lastGrouping);
}

return list;
Debug.Assert(index == results.Length, "All list elements were not initialized.");
}

int IIListProvider<IGrouping<TKey, TElement>>.GetCount(bool onlyIfCheap) => _count;
Expand Down
7 changes: 6 additions & 1 deletion src/libraries/System.Linq/src/System/Linq/Lookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,20 @@ internal List<TResult> ToList<TResult>(Func<TKey, IEnumerable<TElement>, TResult
Grouping<TKey, TElement>? g = _lastGrouping;
if (g != null)
{
Span<TResult> span = Enumerable.SetCountAndGetSpan(list, _count);
int index = 0;
do
{
g = g._next;

Debug.Assert(g != null);
g.Trim();
list.Add(resultSelector(g._key, g._elements));
span[index] = resultSelector(g._key, g._elements);
++index;
}
while (g != _lastGrouping);

Debug.Assert(index == _count, "All list elements were not initialized.");
}

return list;
Expand Down
29 changes: 18 additions & 11 deletions src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -611,13 +611,7 @@ private TResult[] PreallocatingToArray(int count)
Debug.Assert(count == _source.GetCount(onlyIfCheap: true));

TResult[] array = new TResult[count];
int index = 0;
foreach (TSource input in _source)
{
array[index] = _selector(input);
++index;
}

Fill(_source, array, _selector);
return array;
}

Expand All @@ -640,20 +634,33 @@ public List<TResult> ToList()
{
case -1:
list = new List<TResult>();
foreach (TSource input in _source)
{
list.Add(_selector(input));
}
break;
case 0:
return new List<TResult>();
list = new List<TResult>();
break;
default:
list = new List<TResult>(count);
Fill(_source, SetCountAndGetSpan(list, count), _selector);
break;
}

foreach (TSource input in _source)
return list;
}

private static void Fill(IPartition<TSource> source, Span<TResult> results, Func<TSource, TResult> func)
{
int index = 0;
foreach (TSource item in source)
{
list.Add(_selector(input));
results[index] = func(item);
++index;
}

return list;
Debug.Assert(index == results.Length, "All list elements were not initialized.");
}

public int GetCount(bool onlyIfCheap)
Expand Down
31 changes: 26 additions & 5 deletions src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,36 @@ public TSource[] ToArray(int count)
Debug.Assert(count == GetCount());

TSource[] array = new TSource[count];
int index = count;
FillReversed(array);
return array;
}

/// <summary>
/// Fills a start of a span with the items of this node's singly-linked list.
/// </summary>
/// <param name="span">The span to fill. Must be the precise size required.</param>
public void Fill(Span<TSource> span)
{
int index = 0;
for (SingleLinkedNode<TSource>? node = this; node != null; node = node.Linked)
{
--index;
array[index] = node.Item;
span[index] = node.Item;
index++;
}
}

Debug.Assert(index == 0);
return array;
/// <summary>
/// Fills the end of a span with the items of this node's singly-linked list in reverse.
/// </summary>
/// <param name="span">The span to fill.</param>
public void FillReversed(Span<TSource> span)
{
int index = span.Length;
for (SingleLinkedNode<TSource>? node = this; node != null; node = node.Linked)
{
--index;
span[index] = node.Item;
}
}
}
}
17 changes: 14 additions & 3 deletions src/libraries/System.Linq/src/System/Linq/ToCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;

namespace System.Linq
{
Expand Down Expand Up @@ -255,11 +256,21 @@ private static TSource[] HashSetToArray<TSource>(HashSet<TSource> set)

private static List<TSource> HashSetToList<TSource>(HashSet<TSource> set)
{
var result = new List<TSource>(set.Count);
int count = set.Count;

foreach (TSource item in set)
var result = new List<TSource>(count);
if (count > 0)
{
result.Add(item);
Span<TSource> span = SetCountAndGetSpan(result, count);

int index = 0;
foreach (TSource item in set)
{
span[index] = item;
++index;
}

Debug.Assert(index == span.Length, "All list elements were not initialized.");
}

return result;
Expand Down
14 changes: 14 additions & 0 deletions src/libraries/System.Linq/tests/SelectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,13 @@ public void Select_SourceIsArrayTakeTake()
Assert.Equal(new[] { 2 }, source.Take(10));
}

[Fact]
public void Select_SourceIsIPartitionToArray()
{
Assert.Equal(Array.Empty<int>(), new List<int>().Order().Select(i => i * 2).ToArray());
Assert.Equal(new[] { 2, 4, 6, 8 }, new List<int> { 1, 2, 3, 4 }.Order().Select(i => i * 2).ToArray());
}

[Fact]
public void Select_SourceIsListSkipTakeCount()
{
Expand Down Expand Up @@ -1134,6 +1141,13 @@ public void Select_SourceIsListSkipTakeToList()
Assert.Empty(new List<int> { 1, 2, 3, 4 }.Select(i => i * 2).Skip(8).ToList());
}

[Fact]
public void Select_SourceIsIPartitionToList()
{
Assert.Equal(Array.Empty<int>(), new List<int>().Order().Select(i => i * 2).ToList());
Assert.Equal(new[] { 2, 4, 6, 8 }, new List<int> { 1, 2, 3, 4 }.Order().Select(i => i * 2).ToList());
}

[Theory]
[MemberData(nameof(MoveNextAfterDisposeData))]
public void MoveNextAfterDispose(IEnumerable<int> source)
Expand Down