Fix StyleCop warning SA1121 (use built-in types)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Globalization / CompareInfo.Unix.cs
blobf6ca669d61867cb6902b9d8bee17860b6eca8b43
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.
5 using System.Buffers;
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
19 [NonSerialized]
20 private IntPtr _sortHandle;
22 [NonSerialized]
23 private bool _isAsciiEqualityOrdinal;
25 private void InitSort(CultureInfo culture)
27 _sortName = culture.SortName;
29 if (GlobalizationMode.Invariant)
31 _isAsciiEqualityOrdinal = true;
33 else
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)
50 return startIndex;
53 if (count < value.Length)
55 return -1;
58 if (ignoreCase)
60 fixed (char* pSource = source)
62 int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(value, value.Length, pSource + startIndex, count, findLast: false);
63 return index != -1 ?
64 startIndex + index :
65 -1;
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)
80 return i;
84 return -1;
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)
96 return -1;
99 if (ignoreCase)
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;
109 if (fromBeginning)
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.
113 startIndex = 0;
114 endIndex = source.Length - value.Length + 1;
115 jump = 1;
117 else
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;
122 endIndex = -1;
123 jump = -1;
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)
137 return i;
141 return -1;
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)
153 return startIndex;
156 if (count < value.Length)
158 return -1;
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;
166 if (ignoreCase)
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) {
186 return i;
190 return -1;
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);
241 #if CORECLR
242 if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && target.IsFastSort())
244 int index = IndexOf(source, target, startIndex, count, GetOrdinalCompareOptions(options));
245 if (index != -1)
247 if (matchLengthPtr != null)
248 *matchLengthPtr = target.Length;
250 return index;
252 #endif
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);
274 else
275 return IndexOfOrdinalHelper(source, target, options, matchLengthPtr, fromBeginning);
277 else
279 fixed (char* pSource = &MemoryMarshal.GetReference(source))
280 fixed (char* pTarget = &MemoryMarshal.GetReference(target))
282 if (fromBeginning)
283 return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options, matchLengthPtr);
284 else
285 return Interop.Globalization.LastIndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options);
290 /// <summary>
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.
293 /// </summary>
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))
306 char* a = ap;
307 char* b = bp;
309 if (target.Length > source.Length)
310 goto InteropCall;
312 for (int j = 0; j < target.Length; j++)
314 char targetChar = *(b + j);
315 if (targetChar >= 0x80 || s_highCharTable[targetChar])
316 goto InteropCall;
319 int startIndex, endIndex, jump;
320 if (fromBeginning)
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.
324 startIndex = 0;
325 endIndex = source.Length - target.Length + 1;
326 jump = 1;
328 else
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;
333 endIndex = -1;
334 jump = -1;
337 for (int i = startIndex; i != endIndex; i += jump)
339 int targetIndex = 0;
340 int sourceIndex = i;
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])
349 continue;
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])
359 goto InteropCall;
360 else if (valueChar != targetChar)
361 break;
364 if (targetIndex == target.Length)
366 if (matchLengthPtr != null)
367 *matchLengthPtr = target.Length;
368 return i;
372 return -1;
373 InteropCall:
374 if (fromBeginning)
375 return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
376 else
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))
392 char* a = ap;
393 char* b = bp;
395 if (target.Length > source.Length)
396 goto InteropCall;
398 for (int j = 0; j < target.Length; j++)
400 char targetChar = *(b + j);
401 if (targetChar >= 0x80 || s_highCharTable[targetChar])
402 goto InteropCall;
405 int startIndex, endIndex, jump;
406 if (fromBeginning)
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.
410 startIndex = 0;
411 endIndex = source.Length - target.Length + 1;
412 jump = 1;
414 else
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;
419 endIndex = -1;
420 jump = -1;
423 for (int i = startIndex; i != endIndex; i += jump)
425 int targetIndex = 0;
426 int sourceIndex = i;
428 for (; targetIndex < target.Length; targetIndex++, sourceIndex++)
430 char valueChar = *(a + sourceIndex);
431 char targetChar = *(b + targetIndex);
433 if (valueChar >= 0x80 || s_highCharTable[valueChar])
434 goto InteropCall;
435 else if (valueChar != targetChar)
436 break;
439 if (targetIndex == target.Length)
441 if (matchLengthPtr != null)
442 *matchLengthPtr = target.Length;
443 return i;
447 return -1;
448 InteropCall:
449 if (fromBeginning)
450 return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
451 else
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)
466 return startIndex;
469 if (options == CompareOptions.Ordinal)
471 return LastIndexOfOrdinalCore(source, target, startIndex, count, ignoreCase: false);
474 #if CORECLR
475 if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && target.IsFastSort())
477 return LastIndexOf(source, target, startIndex, count, GetOrdinalCompareOptions(options));
479 #endif
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);
502 #if CORECLR
503 if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && prefix.IsFastSort())
505 return IsPrefix(source, prefix, GetOrdinalCompareOptions(options));
507 #endif
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)
524 return false;
527 if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
529 return StartsWithOrdinalIgnoreCaseHelper(source, prefix, options);
531 else
533 return StartsWithOrdinalHelper(source, prefix, options);
536 else
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))
560 char* a = ap;
561 char* b = bp;
563 while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
565 int charA = *a;
566 int charB = *b;
568 if (charA == charB)
570 a++; b++;
571 length--;
572 continue;
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;
579 if (charA != charB)
580 return false;
582 // Next char
583 a++; b++;
584 length--;
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))
606 char* a = ap;
607 char* b = bp;
609 while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
611 int charA = *a;
612 int charB = *b;
614 if (charA != charB)
615 return false;
617 // Next char
618 a++; b++;
619 length--;
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);
635 #if CORECLR
636 if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && suffix.IsFastSort())
638 return IsSuffix(source, suffix, GetOrdinalCompareOptions(options));
640 #endif
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)
657 return false;
660 if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
662 return EndsWithOrdinalIgnoreCaseHelper(source, suffix, options);
664 else
666 return EndsWithOrdinalHelper(source, suffix, options);
669 else
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]))
698 int charA = *a;
699 int charB = *b;
701 if (charA == charB)
703 a--; b--;
704 length--;
705 continue;
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;
712 if (charA != charB)
713 return false;
715 // Next char
716 a--; b--;
717 length--;
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]))
744 int charA = *a;
745 int charB = *b;
747 if (charA != charB)
748 return false;
750 // Next char
751 a--; b--;
752 length--;
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));
771 byte [] keyData;
772 if (source.Length == 0)
774 keyData = Array.Empty<byte>();
776 else
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);
800 int index = 0;
801 UnicodeCategory uc;
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)
812 return false;
814 index += 2;
815 continue;
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)
826 return false;
829 index++;
832 return true;
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)
846 return 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);
894 return hash;
897 private static CompareOptions GetOrdinalCompareOptions(CompareOptions options)
899 if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
901 return CompareOptions.OrdinalIgnoreCase;
903 else
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,
921 (byte) (LCID >> 24),
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);
950 catch
952 Interop.Globalization.CloseSortHandle(result);
954 throw;
958 return 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]
966 true, /* 0x0, 0x0 */
967 true, /* 0x1, .*/
968 true, /* 0x2, .*/
969 true, /* 0x3, .*/
970 true, /* 0x4, .*/
971 true, /* 0x5, .*/
972 true, /* 0x6, .*/
973 true, /* 0x7, .*/
974 true, /* 0x8, .*/
975 false, /* 0x9, */
976 true, /* 0xA, */
977 false, /* 0xB, .*/
978 false, /* 0xC, .*/
979 true, /* 0xD, */
980 true, /* 0xE, .*/
981 true, /* 0xF, .*/
982 true, /* 0x10, .*/
983 true, /* 0x11, .*/
984 true, /* 0x12, .*/
985 true, /* 0x13, .*/
986 true, /* 0x14, .*/
987 true, /* 0x15, .*/
988 true, /* 0x16, .*/
989 true, /* 0x17, .*/
990 true, /* 0x18, .*/
991 true, /* 0x19, .*/
992 true, /* 0x1A, */
993 true, /* 0x1B, .*/
994 true, /* 0x1C, .*/
995 true, /* 0x1D, .*/
996 true, /* 0x1E, .*/
997 true, /* 0x1F, .*/
998 false, /*0x20, */
999 false, /*0x21, !*/
1000 false, /*0x22, "*/
1001 false, /*0x23, #*/
1002 false, /*0x24, $*/
1003 false, /*0x25, %*/
1004 false, /*0x26, &*/
1005 true, /*0x27, '*/
1006 false, /*0x28, (*/
1007 false, /*0x29, )*/
1008 false, /*0x2A **/
1009 false, /*0x2B, +*/
1010 false, /*0x2C, ,*/
1011 true, /*0x2D, -*/
1012 false, /*0x2E, .*/
1013 false, /*0x2F, /*/
1014 false, /*0x30, 0*/
1015 false, /*0x31, 1*/
1016 false, /*0x32, 2*/
1017 false, /*0x33, 3*/
1018 false, /*0x34, 4*/
1019 false, /*0x35, 5*/
1020 false, /*0x36, 6*/
1021 false, /*0x37, 7*/
1022 false, /*0x38, 8*/
1023 false, /*0x39, 9*/
1024 false, /*0x3A, :*/
1025 false, /*0x3B, ;*/
1026 false, /*0x3C, <*/
1027 false, /*0x3D, =*/
1028 false, /*0x3E, >*/
1029 false, /*0x3F, ?*/
1030 false, /*0x40, @*/
1031 false, /*0x41, A*/
1032 false, /*0x42, B*/
1033 false, /*0x43, C*/
1034 false, /*0x44, D*/
1035 false, /*0x45, E*/
1036 false, /*0x46, F*/
1037 false, /*0x47, G*/
1038 false, /*0x48, H*/
1039 false, /*0x49, I*/
1040 false, /*0x4A, J*/
1041 false, /*0x4B, K*/
1042 false, /*0x4C, L*/
1043 false, /*0x4D, M*/
1044 false, /*0x4E, N*/
1045 false, /*0x4F, O*/
1046 false, /*0x50, P*/
1047 false, /*0x51, Q*/
1048 false, /*0x52, R*/
1049 false, /*0x53, S*/
1050 false, /*0x54, T*/
1051 false, /*0x55, U*/
1052 false, /*0x56, V*/
1053 false, /*0x57, W*/
1054 false, /*0x58, X*/
1055 false, /*0x59, Y*/
1056 false, /*0x5A, Z*/
1057 false, /*0x5B, [*/
1058 false, /*0x5C, \*/
1059 false, /*0x5D, ]*/
1060 false, /*0x5E, ^*/
1061 false, /*0x5F, _*/
1062 false, /*0x60, `*/
1063 false, /*0x61, a*/
1064 false, /*0x62, b*/
1065 false, /*0x63, c*/
1066 false, /*0x64, d*/
1067 false, /*0x65, e*/
1068 false, /*0x66, f*/
1069 false, /*0x67, g*/
1070 false, /*0x68, h*/
1071 false, /*0x69, i*/
1072 false, /*0x6A, j*/
1073 false, /*0x6B, k*/
1074 false, /*0x6C, l*/
1075 false, /*0x6D, m*/
1076 false, /*0x6E, n*/
1077 false, /*0x6F, o*/
1078 false, /*0x70, p*/
1079 false, /*0x71, q*/
1080 false, /*0x72, r*/
1081 false, /*0x73, s*/
1082 false, /*0x74, t*/
1083 false, /*0x75, u*/
1084 false, /*0x76, v*/
1085 false, /*0x77, w*/
1086 false, /*0x78, x*/
1087 false, /*0x79, y*/
1088 false, /*0x7A, z*/
1089 false, /*0x7B, {*/
1090 false, /*0x7C, |*/
1091 false, /*0x7D, }*/
1092 false, /*0x7E, ~*/
1093 true, /*0x7F, \x7f*/