Skip to content

Commit 1b4eff2

Browse files
Joy-lessCopilotjeffhandleytarekgh
authored
Add methods from #27912 (Flow System.Text.Rune through more APIs) (#117168)
* Add rune overloads * Fix some compiler errors * More compiler error fixes * Even more compiler error fixes * Fix more compiler errors * Various improvements & add refs * Fix validation in LastIndexOf Co-authored-by: Copilot <[email protected]> * Fix replace core usage * Fix TextInfo ToLower/ToUpper with 2-char runes * Optimize `char.Equals` for Ordinal StringComparison * Add conditional compilation for SYSTEM_PRIVATE_CORLIB * Add more conditional compilation for SYSTEM_PRIVATE_CORLIB * Rename `r` to `value` https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356631203 * Replace static Equals with instance methods https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356644411 * Rename `r` to `value` in refs * Fix ToLower & ToUpper bypassing invariant globalization check https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356686500 * Add newline between ToLower, ToUpper https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356689265 * Optimize TextWriter.Write(Rune) https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356754641 * Optimize TextWriter.WriteLine(Rune) https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356758251 * Change TextWriter.WriteAsync(Rune) https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356777291 * Change TextWriter.WriteLineAsync(Rune) https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356784926 * Remove unnecessary slices in ToLower(Rune) & ToUpper(Rune) https://github.com/dotnet/runtime/pull/117168/files#r2356675263 * Revert `Split(char, options)`, `Split(char, int, options)` order #117168 (comment) * Flip Split(Rune, options) and Split(Rune, int, options) order #117168 (comment) * Make `Equals(char, StringComparison)` helper internal #117168 (comment) * Add `StringBuilder.EnumerateRunes()` & `StringBuilder.RuneEnumerator` * Make `IndexOf`/`LastIndexOf` overload helpers internal * Fix WriteLineAsync(Rune) #117168 (comment) * Rename left/right local variables to old/new #117168 (comment) * Remove unnecessary `else` #117168 (comment) * Move StringBuilder.RuneEnumerator -> StringBuilderRuneEnumerator #117168 (comment) * Add `ArgumentOutOfRangeException.ThrowIfNegative(index);` #117168 (comment) * Replace exception with ThrowHelper.ThrowIndexOutOfRangeException(); #117168 (comment) * Add empty line between StringBuilder.Replace overloads #117168 (comment) * Add doc comments to all added APIs #117168 (comment) * Use invariant culture for string.Replace(Rune, Rune) #117168 (comment) * Reference `StringBuilderRuneEnumerator.cs` file * Update exceptions in string builder GetRuneAt * Change string.Replace(Rune, Rune) compare options from None to Ordinal * Use span comparison for StartsWith/EndsWith * Make sure start index not bypassed in string.Trim(Rune) * Fix incorrect slice end index * Replace IndexOf & LastIndexOf code with span conversion * Add `Contains_Rune` and `Contains_Rune_StringComparison` tests * Add string.Split tests * Add string.IndexOf & string.LastIndexOf tests * Add string.Replace tests * Fix string.Replace tests * Add string.EndsWith tests * Add string.StartsWith tests * Fix bounds check in TrimStart(Rune) * Fix bounds check in Trim(Rune) * Simplify string.Empty to Empty * Use AsSpan helper method to simplify rune to span * Document ordinal comparisons * Fix IndexOf/LastIndexOf startIndex * Add Trim Rune tests * Add StringBuilder test to EnumerateRunes test * Separate EnumerateRunes tests for string & StringBuilder * Add Rune.Equals(Rune, StringComparison) test * Add StringBuilder Append Rune test * Add StringBuilder GetRuneAt/TryGetRuneAt tests * Add StringBuilder Insert_Rune and Insert_Rune_Invalid tests * Use consistent style with other tests * Add StringBuilder ReplaceRune tests * Add TextWriter rune tests * Add TextInfo ToLower/ToUpper tests * Add rune tests to SplitCharSeparator * Fix sub index not offsetted in IndexOf/LastIndexOf * Fix LastIndexOf searchIndex * Fix LastIndexOf * Remove `#if SYSTEM_PRIVATE_CORLIB` * Change code style for rune trimming * Fix error in new code style * Fix `Rune` not found in `TextInfoTests.cs` * Fix incorrect method name in `TextWriterTests` * Fix incorrect StartsWith Rune test * Fix tests with Rune attribute arguments * Fix `str` -> `value` * Fix concatenating rune array * Add dedicated `SplitRuneSeparator` test * Fix trim rune tests * Fix invalid collection expressions in tests * Fix InsertRune test * Fix replace rune tests * Fix unused comparisonType parameter * Fix index of test names * Remove redundant test * Fix missing using * Fix replace method called with string comparison argument * Fix tried to test split with rune on span * Remove start index from LastIndexOf tests * Remove non-existent comparisonType parameter * Fix StartsWith test incorrect call * Fix LastIndexOf behaviour * Various rune test fixes * Fix error in LastIndexOf test * Some fixes and ensure the Runtime tests passes * Add brackets to ternary condition * Rename `runeSeparator` to `separatorSpan` * Add rune overloads * Fix some compiler errors * More compiler error fixes * Even more compiler error fixes * Fix more compiler errors * Various improvements & add refs * Fix validation in LastIndexOf Co-authored-by: Copilot <[email protected]> * Fix replace core usage * Fix TextInfo ToLower/ToUpper with 2-char runes * Optimize `char.Equals` for Ordinal StringComparison * Add conditional compilation for SYSTEM_PRIVATE_CORLIB * Add more conditional compilation for SYSTEM_PRIVATE_CORLIB * Rename `r` to `value` https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356631203 * Replace static Equals with instance methods https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356644411 * Rename `r` to `value` in refs * Fix ToLower & ToUpper bypassing invariant globalization check https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356686500 * Add newline between ToLower, ToUpper https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356689265 * Optimize TextWriter.Write(Rune) https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356754641 * Optimize TextWriter.WriteLine(Rune) https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356758251 * Change TextWriter.WriteAsync(Rune) https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356777291 * Change TextWriter.WriteLineAsync(Rune) https://github.com/dotnet/runtime/pull/117168/files/663459d5b1ab973657d22e5cce43c02477386fbe#r2356784926 * Remove unnecessary slices in ToLower(Rune) & ToUpper(Rune) https://github.com/dotnet/runtime/pull/117168/files#r2356675263 * Revert `Split(char, options)`, `Split(char, int, options)` order #117168 (comment) * Flip Split(Rune, options) and Split(Rune, int, options) order #117168 (comment) * Make `Equals(char, StringComparison)` helper internal #117168 (comment) * Add `StringBuilder.EnumerateRunes()` & `StringBuilder.RuneEnumerator` * Make `IndexOf`/`LastIndexOf` overload helpers internal * Fix WriteLineAsync(Rune) #117168 (comment) * Rename left/right local variables to old/new #117168 (comment) * Remove unnecessary `else` #117168 (comment) * Move StringBuilder.RuneEnumerator -> StringBuilderRuneEnumerator #117168 (comment) * Add `ArgumentOutOfRangeException.ThrowIfNegative(index);` #117168 (comment) * Replace exception with ThrowHelper.ThrowIndexOutOfRangeException(); #117168 (comment) * Add empty line between StringBuilder.Replace overloads #117168 (comment) * Add doc comments to all added APIs #117168 (comment) * Use invariant culture for string.Replace(Rune, Rune) #117168 (comment) * Reference `StringBuilderRuneEnumerator.cs` file * Update exceptions in string builder GetRuneAt * Change string.Replace(Rune, Rune) compare options from None to Ordinal * Use span comparison for StartsWith/EndsWith * Make sure start index not bypassed in string.Trim(Rune) * Fix incorrect slice end index * Replace IndexOf & LastIndexOf code with span conversion * Add `Contains_Rune` and `Contains_Rune_StringComparison` tests * Add string.Split tests * Add string.IndexOf & string.LastIndexOf tests * Add string.Replace tests * Fix string.Replace tests * Add string.EndsWith tests * Add string.StartsWith tests * Fix bounds check in TrimStart(Rune) * Fix bounds check in Trim(Rune) * Simplify string.Empty to Empty * Use AsSpan helper method to simplify rune to span * Document ordinal comparisons * Fix IndexOf/LastIndexOf startIndex * Add Trim Rune tests * Add StringBuilder test to EnumerateRunes test * Separate EnumerateRunes tests for string & StringBuilder * Add Rune.Equals(Rune, StringComparison) test * Add StringBuilder Append Rune test * Add StringBuilder GetRuneAt/TryGetRuneAt tests * Add StringBuilder Insert_Rune and Insert_Rune_Invalid tests * Use consistent style with other tests * Add StringBuilder ReplaceRune tests * Add TextWriter rune tests * Add TextInfo ToLower/ToUpper tests * Add rune tests to SplitCharSeparator * Fix sub index not offsetted in IndexOf/LastIndexOf * Fix LastIndexOf searchIndex * Fix LastIndexOf * Remove `#if SYSTEM_PRIVATE_CORLIB` * Change code style for rune trimming * Fix error in new code style * Fix `Rune` not found in `TextInfoTests.cs` * Fix incorrect method name in `TextWriterTests` * Fix incorrect StartsWith Rune test * Fix tests with Rune attribute arguments * Fix `str` -> `value` * Fix concatenating rune array * Add dedicated `SplitRuneSeparator` test * Fix trim rune tests * Fix invalid collection expressions in tests * Fix InsertRune test * Fix replace rune tests * Fix unused comparisonType parameter * Fix index of test names * Remove redundant test * Fix missing using * Fix replace method called with string comparison argument * Fix tried to test split with rune on span * Remove start index from LastIndexOf tests * Remove non-existent comparisonType parameter * Fix StartsWith test incorrect call * Fix LastIndexOf behaviour * Various rune test fixes * Some fixes and ensure the Runtime tests passes * Add brackets to ternary condition * Rename `runeSeparator` to `separatorSpan` * Final fixes touches. --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Jeff Handley <[email protected]> Co-authored-by: Tarek Mahmoud Sayed <[email protected]> Co-authored-by: Tarek Mahmoud Sayed <[email protected]>
1 parent 7430c6d commit 1b4eff2

