diff --git a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs index a058b6048c2c4d..caf83bee1fcaac 100644 --- a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs @@ -73,6 +73,13 @@ public override TSource[] ToArray() public override List 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(1) { _item }; + } + List list = count == -1 ? new List() : new List(count); if (!_appending) { @@ -122,17 +129,8 @@ private TSource[] LazyToArray() TSource[] array = builder.ToArray(); - int index = 0; - for (SingleLinkedNode? node = _prepended; node != null; node = node.Linked) - { - array[index++] = node.Item; - } - - index = array.Length - 1; - for (SingleLinkedNode? node = _appended; node != null; node = node.Linked) - { - array[index--] = node.Item; - } + _prepended?.Fill(array); + _appended?.FillReversed(array); return array; } @@ -181,17 +179,11 @@ public override List ToList() int count = GetCount(onlyIfCheap: true); List list = count == -1 ? new List() : new List(count); - for (SingleLinkedNode? 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; } diff --git a/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs index 5fc89fc52555b6..2ef86a008493a8 100644 --- a/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs @@ -10,22 +10,16 @@ public partial class Lookup : IIListProvider[] IIListProvider>.ToArray() { - IGrouping[] array = new IGrouping[_count]; - int index = 0; - Grouping? g = _lastGrouping; - if (g != null) + IGrouping[] array; + if (_count > 0) { - do - { - g = g._next; - Debug.Assert(g != null); - - array[index] = g; - ++index; - } - while (g != _lastGrouping); + array = new IGrouping[_count]; + Fill(_lastGrouping, array); + } + else + { + array = Array.Empty>(); } - return array; } @@ -53,8 +47,19 @@ internal TResult[] ToArray(Func, TResult> r List> IIListProvider>.ToList() { - List> list = new List>(_count); - Grouping? g = _lastGrouping; + var list = new List>(_count); + if (_count > 0) + { + Fill(_lastGrouping, Enumerable.SetCountAndGetSpan(list, _count)); + } + + return list; + } + + private static void Fill(Grouping? lastGrouping, Span> results) + { + int index = 0; + Grouping? g = lastGrouping; if (g != null) { do @@ -62,12 +67,13 @@ List> IIListProvider>.ToList 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>.GetCount(bool onlyIfCheap) => _count; diff --git a/src/libraries/System.Linq/src/System/Linq/Lookup.cs b/src/libraries/System.Linq/src/System/Linq/Lookup.cs index 2ee2d329255eb9..4e40e9c2e695d2 100644 --- a/src/libraries/System.Linq/src/System/Linq/Lookup.cs +++ b/src/libraries/System.Linq/src/System/Linq/Lookup.cs @@ -154,15 +154,20 @@ internal List ToList(Func, TResult Grouping? g = _lastGrouping; if (g != null) { + Span 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; diff --git a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs index 7eed5849965e97..faf09335d5f3a3 100644 --- a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs @@ -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; } @@ -640,20 +634,33 @@ public List ToList() { case -1: list = new List(); + foreach (TSource input in _source) + { + list.Add(_selector(input)); + } break; case 0: - return new List(); + list = new List(); + break; default: list = new List(count); + Fill(_source, SetCountAndGetSpan(list, count), _selector); break; } - foreach (TSource input in _source) + return list; + } + + private static void Fill(IPartition source, Span results, Func 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) diff --git a/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs b/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs index 37a4a3a60c5582..7468973d2c77c0 100644 --- a/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs +++ b/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs @@ -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; + } + + /// + /// Fills a start of a span with the items of this node's singly-linked list. + /// + /// The span to fill. Must be the precise size required. + public void Fill(Span span) + { + int index = 0; for (SingleLinkedNode? node = this; node != null; node = node.Linked) { - --index; - array[index] = node.Item; + span[index] = node.Item; + index++; } + } - Debug.Assert(index == 0); - return array; + /// + /// Fills the end of a span with the items of this node's singly-linked list in reverse. + /// + /// The span to fill. + public void FillReversed(Span span) + { + int index = span.Length; + for (SingleLinkedNode? node = this; node != null; node = node.Linked) + { + --index; + span[index] = node.Item; + } } } } diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs index 280ced4d34f629..aaf9b86dc93a54 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -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 { @@ -255,11 +256,21 @@ private static TSource[] HashSetToArray(HashSet set) private static List HashSetToList(HashSet set) { - var result = new List(set.Count); + int count = set.Count; - foreach (TSource item in set) + var result = new List(count); + if (count > 0) { - result.Add(item); + Span 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; diff --git a/src/libraries/System.Linq/tests/SelectTests.cs b/src/libraries/System.Linq/tests/SelectTests.cs index 20de7814b6bcca..1f709bf9caadaf 100644 --- a/src/libraries/System.Linq/tests/SelectTests.cs +++ b/src/libraries/System.Linq/tests/SelectTests.cs @@ -1107,6 +1107,13 @@ public void Select_SourceIsArrayTakeTake() Assert.Equal(new[] { 2 }, source.Take(10)); } + [Fact] + public void Select_SourceIsIPartitionToArray() + { + Assert.Equal(Array.Empty(), new List().Order().Select(i => i * 2).ToArray()); + Assert.Equal(new[] { 2, 4, 6, 8 }, new List { 1, 2, 3, 4 }.Order().Select(i => i * 2).ToArray()); + } + [Fact] public void Select_SourceIsListSkipTakeCount() { @@ -1134,6 +1141,13 @@ public void Select_SourceIsListSkipTakeToList() Assert.Empty(new List { 1, 2, 3, 4 }.Select(i => i * 2).Skip(8).ToList()); } + [Fact] + public void Select_SourceIsIPartitionToList() + { + Assert.Equal(Array.Empty(), new List().Order().Select(i => i * 2).ToList()); + Assert.Equal(new[] { 2, 4, 6, 8 }, new List { 1, 2, 3, 4 }.Order().Select(i => i * 2).ToList()); + } + [Theory] [MemberData(nameof(MoveNextAfterDisposeData))] public void MoveNextAfterDispose(IEnumerable source)