Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 1494667

Browse files
authored
Add ReadOnlySpan string-like ToLower/ToUpper API with globalization support (#16379)
* Add ReadOnlySpan string-like ToLower/ToUpper API with globalization support * Address PR feedback.
1 parent dab4790 commit 1494667

File tree

6 files changed

+185
-10
lines changed

6 files changed

+185
-10
lines changed

src/mscorlib/shared/System/Globalization/TextInfo.Unix.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Diagnostics;
6+
using System.Runtime.InteropServices;
67
using System.Security;
78
using System.Text;
89

@@ -64,6 +65,52 @@ private unsafe string ChangeCase(string s, bool toUpper)
6465
return result;
6566
}
6667

68+
internal unsafe void ChangeCase(ReadOnlySpan<char> source, Span<char> destination, bool toUpper)
69+
{
70+
Debug.Assert(!_invariantMode);
71+
Debug.Assert(destination.Length >= source.Length);
72+
73+
if (source.IsEmpty)
74+
{
75+
return;
76+
}
77+
78+
fixed (char* pSource = &MemoryMarshal.GetReference(source))
79+
{
80+
fixed (char* pResult = &MemoryMarshal.GetReference(destination))
81+
{
82+
if (IsAsciiCasingSameAsInvariant)
83+
{
84+
int length = source.Length;
85+
char* a = pSource, b = pResult;
86+
if (toUpper)
87+
{
88+
while (length-- != 0 && *a < 0x80)
89+
{
90+
*b++ = ToUpperAsciiInvariant(*a++);
91+
}
92+
}
93+
else
94+
{
95+
while (length-- != 0 && *a < 0x80)
96+
{
97+
*b++ = ToLowerAsciiInvariant(*a++);
98+
}
99+
}
100+
101+
if (length != 0)
102+
{
103+
ChangeCase(a, source.Length - length, b, destination.Length - length, toUpper);
104+
}
105+
}
106+
else
107+
{
108+
ChangeCase(pSource, source.Length, pResult, destination.Length, toUpper);
109+
}
110+
}
111+
}
112+
}
113+
67114
private unsafe char ChangeCase(char c, bool toUpper)
68115
{
69116
Debug.Assert(!_invariantMode);

src/mscorlib/shared/System/Globalization/TextInfo.Windows.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Diagnostics;
6+
using System.Runtime.InteropServices;
67

78
namespace System.Globalization
89
{
@@ -75,6 +76,43 @@ private unsafe string ChangeCase(string s, bool toUpper)
7576
return result;
7677
}
7778

79+
internal unsafe void ChangeCase(ReadOnlySpan<char> source, Span<char> destination, bool toUpper)
80+
{
81+
Debug.Assert(!_invariantMode);
82+
Debug.Assert(destination.Length >= source.Length);
83+
84+
if (source.IsEmpty)
85+
{
86+
return;
87+
}
88+
89+
int ret;
90+
91+
// Check for Invariant to avoid A/V in LCMapStringEx
92+
uint linguisticCasing = IsInvariantLocale(_textInfoName) ? 0 : LCMAP_LINGUISTIC_CASING;
93+
94+
fixed (char* pSource = &MemoryMarshal.GetReference(source))
95+
fixed (char* pResult = &MemoryMarshal.GetReference(destination))
96+
{
97+
ret = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName,
98+
linguisticCasing | (toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE),
99+
pSource,
100+
source.Length,
101+
pResult,
102+
source.Length,
103+
null,
104+
null,
105+
_sortHandle);
106+
}
107+
108+
if (ret == 0)
109+
{
110+
throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
111+
}
112+
113+
Debug.Assert(ret == source.Length, "Expected getting the same length of the original span");
114+
}
115+
78116
private unsafe char ChangeCase(char c, bool toUpper)
79117
{
80118
Debug.Assert(!_invariantMode);

src/mscorlib/shared/System/Globalization/TextInfo.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,16 @@ private unsafe string ToLowerAsciiInvariant(string s)
258258
}
259259
}
260260

261+
internal void ToLowerAsciiInvariant(ReadOnlySpan<char> source, Span<char> destination)
262+
{
263+
Debug.Assert(destination.Length >= source.Length);
264+
265+
for (int i = 0; i < source.Length; i++)
266+
{
267+
destination[i] = ToLowerAsciiInvariant(source[i]);
268+
}
269+
}
270+
261271
private unsafe string ToUpperAsciiInvariant(string s)
262272
{
263273
if (s.Length == 0)
@@ -304,6 +314,16 @@ private unsafe string ToUpperAsciiInvariant(string s)
304314
}
305315
}
306316