File tree

18 files changed

+1602
-6
lines changed

18 files changed

+1602
-6
lines changed

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,7 @@
12171217
<Compile Include="$(MSBuildThisFileDirectory)System\Text\SpanRuneEnumerator.cs" />
12181218
<Compile Include="$(MSBuildThisFileDirectory)System\Text\StringBuilder.cs" />
12191219
<Compile Include="$(MSBuildThisFileDirectory)System\Text\StringBuilder.Debug.cs" Condition="'$(Configuration)' == 'Debug'" />
1220+
<Compile Include="$(MSBuildThisFileDirectory)System\Text\StringBuilderRuneEnumerator.cs" />
12201221
<Compile Include="$(MSBuildThisFileDirectory)System\Text\StringRuneEnumerator.cs" />
12211222
<Compile Include="$(MSBuildThisFileDirectory)System\Text\TranscodingStream.cs" />
12221223
<Compile Include="$(MSBuildThisFileDirectory)System\Text\TrimType.cs" />

src/libraries/System.Private.CoreLib/src/System/Char.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ public bool Equals(char obj)
129129
return m_value == obj;
130130
}
131131

132+
internal bool Equals(char right, StringComparison comparisonType)
133+
{
134+
switch (comparisonType)
135+
{
136+
case StringComparison.Ordinal:
137+
return Equals(right);
138+
default:
139+
ReadOnlySpan<char> leftCharsSlice = [this];
140+
ReadOnlySpan<char> rightCharsSlice = [right];
141+
return leftCharsSlice.Equals(rightCharsSlice, comparisonType);
142+
}
143+
}
144+
132145
// Compares this object to another object, returning an integer that
133146
// indicates the relationship.
134147
// Returns a value less than zero if this object

