Avoid closures when calling TreeNode.Find{Last}Child. Added an overload that takes state, and made all call sites use static to help flag places state was being closed over.
Avoid closures in BuildEventArgs.Read. The error paths were closing over state used by the success path, resulting in allocation even on success.
Changed some methods that were returning interfaces to return the strongly-typed collection instead. Consumers that were iterating through them can now automatically avoid going through an enumerator.
Avoid params allocations for most call sites to FormatResourceStringIgnoreCodeAndKeyword by adding dedicated overloads for 1/2/3 args.
Avoid an int boxing allocation (on core) when concatening a string with an int.
Updated TextUtilities to use more modern APIs, e.g. MemoryExtensions.IndexOfAny, IndexOf, ReplaceLineEndings, etc. Some uses were (accidentally?) using Enumerable.Contains.
@KirillOsenkov asked me to look at why the .NET 8 build was slower than the .NET Framework build (answer: https://github.com/KirillOsenkov/MSBuildStructuredLog/pull/819), and why so much time was being taken up in ReadByte (answer: https://github.com/KirillOsenkov/MSBuildStructuredLog/issues/820, https://github.com/dotutils/streamutils/pull/2). But while looking into it, I did an allocation profile and noticed some low-hanging fruit easily avoided, so this addresses those.
static
to help flag places state was being closed over.