dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.07k stars 4.04k forks source link

Elide calls to immediately-spread Enumerable.ToList/ToArray #70175

Open jnm2 opened 1 year ago

jnm2 commented 1 year ago

Since order is explicitly undefined between enumeration of a collection expression spread element and evaluation of the next collection expression element, folks will sometimes need to force enumeration early. Forcing enumeration early is easy, familiar, and reads nicely:

// Goal: enumerate the iterator method ReadHeaderLines completely,
// and call ReadBody only afterward.
[.. ReadHeaderLines().ToList(), ReadBody()]

The only downside is that this allocates an intermediate List<T> or T[]. However, this intermediate allocation is immediately spread, so the compiler could elide the allocation at its discretion without language users being able to tell the difference. This is in line with tentative plans to do similar eliding of collection instances in foreach (var x in new[] { a, b, c }) and stack-allocating instead.

The important part would be that the semantics are preserved: enumeration of ReadHeaderLines() must finish before the evaluation of ReadHeaderLines().ToList() completes. Even with the ToList call being elided as a compiler optimization, the call is still there in concept, and is still causing enumeration to happen as part of evaluating the spread expression.

Only Enumerable.ToList/ToArray should be elided. Instance ToArray methods such as ConcurrentDictionary.ToArray can't be elided without changing the semantics. (ConcurrentDictionary.ToArray creates an atomic snapshot, and enumerating ConcurrentDictionary does not.)

dotnet-issue-labeler[bot] commented 1 year ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.