src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,17 @@ public string ToLower(string str)
180180
return ChangeCaseCommon<ToLowerConversion>(str);
181181
}
182182

183+
internal void ToLower(ReadOnlySpan<char> source, Span<char> destination)
184+
{
185+
if (GlobalizationMode.Invariant)
186+
{
187+
InvariantModeCasing.ToLower(source, destination);
188+
return;
189+
}
190+
191+
ChangeCaseCommon<ToLowerConversion>(source, destination);
192+
}
193+
183194
private unsafe char ChangeCase(char c, bool toUpper)
184195
{
185196
Debug.Assert(!GlobalizationMode.Invariant);
@@ -451,6 +462,17 @@ public string ToUpper(string str)
451462
return ChangeCaseCommon<ToUpperConversion>(str);
452463
}
453464

465+
internal void ToUpper(ReadOnlySpan<char> source, Span<char> destination)
466+
{
467+
if (GlobalizationMode.Invariant)
468+
{
469+
InvariantModeCasing.ToUpper(source, destination);
470+
return;
471+
}
472+
473+
ChangeCaseCommon<ToUpperConversion>(source, destination);
474+
}
475+
454476
[MethodImpl(MethodImplOptions.AggressiveInlining)]
455477
internal static char ToUpperAsciiInvariant(char c)
456478
{
@@ -461,6 +483,50 @@ internal static char ToUpperAsciiInvariant(char c)
461483
return c;
462484
}
463485

