| File: Utilities\StringBuilderHelper.cs | Web Access |
| Project: src\msbuild\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; #if NET using System.Runtime.CompilerServices; #endif using System.Text; #pragma warning disable IDE0038 // Use pattern matching namespace Microsoft.Build.Framework.Utilities; /// <summary> /// Helper struct to use with custom interpolated string handlers. /// </summary> /// <remarks> /// Acquires a <see cref="StringBuilder"/> from <see cref="StringBuilderCache"/> on construction. /// The builder is returned to the cache when <see cref="GetFormattedText"/> is called, which /// produces the final string and releases the underlying <see cref="StringBuilder"/>. /// </remarks> internal ref struct StringBuilderHelper(int capacity) { private StringBuilder? _builder = StringBuilderCache.Acquire(capacity); public readonly void AppendLiteral(string value) => _builder?.Append(value); public readonly void AppendFormatted<T>(T value) { if (value is null || _builder is null) { return; } // PERF: Intentionally not using pattern matching here to avoid boxing. // - On .NET, we prefer ISpanFormattable and perform that check below. // - On .NET Framework, we make a constrained call to IFormattable to avoid boxing, // which is not possible with pattern matching. if (value is IFormattable) { #if NET if (value is ISpanFormattable) { // This calls the Append overload that takes a StringBuilder.AppendInterpolatedStringHandler, // which will avoid allocations for value types. _builder.Append($"{value}"); return; } #endif // Make a constrained call through IFormattable to avoid boxing value types. _builder.Append(((IFormattable)value).ToString(format: null, formatProvider: null)); } else { _builder.Append(value.ToString()); } } public readonly void AppendFormatted<T>(T value, string? format) where T : IFormattable { if (value is null || _builder is null) { return; } #if NET if (value is ISpanFormattable) { // Use System.Runtime.CompilerServices.DefaultInterpolatedStringHandler // to do the work of appending 'value' and formatting with 'format'. // We initialize with a stack-allocated buffer of 64 chars, which would // only be a significant concern if this code path were running on .NET Framework. var handler = new DefaultInterpolatedStringHandler( literalLength: 0, formattedCount: 1, provider: null, initialBuffer: stackalloc char[64]); handler.AppendFormatted(value, alignment: 0, format); _builder.Append(handler.Text); handler.Clear(); return; } #endif _builder.Append(value.ToString(format, formatProvider: null)); } /// <summary> /// Returns the formatted string and releases the underlying <see cref="StringBuilder"/> /// back to the <see cref="StringBuilderCache"/>. Subsequent calls return <see cref="string.Empty"/>. /// </summary> public string GetFormattedText() { StringBuilder? builder = _builder; _builder = null; return builder is not null ? StringBuilderCache.GetStringAndRelease(builder) : string.Empty; } }