Add net11 StringBuilder Rune members - #589
Merged
Merged
Conversation
Adds the Rune members that can be reached from a polyfill: * EnumerateRunes(), and the StringBuilderRuneEnumerator it returns * GetRuneAt(int) and TryGetRuneAt(int, out Rune) * Replace(Rune, Rune) and Replace(Rune, Rune, int, int) Append(Rune) and Insert(int, Rune) are deliberately not included. StringBuilder already has Append(object) and Insert(int, object), which are applicable to a Rune, and an instance method always beats an extension method, so an extension taking a Rune could never bind. Those two calls already compile on earlier target frameworks and produce the same characters through the object overloads, at the cost of boxing. StringBuilderRuneEnumerator is a top level type rather than nested in StringBuilder, so it can be recreated. It delegates to StringRuneEnumerator over a snapshot of the content, which gives the same replacement character behaviour for unpaired surrogates. Semantics were taken from net11: * an unpaired surrogate enumerates as U+FFFD * GetRuneAt on a low surrogate, or on an unpaired surrogate, throws ArgumentException, and an out of range index throws ArgumentOutOfRangeException * TryGetRuneAt returns false for those same surrogates, but still throws for an out of range index All of this needs Rune, so it applies from netcoreapp3.0.
This was referenced Sep 10, 2026
This was referenced Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the
StringBuilderRunemembers that a polyfill can actually reach:EnumerateRunes(), and theStringBuilderRuneEnumeratorit returnsGetRuneAt(int)andTryGetRuneAt(int, out Rune)Replace(Rune, Rune)andReplace(Rune, Rune, int, int)API count 1067 to 1072. All of it needs
Rune, so it applies from netcoreapp3.0.Append(Rune) and Insert(int, Rune) are deliberately excluded
StringBuilderalready hasAppend(object)andInsert(int, object), which are applicable to aRune, and an instance method always beats an extension method. An extension taking aRunecan therefore never bind. Confirmed on net8 with an extension that appends a marker: the marker never appears forAppendandInsert, and does appear forReplace.This is not a gap in practice.
sb.Append(rune)already compiles on earlier target frameworks and appends the same characters, becauseAppend(object)callsRune.ToString(). The only difference is a boxing allocation. Adding the members would have produced entries inapi_listfor methods that can never be called.Replacehas no applicable instance overload, sinceRuneconverts to neithercharnorstring, so those two do bind.StringBuilderRuneEnumerator
It is a top level type in
System.Textrather than nested inStringBuilder, so it can be recreated in the normal way, with aTypeForwardedToon net11.It delegates to
StringRuneEnumeratorover a snapshot of the content, which gives the same replacement character behaviour for unpaired surrogates for free. The snapshot means changes made while enumerating are not observed, which is recorded as a//Note:.Semantics
Taken from net11 rather than assumed:
GetRuneAtat the index of a high surrogate returns the whole scalarGetRuneAton a low surrogate, or on an unpaired surrogate, throwsArgumentExceptionGetRuneAtwith an out of range index throwsArgumentOutOfRangeExceptionTryGetRuneAtreturnsfalsefor those same surrogates, but still throws for an out of range index rather than returningfalseReplacemay change the length, since the two runes can differ in UTF-16 lengthVerification
Consumebuilds clean in Debug across all 22 target frameworks.PublicTests,EmbeddedTests,UnsafeTests,NoRefsTestsandNoExtrasTests.