486+
/// <summary>
487+
/// Converts the specified rune to lowercase.
488+
/// </summary>
489+
/// <param name="value">The rune to convert to lowercase.</param>
490+
/// <returns>The specified rune converted to lowercase.</returns>
491+
public Rune ToLower(Rune value)
492+
{
493+
// Convert rune to span
494+
ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);
495+
496+
// Change span to lower and convert to rune
497+
if (valueChars.Length == 2)
498+
{
499+
Span<char> lowerChars = stackalloc char[2];
500+
ToLower(valueChars, lowerChars);
501+
return new Rune(lowerChars[0], lowerChars[1]);
502+
}
503+
504+
char lowerChar = ToLower(valueChars[0]);
505+
return new Rune(lowerChar);
506+
}
507+
508+
/// <summary>
509+
/// Converts the specified rune to uppercase.
510+
/// </summary>
511+
/// <param name="value">The rune to convert to uppercase.</param>
512+
/// <returns>The specified rune converted to uppercase.</returns>
513+
public Rune ToUpper(Rune value)
514+
{
515+
// Convert rune to span
516+
ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);
517+
518+
// Change span to upper and convert to rune
519+
if (valueChars.Length == 2)
520+
{
521+
Span<char> upperChars = stackalloc char[2];
522+
ToUpper(valueChars, upperChars);
523+
return new Rune(upperChars[0], upperChars[1]);
524+
}
525+
526+
char upperChar = ToUpper(valueChars[0]);
527+
return new Rune(upperChar);
528+
}
529+
464530
private bool IsAsciiCasingSameAsInvariant
465531
{
466532
[MethodImpl(MethodImplOptions.AggressiveInlining)]

src/libraries/System.Private.CoreLib/src/System/IO/TextWriter.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,23 @@ public virtual void Write(char value)
125125
{
126126
}
127127

128+
/// <summary>
129+
/// Writes a rune to the text stream.
130+
/// </summary>
131+
/// <param name="value">The rune to write to the text stream.</param>
132+
public virtual void Write(Rune value)
133+
{
134+
// Convert value to span
135+
ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);
136+
137+
// Write span
138+
Write(valueChars[0]);
139+
if (valueChars.Length > 1)
140+
{
141+
Write(valueChars[1]);
142+
}
143+
}
144+
128145
// Writes a character array to the text stream. This default method calls
129146
// Write(char) for each of the characters in the character array.
130147
// If the character array is null, nothing is written.
@@ -343,6 +360,26 @@ public virtual void WriteLine(char value)
343360
WriteLine();
344361
}
345362