317+
internal void ToUpperAsciiInvariant(ReadOnlySpan<char> source, Span<char> destination)
318+
{
319+
Debug.Assert(destination.Length >= source.Length);
320+
321+
for (int i = 0; i < source.Length; i++)
322+
{
323+
destination[i] = ToUpperAsciiInvariant(source[i]);
324+
}
325+
}
326+
307327
private static char ToLowerAsciiInvariant(char c)
308328
{
309329
if ((uint)(c - 'A') <= (uint)('Z' - 'A'))

src/mscorlib/shared/System/Span.NonGeneric.cs

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,77 @@ namespace System
2323
/// </summary>
2424
public static class Span
2525
{
26-
// s_invariantMode is defined for the perf reason as accessing the instance field is faster than access the static property GlobalizationMode.Invariant
27-
private static readonly bool s_invariantMode = GlobalizationMode.Invariant;
26+
/// <summary>
27+
/// Copies the characters from the source span into the destination, converting each character to lowercase.
28+
/// </summary>
29+
public static int ToLower(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
30+
{
31+
if (culture == null)
32+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
33+
34+
// Assuming that changing case does not affect length
35+
if (destination.Length < source.Length)
36+
return -1;
37+
38+
if (GlobalizationMode.Invariant)
39+
culture.TextInfo.ToLowerAsciiInvariant(source, destination);
40+
else
41+
culture.TextInfo.ChangeCase(source, destination, toUpper: false);
42+
return source.Length;
43+
}
44+
45+
/// <summary>
46+
/// Copies the characters from the source span into the destination, converting each character to lowercase
47+
/// using the casing rules of the invariant culture.
48+
/// </summary>
49+
public static int ToLowerInvariant(this ReadOnlySpan<char> source, Span<char> destination)
50+
{
51+
// Assuming that changing case does not affect length
52+
if (destination.Length < source.Length)
53+
return -1;
54+
55+
if (GlobalizationMode.Invariant)
56+
CultureInfo.InvariantCulture.TextInfo.ToLowerAsciiInvariant(source, destination);
57+
else
58+
CultureInfo.InvariantCulture.TextInfo.ChangeCase(source, destination, toUpper: false);
59+
return source.Length;
60+
}
61+
62+
/// <summary>
63+
/// Copies the characters from the source span into the destination, converting each character to uppercase.
64+
/// </summary>
65+
public static int ToUpper(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
66+
{
67+
if (culture == null)
68+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
69+
70+
// Assuming that changing case does not affect length
71+
if (destination.Length < source.Length)
72+
return -1;
73+
74+
if (GlobalizationMode.Invariant)
75+
culture.TextInfo.ToUpperAsciiInvariant(source, destination);
76+
else
77+
culture.TextInfo.ChangeCase(source, destination, toUpper: true);
78+
return source.Length;
79+
}
80+
81+
/// <summary>
82+
/// Copies the characters from the source span into the destination, converting each character to uppercase
83+
/// using the casing rules of the invariant culture.
84+
/// </summary>
85+
public static int ToUpperInvariant(this ReadOnlySpan<char> source, Span<char> destination)
86+
{
87+
// Assuming that changing case does not affect length
88+
if (destination.Length < source.Length)
89+
return -1;
90+
91+
if (GlobalizationMode.Invariant)
92+
CultureInfo.InvariantCulture.TextInfo.ToUpperAsciiInvariant(source, destination);
93+
else
94+
CultureInfo.InvariantCulture.TextInfo.ChangeCase(source, destination, toUpper: true);
95+
return source.Length;
96+
}
2897

2998
/// <summary>
3099
/// Determines whether the beginning of the span matches the specified value when compared using the specified comparison option.
@@ -66,7 +135,7 @@ internal static bool StartsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySp
66135
{
67136
Debug.Assert(value.Length != 0);
68137

69-
if (s_invariantMode)
138+
if (GlobalizationMode.Invariant)
70139
{
71140
return StartsWithOrdinalHelper(span, value);
72141
}
@@ -83,7 +152,7 @@ internal static bool StartsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span,
83152
{
84153
Debug.Assert(value.Length != 0);
85154

86-
if (s_invariantMode)
155+
if (GlobalizationMode.Invariant)
87156
{
88157
return StartsWithOrdinalIgnoreCaseHelper(span, value);
89158
}
@@ -220,7 +289,7 @@ internal static bool EndsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan
220289
{
221290
Debug.Assert(value.Length != 0);
222291

223-
if (s_invariantMode)
292+
if (GlobalizationMode.Invariant)
224293
{
225294
return EndsWithOrdinalHelper(span, value);
226295
}
@@ -237,7 +306,7 @@ internal static bool EndsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, Re
237306
{
238307
Debug.Assert(value.Length != 0);
239308

240-
if (s_invariantMode)
309+
if (GlobalizationMode.Invariant)
241310
{
242311
return EndsWithOrdinalIgnoreCaseHelper(span, value);
243312
}

src/mscorlib/src/System/Globalization/CompareInfo.Unix.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ private unsafe bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source,
380380
char* a = ap;
381381
char* b = bp;
382382

383-
while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!HighCharTable[*a]) && (!HighCharTable[*b]))
383+
while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
384384
{
385385
int charA = *a;
386386
int charB = *b;
@@ -427,7 +427,7 @@ private unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlyS
427427
char* a = ap;
428428
char* b = bp;
429429

430-
while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!HighCharTable[*a]) && (!HighCharTable[*b]))
430+
while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
431431
{
432432
int charA = *a;
433433
int charB = *b;
@@ -605,7 +605,7 @@ private SortVersion GetSortVersion()
605605
}
606606

607607
// See https://github.com/dotnet/coreclr/blob/master/src/utilcode/util_nodependencies.cpp#L970
608-
private static readonly bool[] HighCharTable = new bool[0x80]
608+
private static readonly bool[] s_highCharTable = new bool[0x80]
609609
{
610610
true, /* 0x0, 0x0 */
611611
true, /* 0x1, .*/

src/mscorlib/src/System/ThrowHelper.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,8 @@ internal enum ExceptionArgument
470470
ownedMemory,
471471
pointer,
472472
start,
473-
format
473+
format,
474+
culture
474475
}
475476

476477
//

0 commit comments

Comments
 (0)