1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
6 using System
.Collections
.Generic
;
7 using System
.Diagnostics
;
8 using System
.Runtime
.CompilerServices
;
9 using System
.Runtime
.InteropServices
;
10 using System
.Security
;
11 using System
.Threading
;
13 using Internal
.Runtime
.CompilerServices
;
15 namespace System
.Globalization
17 public partial class CompareInfo
20 private IntPtr _sortHandle
;
23 private bool _isAsciiEqualityOrdinal
;
25 private void InitSort(CultureInfo culture
)
27 _sortName
= culture
.SortName
;
29 if (GlobalizationMode
.Invariant
)
31 _isAsciiEqualityOrdinal
= true;
35 _isAsciiEqualityOrdinal
= (_sortName
== "en-US" || _sortName
== "");
37 _sortHandle
= SortHandleCache
.GetCachedSortHandle(_sortName
);
41 internal static unsafe int IndexOfOrdinalCore(string source
, string value, int startIndex
, int count
, bool ignoreCase
)
43 Debug
.Assert(!GlobalizationMode
.Invariant
);
45 Debug
.Assert(source
!= null);
46 Debug
.Assert(value != null);
48 if (value.Length
== 0)
53 if (count
< value.Length
)
60 fixed (char* pSource
= source
)
62 int index
= Interop
.Globalization
.IndexOfOrdinalIgnoreCase(value, value.Length
, pSource
+ startIndex
, count
, findLast
: false);
69 int endIndex
= startIndex
+ (count
- value.Length
);
70 for (int i
= startIndex
; i
<= endIndex
; i
++)
72 int valueIndex
, sourceIndex
;
74 for (valueIndex
= 0, sourceIndex
= i
;
75 valueIndex
< value.Length
&& source
[sourceIndex
] == value[valueIndex
];
76 valueIndex
++, sourceIndex
++) ;
78 if (valueIndex
== value.Length
)
87 internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan
<char> source
, ReadOnlySpan
<char> value, bool ignoreCase
, bool fromBeginning
)
89 Debug
.Assert(!GlobalizationMode
.Invariant
);
91 Debug
.Assert(source
.Length
!= 0);
92 Debug
.Assert(value.Length
!= 0);
94 if (source
.Length
< value.Length
)
101 fixed (char* pSource
= &MemoryMarshal
.GetReference(source
))
102 fixed (char* pValue
= &MemoryMarshal
.GetReference(value))
104 return Interop
.Globalization
.IndexOfOrdinalIgnoreCase(pValue
, value.Length
, pSource
, source
.Length
, findLast
: !fromBeginning
);
108 int startIndex
, endIndex
, jump
;
111 // Left to right, from zero to last possible index in the source string.
112 // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
114 endIndex
= source
.Length
- value.Length
+ 1;
119 // Right to left, from first possible index in the source string to zero.
120 // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
121 startIndex
= source
.Length
- value.Length
;
126 for (int i
= startIndex
; i
!= endIndex
; i
+= jump
)
128 int valueIndex
, sourceIndex
;
130 for (valueIndex
= 0, sourceIndex
= i
;
131 valueIndex
< value.Length
&& source
[sourceIndex
] == value[valueIndex
];
132 valueIndex
++, sourceIndex
++)
135 if (valueIndex
== value.Length
)
144 internal static unsafe int LastIndexOfOrdinalCore(string source
, string value, int startIndex
, int count
, bool ignoreCase
)
146 Debug
.Assert(!GlobalizationMode
.Invariant
);
148 Debug
.Assert(source
!= null);
149 Debug
.Assert(value != null);
151 if (value.Length
== 0)
156 if (count
< value.Length
)
161 // startIndex is the index into source where we start search backwards from.
162 // leftStartIndex is the index into source of the start of the string that is
163 // count characters away from startIndex.
164 int leftStartIndex
= startIndex
- count
+ 1;
168 fixed (char* pSource
= source
)
170 int lastIndex
= Interop
.Globalization
.IndexOfOrdinalIgnoreCase(value, value.Length
, pSource
+ leftStartIndex
, count
, findLast
: true);
171 return lastIndex
!= -1 ?
172 leftStartIndex
+ lastIndex
:
177 for (int i
= startIndex
- value.Length
+ 1; i
>= leftStartIndex
; i
--)
179 int valueIndex
, sourceIndex
;
181 for (valueIndex
= 0, sourceIndex
= i
;
182 valueIndex
< value.Length
&& source
[sourceIndex
] == value[valueIndex
];
183 valueIndex
++, sourceIndex
++) ;
185 if (valueIndex
== value.Length
) {
193 private static unsafe int CompareStringOrdinalIgnoreCase(ref char string1
, int count1
, ref char string2
, int count2
)
195 Debug
.Assert(!GlobalizationMode
.Invariant
);
197 fixed (char* char1
= &string1
)
198 fixed (char* char2
= &string2
)
200 return Interop
.Globalization
.CompareStringOrdinalIgnoreCase(char1
, count1
, char2
, count2
);
204 // TODO https://github.com/dotnet/coreclr/issues/13827:
205 // This method shouldn't be necessary, as we should be able to just use the overload
206 // that takes two spans. But due to this issue, that's adding significant overhead.
207 private unsafe int CompareString(ReadOnlySpan
<char> string1
, string string2
, CompareOptions options
)
209 Debug
.Assert(!GlobalizationMode
.Invariant
);
210 Debug
.Assert(string2
!= null);
211 Debug
.Assert((options
& (CompareOptions
.Ordinal
| CompareOptions
.OrdinalIgnoreCase
)) == 0);
213 fixed (char* pString1
= &MemoryMarshal
.GetReference(string1
))
214 fixed (char* pString2
= &string2
.GetRawStringData())
216 return Interop
.Globalization
.CompareString(_sortHandle
, pString1
, string1
.Length
, pString2
, string2
.Length
, options
);
220 private unsafe int CompareString(ReadOnlySpan
<char> string1
, ReadOnlySpan
<char> string2
, CompareOptions options
)
222 Debug
.Assert(!GlobalizationMode
.Invariant
);
223 Debug
.Assert((options
& (CompareOptions
.Ordinal
| CompareOptions
.OrdinalIgnoreCase
)) == 0);
225 fixed (char* pString1
= &MemoryMarshal
.GetReference(string1
))
226 fixed (char* pString2
= &MemoryMarshal
.GetReference(string2
))
228 return Interop
.Globalization
.CompareString(_sortHandle
, pString1
, string1
.Length
, pString2
, string2
.Length
, options
);
232 internal unsafe int IndexOfCore(string source
, string target
, int startIndex
, int count
, CompareOptions options
, int* matchLengthPtr
)
234 Debug
.Assert(!GlobalizationMode
.Invariant
);
236 Debug
.Assert(!string.IsNullOrEmpty(source
));
237 Debug
.Assert(target
!= null);
238 Debug
.Assert((options
& CompareOptions
.OrdinalIgnoreCase
) == 0);
239 Debug
.Assert((options
& CompareOptions
.Ordinal
) == 0);
242 if (_isAsciiEqualityOrdinal
&& CanUseAsciiOrdinalForOptions(options
) && source
.IsFastSort() && target
.IsFastSort())
244 int index
= IndexOf(source
, target
, startIndex
, count
, GetOrdinalCompareOptions(options
));
247 if (matchLengthPtr
!= null)
248 *matchLengthPtr
= target
.Length
;
254 fixed (char* pSource
= source
)
255 fixed (char* pTarget
= target
)
257 int index
= Interop
.Globalization
.IndexOf(_sortHandle
, pTarget
, target
.Length
, pSource
+ startIndex
, count
, options
, matchLengthPtr
);
259 return index
!= -1 ? index
+ startIndex
: -1;
263 // For now, this method is only called from Span APIs with either options == CompareOptions.None or CompareOptions.IgnoreCase
264 internal unsafe int IndexOfCore(ReadOnlySpan
<char> source
, ReadOnlySpan
<char> target
, CompareOptions options
, int* matchLengthPtr
, bool fromBeginning
)
266 Debug
.Assert(!GlobalizationMode
.Invariant
);
267 Debug
.Assert(source
.Length
!= 0);
268 Debug
.Assert(target
.Length
!= 0);
270 if (_isAsciiEqualityOrdinal
&& CanUseAsciiOrdinalForOptions(options
))
272 if ((options
& CompareOptions
.IgnoreCase
) == CompareOptions
.IgnoreCase
)
273 return IndexOfOrdinalIgnoreCaseHelper(source
, target
, options
, matchLengthPtr
, fromBeginning
);
275 return IndexOfOrdinalHelper(source
, target
, options
, matchLengthPtr
, fromBeginning
);
279 fixed (char* pSource
= &MemoryMarshal
.GetReference(source
))
280 fixed (char* pTarget
= &MemoryMarshal
.GetReference(target
))
283 return Interop
.Globalization
.IndexOf(_sortHandle
, pTarget
, target
.Length
, pSource
, source
.Length
, options
, matchLengthPtr
);
285 return Interop
.Globalization
.LastIndexOf(_sortHandle
, pTarget
, target
.Length
, pSource
, source
.Length
, options
);
291 /// Duplicate of IndexOfOrdinalHelper that also handles ignore case. Can't converge both methods
292 /// as the JIT wouldn't be able to optimize the ignoreCase path away.
294 /// <returns></returns>
295 private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan
<char> source
, ReadOnlySpan
<char> target
, CompareOptions options
, int* matchLengthPtr
, bool fromBeginning
)
297 Debug
.Assert(!GlobalizationMode
.Invariant
);
299 Debug
.Assert(!source
.IsEmpty
);
300 Debug
.Assert(!target
.IsEmpty
);
301 Debug
.Assert(_isAsciiEqualityOrdinal
);
303 fixed (char* ap
= &MemoryMarshal
.GetReference(source
))
304 fixed (char* bp
= &MemoryMarshal
.GetReference(target
))
309 if (target
.Length
> source
.Length
)
312 for (int j
= 0; j
< target
.Length
; j
++)
314 char targetChar
= *(b
+ j
);
315 if (targetChar
>= 0x80 || s_highCharTable
[targetChar
])
319 int startIndex
, endIndex
, jump
;
322 // Left to right, from zero to last possible index in the source string.
323 // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
325 endIndex
= source
.Length
- target
.Length
+ 1;
330 // Right to left, from first possible index in the source string to zero.
331 // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
332 startIndex
= source
.Length
- target
.Length
;
337 for (int i
= startIndex
; i
!= endIndex
; i
+= jump
)
342 for (; targetIndex
< target
.Length
; targetIndex
++, sourceIndex
++)
344 char valueChar
= *(a
+ sourceIndex
);
345 char targetChar
= *(b
+ targetIndex
);
347 if (valueChar
== targetChar
&& valueChar
< 0x80 && !s_highCharTable
[valueChar
])
352 // uppercase both chars - notice that we need just one compare per char
353 if ((uint)(valueChar
- 'a') <= ('z' - 'a'))
354 valueChar
= (char)(valueChar
- 0x20);
355 if ((uint)(targetChar
- 'a') <= ('z' - 'a'))
356 targetChar
= (char)(targetChar
- 0x20);
358 if (valueChar
>= 0x80 || s_highCharTable
[valueChar
])
360 else if (valueChar
!= targetChar
)
364 if (targetIndex
== target
.Length
)
366 if (matchLengthPtr
!= null)
367 *matchLengthPtr
= target
.Length
;
375 return Interop
.Globalization
.IndexOf(_sortHandle
, b
, target
.Length
, a
, source
.Length
, options
, matchLengthPtr
);
377 return Interop
.Globalization
.LastIndexOf(_sortHandle
, b
, target
.Length
, a
, source
.Length
, options
);
381 private unsafe int IndexOfOrdinalHelper(ReadOnlySpan
<char> source
, ReadOnlySpan
<char> target
, CompareOptions options
, int* matchLengthPtr
, bool fromBeginning
)
383 Debug
.Assert(!GlobalizationMode
.Invariant
);
385 Debug
.Assert(!source
.IsEmpty
);
386 Debug
.Assert(!target
.IsEmpty
);
387 Debug
.Assert(_isAsciiEqualityOrdinal
);
389 fixed (char* ap
= &MemoryMarshal
.GetReference(source
))
390 fixed (char* bp
= &MemoryMarshal
.GetReference(target
))
395 if (target
.Length
> source
.Length
)
398 for (int j
= 0; j
< target
.Length
; j
++)
400 char targetChar
= *(b
+ j
);
401 if (targetChar
>= 0x80 || s_highCharTable
[targetChar
])
405 int startIndex
, endIndex
, jump
;
408 // Left to right, from zero to last possible index in the source string.
409 // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
411 endIndex
= source
.Length
- target
.Length
+ 1;
416 // Right to left, from first possible index in the source string to zero.
417 // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
418 startIndex
= source
.Length
- target
.Length
;
423 for (int i
= startIndex
; i
!= endIndex
; i
+= jump
)
428 for (; targetIndex
< target
.Length
; targetIndex
++, sourceIndex
++)
430 char valueChar
= *(a
+ sourceIndex
);
431 char targetChar
= *(b
+ targetIndex
);
433 if (valueChar
>= 0x80 || s_highCharTable
[valueChar
])
435 else if (valueChar
!= targetChar
)
439 if (targetIndex
== target
.Length
)
441 if (matchLengthPtr
!= null)
442 *matchLengthPtr
= target
.Length
;
450 return Interop
.Globalization
.IndexOf(_sortHandle
, b
, target
.Length
, a
, source
.Length
, options
, matchLengthPtr
);
452 return Interop
.Globalization
.LastIndexOf(_sortHandle
, b
, target
.Length
, a
, source
.Length
, options
);
456 private unsafe int LastIndexOfCore(string source
, string target
, int startIndex
, int count
, CompareOptions options
)
458 Debug
.Assert(!GlobalizationMode
.Invariant
);
460 Debug
.Assert(!string.IsNullOrEmpty(source
));
461 Debug
.Assert(target
!= null);
462 Debug
.Assert((options
& CompareOptions
.OrdinalIgnoreCase
) == 0);
464 if (target
.Length
== 0)
469 if (options
== CompareOptions
.Ordinal
)
471 return LastIndexOfOrdinalCore(source
, target
, startIndex
, count
, ignoreCase
: false);
475 if (_isAsciiEqualityOrdinal
&& CanUseAsciiOrdinalForOptions(options
) && source
.IsFastSort() && target
.IsFastSort())
477 return LastIndexOf(source
, target
, startIndex
, count
, GetOrdinalCompareOptions(options
));
481 // startIndex is the index into source where we start search backwards from. leftStartIndex is the index into source
482 // of the start of the string that is count characters away from startIndex.
483 int leftStartIndex
= (startIndex
- count
+ 1);
485 fixed (char* pSource
= source
)
486 fixed (char* pTarget
= target
)
488 int lastIndex
= Interop
.Globalization
.LastIndexOf(_sortHandle
, pTarget
, target
.Length
, pSource
+ (startIndex
- count
+ 1), count
, options
);
490 return lastIndex
!= -1 ? lastIndex
+ leftStartIndex
: -1;
494 private bool StartsWith(string source
, string prefix
, CompareOptions options
)
496 Debug
.Assert(!GlobalizationMode
.Invariant
);
498 Debug
.Assert(!string.IsNullOrEmpty(source
));
499 Debug
.Assert(!string.IsNullOrEmpty(prefix
));
500 Debug
.Assert((options
& (CompareOptions
.Ordinal
| CompareOptions
.OrdinalIgnoreCase
)) == 0);
503 if (_isAsciiEqualityOrdinal
&& CanUseAsciiOrdinalForOptions(options
) && source
.IsFastSort() && prefix
.IsFastSort())
505 return IsPrefix(source
, prefix
, GetOrdinalCompareOptions(options
));
509 return Interop
.Globalization
.StartsWith(_sortHandle
, prefix
, prefix
.Length
, source
, source
.Length
, options
);
512 private unsafe bool StartsWith(ReadOnlySpan
<char> source
, ReadOnlySpan
<char> prefix
, CompareOptions options
)
514 Debug
.Assert(!GlobalizationMode
.Invariant
);
516 Debug
.Assert(!source
.IsEmpty
);
517 Debug
.Assert(!prefix
.IsEmpty
);
518 Debug
.Assert((options
& (CompareOptions
.Ordinal
| CompareOptions
.OrdinalIgnoreCase
)) == 0);
520 if (_isAsciiEqualityOrdinal
&& CanUseAsciiOrdinalForOptions(options
))
522 if (source
.Length
< prefix
.Length
)
527 if ((options
& CompareOptions
.IgnoreCase
) == CompareOptions
.IgnoreCase
)
529 return StartsWithOrdinalIgnoreCaseHelper(source
, prefix
, options
);
533 return StartsWithOrdinalHelper(source
, prefix
, options
);
538 fixed (char* pSource
= &MemoryMarshal
.GetReference(source
))
539 fixed (char* pPrefix
= &MemoryMarshal
.GetReference(prefix
))
541 return Interop
.Globalization
.StartsWith(_sortHandle
, pPrefix
, prefix
.Length
, pSource
, source
.Length
, options
);
546 private unsafe bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan
<char> source
, ReadOnlySpan
<char> prefix
, CompareOptions options
)
548 Debug
.Assert(!GlobalizationMode
.Invariant
);
550 Debug
.Assert(!source
.IsEmpty
);
551 Debug
.Assert(!prefix
.IsEmpty
);
552 Debug
.Assert(_isAsciiEqualityOrdinal
);
553 Debug
.Assert(source
.Length
>= prefix
.Length
);
555 int length
= prefix
.Length
;
557 fixed (char* ap
= &MemoryMarshal
.GetReference(source
))
558 fixed (char* bp
= &MemoryMarshal
.GetReference(prefix
))
563 while (length
!= 0 && (*a
< 0x80) && (*b
< 0x80) && (!s_highCharTable
[*a
]) && (!s_highCharTable
[*b
]))
575 // uppercase both chars - notice that we need just one compare per char
576 if ((uint)(charA
- 'a') <= (uint)('z' - 'a')) charA
-= 0x20;
577 if ((uint)(charB
- 'a') <= (uint)('z' - 'a')) charB
-= 0x20;
587 if (length
== 0) return true;
588 return Interop
.Globalization
.StartsWith(_sortHandle
, b
, length
, a
, length
, options
);
592 private unsafe bool StartsWithOrdinalHelper(ReadOnlySpan
<char> source
, ReadOnlySpan
<char> prefix
, CompareOptions options
)
594 Debug
.Assert(!GlobalizationMode
.Invariant
);
596 Debug
.Assert(!source
.IsEmpty
);
597 Debug
.Assert(!prefix
.IsEmpty
);
598 Debug
.Assert(_isAsciiEqualityOrdinal
);
599 Debug
.Assert(source
.Length
>= prefix
.Length
);
601 int length
= prefix
.Length
;
603 fixed (char* ap
= &MemoryMarshal
.GetReference(source
))
604 fixed (char* bp
= &MemoryMarshal
.GetReference(prefix
))
609 while (length
!= 0 && (*a
< 0x80) && (*b
< 0x80) && (!s_highCharTable
[*a
]) && (!s_highCharTable
[*b
]))
622 if (length
== 0) return true;
623 return Interop
.Globalization
.StartsWith(_sortHandle
, b
, length
, a
, length
, options
);
627 private bool EndsWith(string source
, string suffix
, CompareOptions options
)
629 Debug
.Assert(!GlobalizationMode
.Invariant
);
631 Debug
.Assert(!string.IsNullOrEmpty(source
));
632 Debug
.Assert(!string.IsNullOrEmpty(suffix
));
633 Debug
.Assert((options
& (CompareOptions
.Ordinal
| CompareOptions
.OrdinalIgnoreCase
)) == 0);
636 if (_isAsciiEqualityOrdinal
&& CanUseAsciiOrdinalForOptions(options
) && source
.IsFastSort() && suffix
.IsFastSort())
638 return IsSuffix(source
, suffix
, GetOrdinalCompareOptions(options
));
642 return Interop
.Globalization
.EndsWith(_sortHandle
, suffix
, suffix
.Length
, source
, source
.Length
, options
);
645 private unsafe bool EndsWith(ReadOnlySpan
<char> source
, ReadOnlySpan
<char> suffix
, CompareOptions options
)
647 Debug
.Assert(!GlobalizationMode
.Invariant
);
649 Debug
.Assert(!source
.IsEmpty
);
650 Debug
.Assert(!suffix
.IsEmpty
);
651 Debug
.Assert((options
& (CompareOptions
.Ordinal
| CompareOptions
.OrdinalIgnoreCase
)) == 0);
653 if (_isAsciiEqualityOrdinal
&& CanUseAsciiOrdinalForOptions(options
))
655 if (source
.Length
< suffix
.Length
)
660 if ((options
& CompareOptions
.IgnoreCase
) == CompareOptions
.IgnoreCase
)
662 return EndsWithOrdinalIgnoreCaseHelper(source
, suffix
, options
);
666 return EndsWithOrdinalHelper(source
, suffix
, options
);
671 fixed (char* pSource
= &MemoryMarshal
.GetReference(source
))
672 fixed (char* pSuffix
= &MemoryMarshal
.GetReference(suffix
))
674 return Interop
.Globalization
.EndsWith(_sortHandle
, pSuffix
, suffix
.Length
, pSource
, source
.Length
, options
);
679 private unsafe bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan
<char> source
, ReadOnlySpan
<char> suffix
, CompareOptions options
)
681 Debug
.Assert(!GlobalizationMode
.Invariant
);
683 Debug
.Assert(!source
.IsEmpty
);
684 Debug
.Assert(!suffix
.IsEmpty
);
685 Debug
.Assert(_isAsciiEqualityOrdinal
);
686 Debug
.Assert(source
.Length
>= suffix
.Length
);
688 int length
= suffix
.Length
;
690 fixed (char* ap
= &MemoryMarshal
.GetReference(source
))
691 fixed (char* bp
= &MemoryMarshal
.GetReference(suffix
))
693 char* a
= ap
+ source
.Length
- 1;
694 char* b
= bp
+ suffix
.Length
- 1;
696 while (length
!= 0 && (*a
< 0x80) && (*b
< 0x80) && (!s_highCharTable
[*a
]) && (!s_highCharTable
[*b
]))
708 // uppercase both chars - notice that we need just one compare per char
709 if ((uint)(charA
- 'a') <= (uint)('z' - 'a')) charA
-= 0x20;
710 if ((uint)(charB
- 'a') <= (uint)('z' - 'a')) charB
-= 0x20;
720 if (length
== 0) return true;
721 return Interop
.Globalization
.EndsWith(_sortHandle
, b
- length
+ 1, length
, a
- length
+ 1, length
, options
);
725 private unsafe bool EndsWithOrdinalHelper(ReadOnlySpan
<char> source
, ReadOnlySpan
<char> suffix
, CompareOptions options
)
727 Debug
.Assert(!GlobalizationMode
.Invariant
);
729 Debug
.Assert(!source
.IsEmpty
);
730 Debug
.Assert(!suffix
.IsEmpty
);
731 Debug
.Assert(_isAsciiEqualityOrdinal
);
732 Debug
.Assert(source
.Length
>= suffix
.Length
);
734 int length
= suffix
.Length
;
736 fixed (char* ap
= &MemoryMarshal
.GetReference(source
))
737 fixed (char* bp
= &MemoryMarshal
.GetReference(suffix
))
739 char* a
= ap
+ source
.Length
- 1;
740 char* b
= bp
+ suffix
.Length
- 1;
742 while (length
!= 0 && (*a
< 0x80) && (*b
< 0x80) && (!s_highCharTable
[*a
]) && (!s_highCharTable
[*b
]))
755 if (length
== 0) return true;
756 return Interop
.Globalization
.EndsWith(_sortHandle
, b
- length
+ 1, length
, a
- length
+ 1, length
, options
);
760 private unsafe SortKey
CreateSortKey(string source
, CompareOptions options
)
762 Debug
.Assert(!GlobalizationMode
.Invariant
);
764 if (source
==null) { throw new ArgumentNullException(nameof(source)); }
766 if ((options
& ValidSortkeyCtorMaskOffFlags
) != 0)
768 throw new ArgumentException(SR
.Argument_InvalidFlag
, nameof(options
));
772 if (source
.Length
== 0)
774 keyData
= Array
.Empty
<byte>();
778 fixed (char* pSource
= source
)
780 int sortKeyLength
= Interop
.Globalization
.GetSortKey(_sortHandle
, pSource
, source
.Length
, null, 0, options
);
781 keyData
= new byte[sortKeyLength
];
783 fixed (byte* pSortKey
= keyData
)
785 if (Interop
.Globalization
.GetSortKey(_sortHandle
, pSource
, source
.Length
, pSortKey
, sortKeyLength
, options
) != sortKeyLength
)
787 throw new ArgumentException(SR
.Arg_ExternalException
);
793 return new SortKey(Name
, source
, options
, keyData
);
796 private static unsafe bool IsSortable(char *text
, int length
)
798 Debug
.Assert(!GlobalizationMode
.Invariant
);
803 while (index
< length
)
805 if (char.IsHighSurrogate(text
[index
]))
807 if (index
== length
- 1 || !char.IsLowSurrogate(text
[index
+1]))
808 return false; // unpaired surrogate
810 uc
= CharUnicodeInfo
.GetUnicodeCategory(char.ConvertToUtf32(text
[index
], text
[index
+1]));
811 if (uc
== UnicodeCategory
.PrivateUse
|| uc
== UnicodeCategory
.OtherNotAssigned
)
818 if (char.IsLowSurrogate(text
[index
]))
820 return false; // unpaired surrogate
823 uc
= CharUnicodeInfo
.GetUnicodeCategory(text
[index
]);
824 if (uc
== UnicodeCategory
.PrivateUse
|| uc
== UnicodeCategory
.OtherNotAssigned
)
835 // -----------------------------
836 // ---- PAL layer ends here ----
837 // -----------------------------
839 internal unsafe int GetHashCodeOfStringCore(ReadOnlySpan
<char> source
, CompareOptions options
)
841 Debug
.Assert(!GlobalizationMode
.Invariant
);
842 Debug
.Assert((options
& (CompareOptions
.Ordinal
| CompareOptions
.OrdinalIgnoreCase
)) == 0);
844 if (source
.Length
== 0)
849 // according to ICU User Guide the performance of ucol_getSortKey is worse when it is called with null output buffer
850 // the solution is to try to fill the sort key in a temporary buffer of size equal 4 x string length
851 // 1MB is the biggest array that can be rented from ArrayPool.Shared without memory allocation
852 int sortKeyLength
= (source
.Length
> 1024 * 1024 / 4) ? 0 : 4 * source
.Length
;
854 byte[]? borrowedArray
= null;
855 Span
<byte> sortKey
= sortKeyLength
<= 1024
856 ? stackalloc byte[1024]
857 : (borrowedArray
= ArrayPool
<byte>.Shared
.Rent(sortKeyLength
));
859 fixed (char* pSource
= &MemoryMarshal
.GetReference(source
))
861 fixed (byte* pSortKey
= &MemoryMarshal
.GetReference(sortKey
))
863 sortKeyLength
= Interop
.Globalization
.GetSortKey(_sortHandle
, pSource
, source
.Length
, pSortKey
, sortKey
.Length
, options
);
866 if (sortKeyLength
> sortKey
.Length
) // slow path for big strings
868 if (borrowedArray
!= null)
870 ArrayPool
<byte>.Shared
.Return(borrowedArray
);
873 sortKey
= (borrowedArray
= ArrayPool
<byte>.Shared
.Rent(sortKeyLength
));
875 fixed (byte* pSortKey
= &MemoryMarshal
.GetReference(sortKey
))
877 sortKeyLength
= Interop
.Globalization
.GetSortKey(_sortHandle
, pSource
, source
.Length
, pSortKey
, sortKey
.Length
, options
);
882 if (sortKeyLength
== 0 || sortKeyLength
> sortKey
.Length
) // internal error (0) or a bug (2nd call failed) in ucol_getSortKey
884 throw new ArgumentException(SR
.Arg_ExternalException
);
887 int hash
= Marvin
.ComputeHash32(sortKey
.Slice(0, sortKeyLength
), Marvin
.DefaultSeed
);
889 if (borrowedArray
!= null)
891 ArrayPool
<byte>.Shared
.Return(borrowedArray
);
897 private static CompareOptions
GetOrdinalCompareOptions(CompareOptions options
)
899 if ((options
& CompareOptions
.IgnoreCase
) == CompareOptions
.IgnoreCase
)
901 return CompareOptions
.OrdinalIgnoreCase
;
905 return CompareOptions
.Ordinal
;
909 private static bool CanUseAsciiOrdinalForOptions(CompareOptions options
)
911 // Unlike the other Ignore options, IgnoreSymbols impacts ASCII characters (e.g. ').
912 return (options
& CompareOptions
.IgnoreSymbols
) == 0;
915 private SortVersion
GetSortVersion()
917 Debug
.Assert(!GlobalizationMode
.Invariant
);
919 int sortVersion
= Interop
.Globalization
.GetSortVersion(_sortHandle
);
920 return new SortVersion(sortVersion
, LCID
, new Guid(sortVersion
, 0, 0, 0, 0, 0, 0,
922 (byte) ((LCID
& 0x00FF0000) >> 16),
923 (byte) ((LCID
& 0x0000FF00) >> 8),
924 (byte) (LCID
& 0xFF)));
927 private static class SortHandleCache
929 // in most scenarios there is a limited number of cultures with limited number of sort options
930 // so caching the sort handles and not freeing them is OK, see https://github.com/dotnet/coreclr/pull/25117 for more
931 private static readonly Dictionary
<string, IntPtr
> s_sortNameToSortHandleCache
= new Dictionary
<string, IntPtr
>();
933 internal static IntPtr
GetCachedSortHandle(string sortName
)
935 lock (s_sortNameToSortHandleCache
)
937 if (!s_sortNameToSortHandleCache
.TryGetValue(sortName
, out IntPtr result
))
939 Interop
.Globalization
.ResultCode resultCode
= Interop
.Globalization
.GetSortHandle(sortName
, out result
);
941 if (resultCode
== Interop
.Globalization
.ResultCode
.OutOfMemory
)
942 throw new OutOfMemoryException();
943 else if (resultCode
!= Interop
.Globalization
.ResultCode
.Success
)
944 throw new ExternalException(SR
.Arg_ExternalException
);
948 s_sortNameToSortHandleCache
.Add(sortName
, result
);
952 Interop
.Globalization
.CloseSortHandle(result
);
963 // See https://github.com/dotnet/coreclr/blob/master/src/utilcode/util_nodependencies.cpp#L970
964 private static readonly bool[] s_highCharTable
= new bool[0x80]
1093 true, /*0x7F, \x7f*/