363+
/// <summary>
364+
/// Writes a rune followed by a line terminator to the text stream.
365+
/// </summary>
366+
/// <param name="value">The rune to write to the text stream.</param>
367+
public virtual void WriteLine(Rune value)
368+
{
369+
// Convert value to span
370+
ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);
371+
372+
if (valueChars.Length > 1)
373+
{
374+
Write(valueChars[0]);
375+
WriteLine(valueChars[1]);
376+
}
377+
else
378+
{
379+
WriteLine(valueChars[0]);
380+
}
381+
}
382+
346383
// Writes an array of characters followed by a line terminator to the text
347384
// stream.
348385
//
@@ -542,6 +579,28 @@ public virtual Task WriteAsync(char value) =>
542579
t.Item1.Write(t.Item2);
543580
}, new TupleSlim<TextWriter, char>(this, value), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
544581

582+
/// <summary>
583+
/// Writes a rune to the text stream asynchronously.
584+
/// </summary>
585+
/// <param name="value">The rune to write to the text stream.</param>
586+
/// <returns>A task that represents the asynchronous write operation.</returns>
587+
public virtual Task WriteAsync(Rune value)
588+
{
589+
ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);
590+
591+
if (valueChars.Length > 1)
592+
{
593+
return Task.Factory.StartNew(static state =>
594+
{
595+
var t = (TupleSlim<TextWriter, char, char>)state!;
596+
t.Item1.Write(t.Item2);
597+
t.Item1.Write(t.Item3);
598+
}, new TupleSlim<TextWriter, char, char>(this, valueChars[0], valueChars[1]), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
599+
}
600+
601+
return WriteAsync(valueChars[0]);
602+
}
603+
545604
public virtual Task WriteAsync(string? value) =>
546605
Task.Factory.StartNew(static state =>
547606
{
@@ -605,6 +664,28 @@ public virtual Task WriteLineAsync(char value) =>
605664
t.Item1.WriteLine(t.Item2);
606665
}, new TupleSlim<TextWriter, char>(this, value), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
607666

667+
/// <summary>
668+
/// Writes a rune followed by a line terminator to the text stream asynchronously.
669+
/// </summary>
670+
/// <param name="value">The rune to write to the text stream.</param>
671+
/// <returns>A task that represents the asynchronous write operation.</returns>
672+
public virtual Task WriteLineAsync(Rune value)
673+
{
674+
ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);
675+
676+
if (valueChars.Length > 1)
677+
{
678+
return Task.Factory.StartNew(static state =>
679+
{
680+
var t = (TupleSlim<TextWriter, char, char>)state!;
681+
t.Item1.Write(t.Item2);
682+
t.Item1.WriteLine(t.Item3);
683+
}, new TupleSlim<TextWriter, char, char>(this, valueChars[0], valueChars[1]), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
684+
}
685+
686+
return WriteLineAsync(valueChars[0]);
687+
}
688+
608689
public virtual Task WriteLineAsync(string? value) =>
609690
Task.Factory.StartNew(static state =>
610691
{

src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Numerics;
1010
using System.Runtime.CompilerServices;
1111
using System.Runtime.InteropServices;
12+
using System.Text;
1213
using System.Text.Unicode;
1314

1415
namespace System
@@ -589,6 +590,44 @@ public bool EndsWith(char value)
589590
return ((uint)lastPos < (uint)Length) && this[lastPos] == value;
590591
}
591592

593+
/// <summary>
594+
/// Determines whether the end of this string instance matches the specified character.
595+
/// </summary>
596+
/// <param name="value">The character to compare to the character at the end of this instance.</param>
597+
/// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
598+
/// <returns><see langword="true"/> if <paramref name="value"/> matches the end of this instance; otherwise, <see langword="false"/>.</returns>
599+
public bool EndsWith(char value, StringComparison comparisonType)
600+
{
601+
// Convert value to span
602+
ReadOnlySpan<char> valueChars = [value];
603+
604+
return this.EndsWith(valueChars, comparisonType);
605+
}
606+
607+
/// <summary>
608+
/// Determines whether the end of this string instance matches the specified rune using an ordinal comparison.
609+
/// </summary>
610+
/// <param name="value">The character to compare to the character at the end of this instance.</param>
611+
/// <returns><see langword="true"/> if <paramref name="value"/> matches the end of this instance; otherwise, <see langword="false"/>.</returns>
612+
public bool EndsWith(Rune value)
613+
{
614+
return EndsWith(value, StringComparison.Ordinal);
615+
}
616+
617+
/// <summary>
618+
/// Determines whether the end of this string instance matches the specified rune when compared using the specified comparison option.
619+
/// </summary>
620+
/// <param name="value">The character to compare to the character at the end of this instance.</param>
621+
/// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
622+
/// <returns><see langword="true"/> if <paramref name="value"/> matches the end of this instance; otherwise, <see langword="false"/>.</returns>
623+
public bool EndsWith(Rune value, StringComparison comparisonType)
624+
{
625+
// Convert value to span
626+
ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);
627+
628+
return this.EndsWith(valueChars, comparisonType);
629+
}
630+
592631
// Determines whether two strings match.
593632
public override bool Equals([NotNullWhen(true)] object? obj)
594633
{
@@ -1162,6 +1201,44 @@ public bool StartsWith(char value)
11621201
return Length != 0 && _firstChar == value;
11631202
}
11641203

1204+
/// <summary>
1205+
/// Determines whether the beginning of this string instance matches the specified character when compared using the specified comparison option.
1206+
/// </summary>
1207+
/// <param name="value">The character to compare.</param>
1208+
/// <param name="comparisonType">One of the enumeration values that determines how this string and <paramref name="value"/> are compared.</param>
1209+
/// <returns><see langword="true"/> if value matches the beginning of this string; otherwise, <see langword="false"/>.</returns>
1210+
public bool StartsWith(char value, StringComparison comparisonType)
1211+
{
1212+
// Convert value to span
1213+
ReadOnlySpan<char> valueChars = [value];
1214+
1215+
return this.StartsWith(valueChars, comparisonType);
1216+
}
1217+
1218+
/// <summary>
1219+
/// Determines whether the beginning of this string instance matches the specified rune using an ordinal comparison.
1220+
/// </summary>
1221+
/// <param name="value">The rune to compare.</param>
1222+
/// <returns><see langword="true"/> if value matches the beginning of this string; otherwise, <see langword="false"/>.</returns>
1223+
public bool StartsWith(Rune value)
1224+
{
1225+
return StartsWith(value, StringComparison.Ordinal);
1226+
}
1227+
1228+
/// <summary>
1229+
/// Determines whether the beginning of this string instance matches the specified rune when compared using the specified comparison option.
1230+
/// </summary>
1231+
/// <param name="value">The rune to compare.</param>
1232+
/// <param name="comparisonType">One of the enumeration values that determines how this string and <paramref name="value"/> are compared.</param>
1233+
/// <returns><see langword="true"/> if value matches the beginning of this string; otherwise, <see langword="false"/>.</returns>
1234+
public bool StartsWith(Rune value, StringComparison comparisonType)
1235+
{
1236+
// Convert value to span
1237+
ReadOnlySpan<char> valueChars = value.AsSpan(stackalloc char[Rune.MaxUtf16CharsPerRune]);
1238+
1239+
return this.StartsWith(valueChars, comparisonType);
1240+
}
1241+
11651242
internal static void CheckStringComparison(StringComparison comparisonType)
11661243
{
11671244
// Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase]

0 commit comments

Comments
 (0)