diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8.cs b/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8.cs index 7e25c0af4ade03..e263bbc49419e1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/Utf8.cs @@ -328,6 +328,14 @@ public bool AppendFormatted(T value) return AppendCustomFormatter(value, format: null); } + // Special-case enums to avoid boxing them. + if (typeof(T).IsEnum) + { + // TODO https://github.com/dotnet/runtime/issues/81500: + // Once Enum.TryFormat provides direct UTF8 support, use that here instead. + return AppendEnum(value, format: null); + } + // If the value can format itself directly into our buffer, do so. if (value is IUtf8SpanFormattable) { @@ -373,6 +381,14 @@ public bool AppendFormatted(T value, string? format) return AppendCustomFormatter(value, format); } + // Special-case enums to avoid boxing them. + if (typeof(T).IsEnum) + { + // TODO https://github.com/dotnet/runtime/issues/81500: + // Once Enum.TryFormat provides direct UTF8 support, use that here instead. + return AppendEnum(value, format); + } + // If the value can format itself directly into our buffer, do so. if (value is IUtf8SpanFormattable) { @@ -546,7 +562,7 @@ private bool AppendCustomFormatter(T value, string? format) } /// Writes the specified ISpanFormattable to the handler. - /// The value to write. It must be an ISpanFormattable but isn't constrained because the caller doesn't have a cosntraint. + /// The value to write. It must be an ISpanFormattable but isn't constrained because the caller doesn't have a constraint. /// The format string. [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool AppendSpanFormattable(T value, string? format) @@ -590,6 +606,53 @@ static bool GrowAndAppendFormatted(scoped ref TryWriteInterpolatedStringHandler } } + // TODO https://github.com/dotnet/runtime/issues/81500: + // Remove once Enum.TryFormat(Span, ...) is available. + /// Writes the specified enum to the handler. + /// The value to write. It must be an enum but isn't constrained because the caller doesn't have a constraint. + /// The format string. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool AppendEnum(T value, string? format) + { + Debug.Assert(typeof(T).IsEnum); + + Span utf16 = stackalloc char[256]; + return Enum.TryFormatUnconstrained(value, utf16, out int charsWritten, format) ? + AppendFormatted(utf16.Slice(0, charsWritten)) : + GrowAndAppendFormatted(ref this, value, utf16.Length, out charsWritten, format); + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool GrowAndAppendFormatted(scoped ref TryWriteInterpolatedStringHandler thisRef, T value, int length, out int charsWritten, string? format) + { + Debug.Assert(value is ISpanFormattable); + + while (true) + { + int newLength = length * 2; + if ((uint)newLength > Array.MaxLength) + { + newLength = length == Array.MaxLength ? + Array.MaxLength + 1 : // force OOM + Array.MaxLength; + } + length = newLength; + + char[] array = ArrayPool.Shared.Rent(length); + try + { + if (Enum.TryFormatUnconstrained(value, array, out charsWritten, format)) + { + return thisRef.AppendFormatted(array.AsSpan(0, charsWritten)); + } + } + finally + { + ArrayPool.Shared.Return(array); + } + } + } + } + /// Handles adding any padding required for aligning a formatted value in an interpolation expression. /// The position at which the written value started. /// Non-zero minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.