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 ////////////////////////////////////////////////////////////////////////////
9 // Purpose: This class implements a set of methods for comparing
13 ////////////////////////////////////////////////////////////////////////////
15 using System.Reflection;
16 using System.Diagnostics;
17 using System.Runtime.InteropServices;
18 using System.Runtime.Serialization;
20 namespace System.Globalization
23 public enum CompareOptions
26 IgnoreCase = 0x00000001,
27 IgnoreNonSpace = 0x00000002,
28 IgnoreSymbols = 0x00000004,
29 IgnoreKanaType = 0x00000008, // ignore kanatype
30 IgnoreWidth = 0x00000010, // ignore width
31 OrdinalIgnoreCase = 0x10000000, // This flag can not be used with other flags.
32 StringSort = 0x20000000, // use string sort method
33 Ordinal = 0x40000000, // This flag can not be used with other flags.
37 [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
38 public partial class CompareInfo : IDeserializationCallback
40 // Mask used to check if IndexOf()/LastIndexOf()/IsPrefix()/IsPostfix() has the right flags.
41 private const CompareOptions ValidIndexMaskOffFlags =
42 ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
43 CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
45 // Mask used to check if Compare() has the right flags.
46 private const CompareOptions ValidCompareMaskOffFlags =
47 ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
48 CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
50 // Mask used to check if GetHashCodeOfString() has the right flags.
51 private const CompareOptions ValidHashCodeOfStringMaskOffFlags =
52 ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
53 CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
55 // Mask used to check if we have the right flags.
56 private const CompareOptions ValidSortkeyCtorMaskOffFlags =
57 ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
58 CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
61 // CompareInfos have an interesting identity. They are attached to the locale that created them,
62 // ie: en-US would have an en-US sort. For haw-US (custom), then we serialize it as haw-US.
63 // The interesting part is that since haw-US doesn't have its own sort, it has to point at another
64 // locale, which is what SCOMPAREINFO does.
65 [OptionalField(VersionAdded = 2)]
66 private string m_name; // The name used to construct this CompareInfo. Do not rename (binary serialization)
69 private string _sortName; // The name that defines our behavior
71 [OptionalField(VersionAdded = 3)]
72 private SortVersion m_SortVersion; // Do not rename (binary serialization)
74 // _invariantMode is defined for the perf reason as accessing the instance field is faster than access the static property GlobalizationMode.Invariant
76 private readonly bool _invariantMode = GlobalizationMode.Invariant;
78 private int culture; // Do not rename (binary serialization). The fields sole purpose is to support Desktop serialization.
80 internal CompareInfo(CultureInfo culture)
82 m_name = culture._name;
86 /*=================================GetCompareInfo==========================
87 **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
88 ** Warning: The assembly versioning mechanism is dead!
89 **Returns: The CompareInfo for the specified culture.
91 ** culture the ID of the culture
92 ** assembly the assembly which contains the sorting table.
94 ** ArugmentNullException when the assembly is null
95 ** ArgumentException if culture is invalid.
96 ============================================================================*/
97 // Assembly constructor should be deprecated, we don't act on the assembly information any more
98 public static CompareInfo GetCompareInfo(int culture, Assembly assembly)
100 // Parameter checking.
101 if (assembly == null)
103 throw new ArgumentNullException(nameof(assembly));
105 if (assembly != typeof(Object).Module.Assembly)
107 throw new ArgumentException(SR.Argument_OnlyMscorlib);
110 return GetCompareInfo(culture);
113 /*=================================GetCompareInfo==========================
114 **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
115 ** The purpose of this method is to provide version for CompareInfo tables.
116 **Returns: The CompareInfo for the specified culture.
118 ** name the name of the culture
119 ** assembly the assembly which contains the sorting table.
121 ** ArugmentNullException when the assembly is null
122 ** ArgumentException if name is invalid.
123 ============================================================================*/
124 // Assembly constructor should be deprecated, we don't act on the assembly information any more
125 public static CompareInfo GetCompareInfo(string name, Assembly assembly)
127 if (name == null || assembly == null)
129 throw new ArgumentNullException(name == null ? nameof(name) : nameof(assembly));
132 if (assembly != typeof(Object).Module.Assembly)
134 throw new ArgumentException(SR.Argument_OnlyMscorlib);
137 return GetCompareInfo(name);
140 /*=================================GetCompareInfo==========================
141 **Action: Get the CompareInfo for the specified culture.
142 ** This method is provided for ease of integration with NLS-based software.
143 **Returns: The CompareInfo for the specified culture.
145 ** culture the ID of the culture.
147 ** ArgumentException if culture is invalid.
148 ============================================================================*/
149 // People really shouldn't be calling LCID versions, no custom support
150 public static CompareInfo GetCompareInfo(int culture)
152 if (CultureData.IsCustomCultureId(culture))
154 // Customized culture cannot be created by the LCID.
155 throw new ArgumentException(SR.Argument_CustomCultureCannotBePassedByNumber, nameof(culture));
158 return CultureInfo.GetCultureInfo(culture).CompareInfo;
161 /*=================================GetCompareInfo==========================
162 **Action: Get the CompareInfo for the specified culture.
163 **Returns: The CompareInfo for the specified culture.
165 ** name the name of the culture.
167 ** ArgumentException if name is invalid.
168 ============================================================================*/
170 public static CompareInfo GetCompareInfo(string name)
174 throw new ArgumentNullException(nameof(name));
177 return CultureInfo.GetCultureInfo(name).CompareInfo;
180 public static unsafe bool IsSortable(char ch)
182 if (GlobalizationMode.Invariant)
187 return IsSortable(pChar, 1);
190 public static unsafe bool IsSortable(string text)
194 // A null param is invalid here.
195 throw new ArgumentNullException(nameof(text));
198 if (text.Length == 0)
200 // A zero length string is not invalid, but it is also not sortable.
204 if (GlobalizationMode.Invariant)
209 fixed (char *pChar = text)
211 return IsSortable(pChar, text.Length);
216 private void OnDeserializing(StreamingContext ctx)
221 void IDeserializationCallback.OnDeserialization(object sender)
227 private void OnDeserialized(StreamingContext ctx)
232 private void OnDeserialized()
234 // If we didn't have a name, use the LCID
237 // From whidbey, didn't have a name
238 CultureInfo ci = CultureInfo.GetCultureInfo(this.culture);
243 InitSort(CultureInfo.GetCultureInfo(m_name));
248 private void OnSerializing(StreamingContext ctx)
250 // This is merely for serialization compatibility with Whidbey/Orcas, it can go away when we don't want that compat any more.
251 culture = CultureInfo.GetCultureInfo(this.Name).LCID; // This is the lcid of the constructing culture (still have to dereference to get target sort)
252 Debug.Assert(m_name != null, "CompareInfo.OnSerializing - expected m_name to be set already");
255 ///////////////////////////----- Name -----/////////////////////////////////
257 // Returns the name of the culture (well actually, of the sort).
258 // Very important for providing a non-LCID way of identifying
261 // Note that this name isn't dereferenced in case the CompareInfo is a different locale
262 // which is consistent with the behaviors of earlier versions. (so if you ask for a sort
263 // and the locale's changed behavior, then you'll get changed behavior, which is like
264 // what happens for a version update)
266 ////////////////////////////////////////////////////////////////////////
268 public virtual string Name
272 Debug.Assert(m_name != null, "CompareInfo.Name Expected _name to be set");
273 if (m_name == "zh-CHT" || m_name == "zh-CHS")
282 ////////////////////////////////////////////////////////////////////////
286 // Compares the two strings with the given options. Returns 0 if the
287 // two strings are equal, a number less than 0 if string1 is less
288 // than string2, and a number greater than 0 if string1 is greater
291 ////////////////////////////////////////////////////////////////////////
293 public virtual int Compare(string string1, string string2)
295 return (Compare(string1, string2, CompareOptions.None));
298 public unsafe virtual int Compare(string string1, string string2, CompareOptions options)
300 if (options == CompareOptions.OrdinalIgnoreCase)
302 return String.Compare(string1, string2, StringComparison.OrdinalIgnoreCase);
305 // Verify the options before we do any real comparison.
306 if ((options & CompareOptions.Ordinal) != 0)
308 if (options != CompareOptions.Ordinal)
310 throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
313 return String.CompareOrdinal(string1, string2);
316 if ((options & ValidCompareMaskOffFlags) != 0)
318 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
321 //Our paradigm is that null sorts less than any other string and
322 //that two nulls sort as equal.
329 return (-1); // null < non-null
333 return (1); // non-null > null
338 if ((options & CompareOptions.IgnoreCase) != 0)
339 return CompareOrdinalIgnoreCase(string1, 0, string1.Length, string2, 0, string2.Length);
341 return String.CompareOrdinal(string1, string2);
344 return CompareString(string1.AsReadOnlySpan(), string2.AsReadOnlySpan(), options);
347 // TODO https://github.com/dotnet/coreclr/issues/13827:
348 // This method shouldn't be necessary, as we should be able to just use the overload
349 // that takes two spans. But due to this issue, that's adding significant overhead.
350 internal unsafe int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions options)
352 if (options == CompareOptions.OrdinalIgnoreCase)
354 return CompareOrdinalIgnoreCase(string1, string2.AsReadOnlySpan());
357 // Verify the options before we do any real comparison.
358 if ((options & CompareOptions.Ordinal) != 0)
360 if (options != CompareOptions.Ordinal)
362 throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
365 return string.CompareOrdinal(string1, string2.AsReadOnlySpan());
368 if ((options & ValidCompareMaskOffFlags) != 0)
370 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
373 // null sorts less than any other string.
381 return (options & CompareOptions.IgnoreCase) != 0 ?
382 CompareOrdinalIgnoreCase(string1, string2.AsReadOnlySpan()) :
383 string.CompareOrdinal(string1, string2.AsReadOnlySpan());
386 return CompareString(string1, string2, options);
389 // TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly?
390 internal unsafe virtual int Compare(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
392 if (options == CompareOptions.OrdinalIgnoreCase)
394 return CompareOrdinalIgnoreCase(string1, string2);
397 // Verify the options before we do any real comparison.
398 if ((options & CompareOptions.Ordinal) != 0)
400 if (options != CompareOptions.Ordinal)
402 throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
405 return string.CompareOrdinal(string1, string2);
408 if ((options & ValidCompareMaskOffFlags) != 0)
410 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
415 return (options & CompareOptions.IgnoreCase) != 0 ?
416 CompareOrdinalIgnoreCase(string1, string2) :
417 string.CompareOrdinal(string1, string2);
420 return CompareString(string1, string2, options);
423 ////////////////////////////////////////////////////////////////////////
427 // Compares the specified regions of the two strings with the given
429 // Returns 0 if the two strings are equal, a number less than 0 if
430 // string1 is less than string2, and a number greater than 0 if
431 // string1 is greater than string2.
433 ////////////////////////////////////////////////////////////////////////
436 public unsafe virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2)
438 return Compare(string1, offset1, length1, string2, offset2, length2, 0);
442 public virtual int Compare(string string1, int offset1, string string2, int offset2, CompareOptions options)
444 return Compare(string1, offset1, string1 == null ? 0 : string1.Length - offset1,
445 string2, offset2, string2 == null ? 0 : string2.Length - offset2, options);
449 public virtual int Compare(string string1, int offset1, string string2, int offset2)
451 return Compare(string1, offset1, string2, offset2, 0);
455 public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options)
457 if (options == CompareOptions.OrdinalIgnoreCase)
459 int result = String.Compare(string1, offset1, string2, offset2, length1 < length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase);
460 if ((length1 != length2) && result == 0)
461 return (length1 > length2 ? 1 : -1);
466 if (length1 < 0 || length2 < 0)
468 throw new ArgumentOutOfRangeException((length1 < 0) ? nameof(length1) : nameof(length2), SR.ArgumentOutOfRange_NeedPosNum);
470 if (offset1 < 0 || offset2 < 0)
472 throw new ArgumentOutOfRangeException((offset1 < 0) ? nameof(offset1) : nameof(offset2), SR.ArgumentOutOfRange_NeedPosNum);
474 if (offset1 > (string1 == null ? 0 : string1.Length) - length1)
476 throw new ArgumentOutOfRangeException(nameof(string1), SR.ArgumentOutOfRange_OffsetLength);
478 if (offset2 > (string2 == null ? 0 : string2.Length) - length2)
480 throw new ArgumentOutOfRangeException(nameof(string2), SR.ArgumentOutOfRange_OffsetLength);
482 if ((options & CompareOptions.Ordinal) != 0)
484 if (options != CompareOptions.Ordinal)
486 throw new ArgumentException(SR.Argument_CompareOptionOrdinal,
490 else if ((options & ValidCompareMaskOffFlags) != 0)
492 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
496 // Check for the null case.
511 if (options == CompareOptions.Ordinal)
513 return CompareOrdinal(string1, offset1, length1,
514 string2, offset2, length2);
519 if ((options & CompareOptions.IgnoreCase) != 0)
520 return CompareOrdinalIgnoreCase(string1, offset1, length1, string2, offset2, length2);
522 return CompareOrdinal(string1, offset1, length1, string2, offset2, length2);
525 return CompareString(
526 string1.AsReadOnlySpan().Slice(offset1, length1),
527 string2.AsReadOnlySpan().Slice(offset2, length2),
531 private static int CompareOrdinal(string string1, int offset1, int length1, string string2, int offset2, int length2)
533 int result = String.CompareOrdinal(string1, offset1, string2, offset2,
534 (length1 < length2 ? length1 : length2));
535 if ((length1 != length2) && result == 0)
537 return (length1 > length2 ? 1 : -1);
543 // CompareOrdinalIgnoreCase compare two string ordinally with ignoring the case.
544 // it assumes the strings are Ascii string till we hit non Ascii character in strA or strB and then we continue the comparison by
547 internal static unsafe int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB)
549 Debug.Assert(indexA + lengthA <= strA.Length);
550 Debug.Assert(indexB + lengthB <= strB.Length);
551 return CompareOrdinalIgnoreCase(strA.AsReadOnlySpan().Slice(indexA, lengthA), strB.AsReadOnlySpan().Slice(indexB, lengthB));
554 internal static unsafe int CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
556 int length = Math.Min(strA.Length, strB.Length);
559 fixed (char* ap = &MemoryMarshal.GetReference(strA))
560 fixed (char* bp = &MemoryMarshal.GetReference(strB))
565 // in InvariantMode we support all range and not only the ascii characters.
566 char maxChar = (char) (GlobalizationMode.Invariant ? 0xFFFF : 0x80);
568 while (length != 0 && (*a <= maxChar) && (*b <= maxChar))
580 // uppercase both chars - notice that we need just one compare per char
581 if ((uint)(charA - 'a') <= 'z' - 'a') charA -= 0x20;
582 if ((uint)(charB - 'a') <= 'z' - 'a') charB -= 0x20;
584 // Return the (case-insensitive) difference between them.
586 return charA - charB;
594 return strA.Length - strB.Length;
596 Debug.Assert(!GlobalizationMode.Invariant);
600 return CompareStringOrdinalIgnoreCase(a, strA.Length - range, b, strB.Length - range);
604 ////////////////////////////////////////////////////////////////////////
608 // Determines whether prefix is a prefix of string. If prefix equals
609 // String.Empty, true is returned.
611 ////////////////////////////////////////////////////////////////////////
612 public virtual bool IsPrefix(string source, string prefix, CompareOptions options)
614 if (source == null || prefix == null)
616 throw new ArgumentNullException((source == null ? nameof(source) : nameof(prefix)),
617 SR.ArgumentNull_String);
620 if (prefix.Length == 0)
625 if (source.Length == 0)
630 if (options == CompareOptions.OrdinalIgnoreCase)
632 return source.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
635 if (options == CompareOptions.Ordinal)
637 return source.StartsWith(prefix, StringComparison.Ordinal);
640 if ((options & ValidIndexMaskOffFlags) != 0)
642 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
647 return source.StartsWith(prefix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
650 return StartsWith(source, prefix, options);
653 public virtual bool IsPrefix(string source, string prefix)
655 return (IsPrefix(source, prefix, 0));
658 ////////////////////////////////////////////////////////////////////////
662 // Determines whether suffix is a suffix of string. If suffix equals
663 // String.Empty, true is returned.
665 ////////////////////////////////////////////////////////////////////////
666 public virtual bool IsSuffix(string source, string suffix, CompareOptions options)
668 if (source == null || suffix == null)
670 throw new ArgumentNullException((source == null ? nameof(source) : nameof(suffix)),
671 SR.ArgumentNull_String);
674 if (suffix.Length == 0)
679 if (source.Length == 0)
684 if (options == CompareOptions.OrdinalIgnoreCase)
686 return source.EndsWith(suffix, StringComparison.OrdinalIgnoreCase);
689 if (options == CompareOptions.Ordinal)
691 return source.EndsWith(suffix, StringComparison.Ordinal);
694 if ((options & ValidIndexMaskOffFlags) != 0)
696 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
701 return source.EndsWith(suffix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
704 return EndsWith(source, suffix, options);
708 public virtual bool IsSuffix(string source, string suffix)
710 return (IsSuffix(source, suffix, 0));
713 ////////////////////////////////////////////////////////////////////////
717 // Returns the first index where value is found in string. The
718 // search starts from startIndex and ends at endIndex. Returns -1 if
719 // the specified value is not found. If value equals String.Empty,
720 // startIndex is returned. Throws IndexOutOfRange if startIndex or
721 // endIndex is less than zero or greater than the length of string.
722 // Throws ArgumentException if value is null.
724 ////////////////////////////////////////////////////////////////////////
727 public virtual int IndexOf(string source, char value)
730 throw new ArgumentNullException(nameof(source));
732 return IndexOf(source, value, 0, source.Length, CompareOptions.None);
736 public virtual int IndexOf(string source, string value)
739 throw new ArgumentNullException(nameof(source));
741 return IndexOf(source, value, 0, source.Length, CompareOptions.None);
745 public virtual int IndexOf(string source, char value, CompareOptions options)
748 throw new ArgumentNullException(nameof(source));
750 return IndexOf(source, value, 0, source.Length, options);
754 public virtual int IndexOf(string source, string value, CompareOptions options)
757 throw new ArgumentNullException(nameof(source));
759 return IndexOf(source, value, 0, source.Length, options);
762 public virtual int IndexOf(string source, char value, int startIndex)
765 throw new ArgumentNullException(nameof(source));
767 return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
770 public virtual int IndexOf(string source, string value, int startIndex)
773 throw new ArgumentNullException(nameof(source));
775 return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
778 public virtual int IndexOf(string source, char value, int startIndex, CompareOptions options)
781 throw new ArgumentNullException(nameof(source));
783 return IndexOf(source, value, startIndex, source.Length - startIndex, options);
787 public virtual int IndexOf(string source, string value, int startIndex, CompareOptions options)
790 throw new ArgumentNullException(nameof(source));
792 return IndexOf(source, value, startIndex, source.Length - startIndex, options);
796 public virtual int IndexOf(string source, char value, int startIndex, int count)
798 return IndexOf(source, value, startIndex, count, CompareOptions.None);
802 public virtual int IndexOf(string source, string value, int startIndex, int count)
804 return IndexOf(source, value, startIndex, count, CompareOptions.None);
807 public unsafe virtual int IndexOf(string source, char value, int startIndex, int count, CompareOptions options)
811 throw new ArgumentNullException(nameof(source));
813 if (startIndex < 0 || startIndex > source.Length)
814 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
816 if (count < 0 || startIndex > source.Length - count)
817 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
819 if (options == CompareOptions.OrdinalIgnoreCase)
821 return source.IndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
824 // Validate CompareOptions
825 // Ordinal can't be selected with other flags
826 if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
827 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
830 return IndexOfOrdinal(source, new string(value, 1), startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
832 return IndexOfCore(source, new string(value, 1), startIndex, count, options, null);
835 public unsafe virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options)
839 throw new ArgumentNullException(nameof(source));
841 throw new ArgumentNullException(nameof(value));
843 if (startIndex > source.Length)
845 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
848 // In Everett we used to return -1 for empty string even if startIndex is negative number so we keeping same behavior here.
849 // We return 0 if both source and value are empty strings for Everett compatibility too.
850 if (source.Length == 0)
852 if (value.Length == 0)
861 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
864 if (count < 0 || startIndex > source.Length - count)
865 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
867 if (options == CompareOptions.OrdinalIgnoreCase)
869 return IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
872 // Validate CompareOptions
873 // Ordinal can't be selected with other flags
874 if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
875 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
878 return IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
880 return IndexOfCore(source, value, startIndex, count, options, null);
883 // The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated
884 // and the caller is passing a valid matchLengthPtr pointer.
885 internal unsafe int IndexOf(string source, string value, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
887 Debug.Assert(source != null);
888 Debug.Assert(value != null);
889 Debug.Assert(startIndex >= 0);
890 Debug.Assert(matchLengthPtr != null);
893 if (source.Length == 0)
895 if (value.Length == 0)
902 if (startIndex >= source.Length)
907 if (options == CompareOptions.OrdinalIgnoreCase)
909 int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
912 *matchLengthPtr = value.Length;
919 int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
922 *matchLengthPtr = value.Length;
927 return IndexOfCore(source, value, startIndex, count, options, matchLengthPtr);
930 internal int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
934 return InvariantIndexOf(source, value, startIndex, count, ignoreCase);
937 return IndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
940 ////////////////////////////////////////////////////////////////////////
944 // Returns the last index where value is found in string. The
945 // search starts from startIndex and ends at endIndex. Returns -1 if
946 // the specified value is not found. If value equals String.Empty,
947 // endIndex is returned. Throws IndexOutOfRange if startIndex or
948 // endIndex is less than zero or greater than the length of string.
949 // Throws ArgumentException if value is null.
951 ////////////////////////////////////////////////////////////////////////
954 public virtual int LastIndexOf(String source, char value)
957 throw new ArgumentNullException(nameof(source));
959 // Can't start at negative index, so make sure we check for the length == 0 case.
960 return LastIndexOf(source, value, source.Length - 1, source.Length, CompareOptions.None);
964 public virtual int LastIndexOf(string source, string value)
967 throw new ArgumentNullException(nameof(source));
969 // Can't start at negative index, so make sure we check for the length == 0 case.
970 return LastIndexOf(source, value, source.Length - 1,
971 source.Length, CompareOptions.None);
975 public virtual int LastIndexOf(string source, char value, CompareOptions options)
978 throw new ArgumentNullException(nameof(source));
980 // Can't start at negative index, so make sure we check for the length == 0 case.
981 return LastIndexOf(source, value, source.Length - 1,
982 source.Length, options);
985 public virtual int LastIndexOf(string source, string value, CompareOptions options)
988 throw new ArgumentNullException(nameof(source));
990 // Can't start at negative index, so make sure we check for the length == 0 case.
991 return LastIndexOf(source, value, source.Length - 1, source.Length, options);
994 public virtual int LastIndexOf(string source, char value, int startIndex)
996 return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
1000 public virtual int LastIndexOf(string source, string value, int startIndex)
1002 return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
1005 public virtual int LastIndexOf(string source, char value, int startIndex, CompareOptions options)
1007 return LastIndexOf(source, value, startIndex, startIndex + 1, options);
1011 public virtual int LastIndexOf(string source, string value, int startIndex, CompareOptions options)
1013 return LastIndexOf(source, value, startIndex, startIndex + 1, options);
1017 public virtual int LastIndexOf(string source, char value, int startIndex, int count)
1019 return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
1023 public virtual int LastIndexOf(string source, string value, int startIndex, int count)
1025 return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
1029 public virtual int LastIndexOf(string source, char value, int startIndex, int count, CompareOptions options)
1033 throw new ArgumentNullException(nameof(source));
1035 // Validate CompareOptions
1036 // Ordinal can't be selected with other flags
1037 if ((options & ValidIndexMaskOffFlags) != 0 &&
1038 (options != CompareOptions.Ordinal) &&
1039 (options != CompareOptions.OrdinalIgnoreCase))
1040 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
1042 // Special case for 0 length input strings
1043 if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
1046 // Make sure we're not out of range
1047 if (startIndex < 0 || startIndex > source.Length)
1048 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
1050 // Make sure that we allow startIndex == source.Length
1051 if (startIndex == source.Length)
1058 // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
1059 if (count < 0 || startIndex - count + 1 < 0)
1060 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
1062 if (options == CompareOptions.OrdinalIgnoreCase)
1064 return source.LastIndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
1068 return InvariantLastIndexOf(source, new string(value, 1), startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
1070 return LastIndexOfCore(source, value.ToString(), startIndex, count, options);
1074 public virtual int LastIndexOf(string source, string value, int startIndex, int count, CompareOptions options)
1078 throw new ArgumentNullException(nameof(source));
1080 throw new ArgumentNullException(nameof(value));
1082 // Validate CompareOptions
1083 // Ordinal can't be selected with other flags
1084 if ((options & ValidIndexMaskOffFlags) != 0 &&
1085 (options != CompareOptions.Ordinal) &&
1086 (options != CompareOptions.OrdinalIgnoreCase))
1087 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
1089 // Special case for 0 length input strings
1090 if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
1091 return (value.Length == 0) ? 0 : -1;
1093 // Make sure we're not out of range
1094 if (startIndex < 0 || startIndex > source.Length)
1095 throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
1097 // Make sure that we allow startIndex == source.Length
1098 if (startIndex == source.Length)
1104 // If we are looking for nothing, just return 0
1105 if (value.Length == 0 && count >= 0 && startIndex - count + 1 >= 0)
1109 // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
1110 if (count < 0 || startIndex - count + 1 < 0)
1111 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
1113 if (options == CompareOptions.OrdinalIgnoreCase)
1115 return LastIndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
1119 return InvariantLastIndexOf(source, value, startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
1121 return LastIndexOfCore(source, value, startIndex, count, options);
1124 internal int LastIndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
1128 return InvariantLastIndexOf(source, value, startIndex, count, ignoreCase);
1131 return LastIndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
1134 ////////////////////////////////////////////////////////////////////////
1138 // Gets the SortKey for the given string with the given options.
1140 ////////////////////////////////////////////////////////////////////////
1141 public virtual SortKey GetSortKey(string source, CompareOptions options)
1144 return InvariantCreateSortKey(source, options);
1146 return CreateSortKey(source, options);
1150 public virtual SortKey GetSortKey(string source)
1153 return InvariantCreateSortKey(source, CompareOptions.None);
1155 return CreateSortKey(source, CompareOptions.None);
1158 ////////////////////////////////////////////////////////////////////////
1162 // Implements Object.Equals(). Returns a boolean indicating whether
1163 // or not object refers to the same CompareInfo as the current
1166 ////////////////////////////////////////////////////////////////////////
1169 public override bool Equals(Object value)
1171 CompareInfo that = value as CompareInfo;
1175 return this.Name == that.Name;
1182 ////////////////////////////////////////////////////////////////////////
1186 // Implements Object.GetHashCode(). Returns the hash code for the
1187 // CompareInfo. The hash code is guaranteed to be the same for
1188 // CompareInfo A and B where A.Equals(B) is true.
1190 ////////////////////////////////////////////////////////////////////////
1193 public override int GetHashCode()
1195 return (this.Name.GetHashCode());
1199 ////////////////////////////////////////////////////////////////////////
1201 // GetHashCodeOfString
1203 // This internal method allows a method that allows the equivalent of creating a Sortkey for a
1204 // string from CompareInfo, and generate a hashcode value from it. It is not very convenient
1205 // to use this method as is and it creates an unnecessary Sortkey object that will be GC'ed.
1207 // The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both
1208 // the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects
1209 // treat the string the same way, this implementation will treat them differently (the same way that
1210 // Sortkey does at the moment).
1212 // This method will never be made public itself, but public consumers of it could be created, e.g.:
1214 // string.GetHashCode(CultureInfo)
1215 // string.GetHashCode(CompareInfo)
1216 // string.GetHashCode(CultureInfo, CompareOptions)
1217 // string.GetHashCode(CompareInfo, CompareOptions)
1220 // (the methods above that take a CultureInfo would use CultureInfo.CompareInfo)
1222 ////////////////////////////////////////////////////////////////////////
1223 internal int GetHashCodeOfString(string source, CompareOptions options)
1226 // Parameter validation
1230 throw new ArgumentNullException(nameof(source));
1233 if ((options & ValidHashCodeOfStringMaskOffFlags) != 0)
1235 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
1238 return GetHashCodeOfStringCore(source, options);
1241 public virtual int GetHashCode(string source, CompareOptions options)
1245 throw new ArgumentNullException(nameof(source));
1248 if (options == CompareOptions.Ordinal)
1250 return source.GetHashCode();
1253 if (options == CompareOptions.OrdinalIgnoreCase)
1255 return TextInfo.GetHashCodeOrdinalIgnoreCase(source);
1259 // GetHashCodeOfString does more parameters validation. basically will throw when
1260 // having Ordinal, OrdinalIgnoreCase and StringSort
1263 return GetHashCodeOfString(source, options);
1266 ////////////////////////////////////////////////////////////////////////
1270 // Implements Object.ToString(). Returns a string describing the
1273 ////////////////////////////////////////////////////////////////////////
1274 public override string ToString()
1276 return ("CompareInfo - " + this.Name);
1279 public SortVersion Version
1283 if (m_SortVersion == null)
1287 m_SortVersion = new SortVersion(0, CultureInfo.LOCALE_INVARIANT, new Guid(0, 0, 0, 0, 0, 0, 0,
1288 (byte) (CultureInfo.LOCALE_INVARIANT >> 24),
1289 (byte) ((CultureInfo.LOCALE_INVARIANT & 0x00FF0000) >> 16),
1290 (byte) ((CultureInfo.LOCALE_INVARIANT & 0x0000FF00) >> 8),
1291 (byte) (CultureInfo.LOCALE_INVARIANT & 0xFF)));
1295 m_SortVersion = GetSortVersion();
1299 return m_SortVersion;
1307 return CultureInfo.GetCultureInfo(Name).LCID;