3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 ////////////////////////////////////////////////////////////////////////////
11 // Purpose: This class implements a set of methods for comparing
14 // Date: August 12, 1998
16 ////////////////////////////////////////////////////////////////////////////
18 namespace System.Globalization {
21 // We pass all of the sorting calls to the native side, preferrably to the OS to do
26 using System.Collections;
27 using System.Collections.Generic;
28 using System.Reflection;
29 using System.Runtime.Serialization;
30 using System.Runtime.CompilerServices;
31 using System.Runtime.ConstrainedExecution;
32 using System.Runtime.InteropServices;
33 using System.Runtime.Versioning;
34 using System.Threading;
35 using System.Security.Permissions;
36 using Microsoft.Win32;
37 using System.Security;
38 using System.Security.Principal;
39 using System.Diagnostics.Contracts;
42 // Options can be used during string comparison.
44 // Native implementation (COMNlsInfo.cpp & SortingTable.cpp) relies on the values of these,
45 // If you change the values below, be sure to change the values in native part as well.
51 [System.Runtime.InteropServices.ComVisible(true)]
52 public enum CompareOptions
55 IgnoreCase = 0x00000001,
56 IgnoreNonSpace = 0x00000002,
57 IgnoreSymbols = 0x00000004,
58 IgnoreKanaType = 0x00000008, // ignore kanatype
59 IgnoreWidth = 0x00000010, // ignore width
60 OrdinalIgnoreCase = 0x10000000, // This flag can not be used with other flags.
61 StringSort = 0x20000000, // use string sort method
62 Ordinal = 0x40000000, // This flag can not be used with other flags.
64 // StopOnNull = 0x10000000,
66 // StopOnNull is defined in SortingTable.h, but we didn't enable this option here.
67 // Do not use this value for other flags accidentally.
72 [System.Runtime.InteropServices.ComVisible(true)]
74 public partial class CompareInfo
75 #if FEATURE_SERIALIZATION
76 : IDeserializationCallback
79 // Mask used to check if IndexOf()/LastIndexOf()/IsPrefix()/IsPostfix() has the right flags.
80 private const CompareOptions ValidIndexMaskOffFlags =
81 ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
82 CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
84 // Mask used to check if Compare() has the right flags.
85 private const CompareOptions ValidCompareMaskOffFlags =
86 ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
87 CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
89 // Mask used to check if GetHashCodeOfString() has the right flags.
90 private const CompareOptions ValidHashCodeOfStringMaskOffFlags =
91 ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
92 CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
95 // CompareInfos have an interesting identity. They are attached to the locale that created them,
96 // ie: en-US would have an en-US sort. For haw-US (custom), then we serialize it as haw-US.
97 // The interesting part is that since haw-US doesn't have its own sort, it has to point at another
98 // locale, which is what SCOMPAREINFO does.
100 [OptionalField(VersionAdded = 2)]
101 private String m_name; // The name used to construct this CompareInfo
104 private String m_sortName; // The name that defines our behavior
108 private IntPtr m_dataHandle;
111 private IntPtr m_handleOrigin;
114 ////////////////////////////////////////////////////////////////////////
116 // CompareInfo Constructor
119 ////////////////////////////////////////////////////////////////////////
120 // Constructs an instance that most closely corresponds to the NLS locale
122 internal CompareInfo(CultureInfo culture)
124 this.m_name = culture.m_name;
125 this.m_sortName = culture.SortName;
129 this.m_dataHandle = InternalInitSortHandle(m_sortName, out handleOrigin);
130 this.m_handleOrigin = handleOrigin;
134 /*=================================GetCompareInfo==========================
135 **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
136 ** Warning: The assembly versioning mechanism is dead!
137 **Returns: The CompareInfo for the specified culture.
139 ** culture the ID of the culture
140 ** assembly the assembly which contains the sorting table.
142 ** ArugmentNullException when the assembly is null
143 ** ArgumentException if culture is invalid.
144 ============================================================================*/
147 public static CompareInfo GetCompareInfo(int culture, Assembly assembly){
148 // Parameter checking.
149 if (assembly == null) {
150 throw new ArgumentNullException("assembly");
152 if (assembly!=typeof(Object).Module.Assembly) {
153 throw new ArgumentException(Environment.GetResourceString("Argument_OnlyMscorlib"));
155 Contract.EndContractBlock();
157 return GetCompareInfo(culture);
162 /*=================================GetCompareInfo==========================
163 **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
164 ** The purpose of this method is to provide version for CompareInfo tables.
165 **Returns: The CompareInfo for the specified culture.
167 ** name the name of the culture
168 ** assembly the assembly which contains the sorting table.
170 ** ArugmentNullException when the assembly is null
171 ** ArgumentException if name is invalid.
172 ============================================================================*/
174 public static CompareInfo GetCompareInfo(String name, Assembly assembly){
175 if (name == null || assembly == null) {
176 throw new ArgumentNullException(name == null ? "name" : "assembly");
178 Contract.EndContractBlock();
180 if (assembly!=typeof(Object).Module.Assembly) {
181 throw new ArgumentException(Environment.GetResourceString("Argument_OnlyMscorlib"));
184 return GetCompareInfo(name);
187 /*=================================GetCompareInfo==========================
188 **Action: Get the CompareInfo for the specified culture.
189 ** This method is provided for ease of integration with NLS-based software.
190 **Returns: The CompareInfo for the specified culture.
192 ** culture the ID of the culture.
194 ** ArgumentException if culture is invalid.
195 ============================================================================*/
198 // People really shouldn't be calling LCID versions, no custom support
199 public static CompareInfo GetCompareInfo(int culture)
201 if (CultureData.IsCustomCultureId(culture))
203 // Customized culture cannot be created by the LCID.
204 throw new ArgumentException(Environment.GetResourceString("Argument_CustomCultureCannotBePassedByNumber", "culture"));
207 return CultureInfo.GetCultureInfo(culture).CompareInfo;
211 /*=================================GetCompareInfo==========================
212 **Action: Get the CompareInfo for the specified culture.
213 **Returns: The CompareInfo for the specified culture.
215 ** name the name of the culture.
217 ** ArgumentException if name is invalid.
218 ============================================================================*/
220 public static CompareInfo GetCompareInfo(String name)
224 throw new ArgumentNullException("name");
226 Contract.EndContractBlock();
228 return CultureInfo.GetCultureInfo(name).CompareInfo;
231 [System.Runtime.InteropServices.ComVisible(false)]
232 public static bool IsSortable(char ch) {
233 return(IsSortable(ch.ToString()));
236 [System.Security.SecuritySafeCritical]
237 [System.Runtime.InteropServices.ComVisible(false)]
238 public static bool IsSortable(String text) {
240 // A null param is invalid here.
241 throw new ArgumentNullException("text");
244 if (0 == text.Length) {
245 // A zero length string is not invalid, but it is also not sortable.
249 return Mono.Globalization.Unicode.MSCompatUnicodeTable.IsSortable (text);
251 CompareInfo c = CultureInfo.InvariantCulture.CompareInfo;
253 return (InternalIsSortable(c.m_dataHandle, c.m_handleOrigin, c.m_sortName, text, text.Length));
258 #if FEATURE_SERIALIZATION // Only defined when FEATURE_USE_LCID is also defined
259 #region Serialization
260 // the following fields are defined to keep the compatibility with Whidbey.
261 // don't change/remove the names/types of these fields.
262 #if FEATURE_USE_LCID || MONO
263 #pragma warning disable 169
264 [OptionalField(VersionAdded = 1)]
265 private int win32LCID; // mapped sort culture id of this instance
266 #pragma warning restore
267 private int culture; // the culture ID used to create this instance.
270 private void OnDeserializing(StreamingContext ctx)
275 private void OnDeserialized()
278 // If we didn't have a name, use the LCID
279 if (this.m_name == null)
281 // From whidbey, didn't have a name
282 ci = CultureInfo.GetCultureInfo(this.culture);
283 this.m_name = ci.m_name;
287 ci = CultureInfo.GetCultureInfo(m_name);
289 this.m_sortName = ci.SortName;
292 this.m_dataHandle = InternalInitSortHandle(m_sortName, out handleOrigin);
293 this.m_handleOrigin = handleOrigin;
298 private void OnDeserialized(StreamingContext ctx)
304 private void OnSerializing(StreamingContext ctx)
306 // This is merely for serialization compatibility with Whidbey/Orcas, it can go away when we don't want that compat any more.
307 culture = CultureInfo.GetCultureInfo(this.Name).LCID; // This is the lcid of the constructing culture (still have to dereference to get target sort)
308 Contract.Assert(m_name != null, "CompareInfo.OnSerializing - expected m_name to be set already");
311 void IDeserializationCallback.OnDeserialization(Object sender)
316 #endregion Serialization
317 #endif // FEATURE_SERIALIZATION
320 ///////////////////////////----- Name -----/////////////////////////////////
322 // Returns the name of the culture (well actually, of the sort).
323 // Very important for providing a non-LCID way of identifying
326 // Note that this name isn't dereferenced in case the CompareInfo is a different locale
327 // which is consistent with the behaviors of earlier versions. (so if you ask for a sort
328 // and the locale's changed behavior, then you'll get changed behavior, which is like
329 // what happens for a version update)
331 ////////////////////////////////////////////////////////////////////////
333 [System.Runtime.InteropServices.ComVisible(false)]
334 public virtual String Name
338 Contract.Assert(m_name != null, "CompareInfo.Name Expected m_name to be set");
339 if (m_name == "zh-CHT" || m_name == "zh-CHS")
348 // These flags are used in the native Win32. so we need to map the managed options to those flags
349 private const int LINGUISTIC_IGNORECASE = 0x00000010; // linguistically appropriate 'ignore case'
350 private const int NORM_IGNORECASE = 0x00000001; // Ignores case. (use LINGUISTIC_IGNORECASE instead)
351 private const int NORM_IGNOREKANATYPE = 0x00010000; // Does not differentiate between Hiragana and Katakana characters. Corresponding Hiragana and Katakana will compare as equal.
352 private const int LINGUISTIC_IGNOREDIACRITIC = 0x00000020; // linguistically appropriate 'ignore nonspace'
353 private const int NORM_IGNORENONSPACE = 0x00000002; // Ignores nonspacing. This flag also removes Japanese accent characters. (use LINGUISTIC_IGNOREDIACRITIC instead)
354 private const int NORM_IGNORESYMBOLS = 0x00000004; // Ignores symbols.
355 private const int NORM_IGNOREWIDTH = 0x00020000; // Does not differentiate between a single-byte character and the same character as a double-byte character.
356 private const int SORT_STRINGSORT = 0x00001000; // Treats punctuation the same as symbols.
357 private const int COMPARE_OPTIONS_ORDINAL = 0x40000000; // Ordinal (handled by Comnlsinfo)
358 internal const int NORM_LINGUISTIC_CASING = 0x08000000; // use linguistic rules for casing
361 private const int RESERVED_FIND_ASCII_STRING = 0x20000000; // This flag used only to tell the sorting DLL can assume the string characters are in ASCII.
364 internal static int GetNativeCompareFlags(CompareOptions options)
366 // some NLS VM functions can handle COMPARE_OPTIONS_ORDINAL
367 // in which case options should be simply cast to int instead of using this function
368 // Does not look like the best approach to me but for now I am going to leave it as it is
370 Contract.Assert(options != CompareOptions.OrdinalIgnoreCase, "[CompareInfo.GetNativeCompareFlags]CompareOptions.OrdinalIgnoreCase should be handled separately");
372 // Use "linguistic casing" by default (load the culture's casing exception tables)
373 int nativeCompareFlags = NORM_LINGUISTIC_CASING;
375 if ((options & CompareOptions.IgnoreCase) != 0) { nativeCompareFlags |= NORM_IGNORECASE; }
376 if ((options & CompareOptions.IgnoreKanaType) != 0) { nativeCompareFlags |= NORM_IGNOREKANATYPE; }
377 if ((options & CompareOptions.IgnoreNonSpace) != 0) { nativeCompareFlags |= NORM_IGNORENONSPACE; }
378 if ((options & CompareOptions.IgnoreSymbols) != 0) { nativeCompareFlags |= NORM_IGNORESYMBOLS; }
379 if ((options & CompareOptions.IgnoreWidth) != 0) { nativeCompareFlags |= NORM_IGNOREWIDTH; }
380 if ((options & CompareOptions.StringSort) != 0) { nativeCompareFlags |= SORT_STRINGSORT; }
382 // Suffix & Prefix shouldn't use this, make sure to turn off the NORM_LINGUISTIC_CASING flag
383 if (options == CompareOptions.Ordinal) { nativeCompareFlags = COMPARE_OPTIONS_ORDINAL; }
385 Contract.Assert(((options & ~(CompareOptions.IgnoreCase |
386 CompareOptions.IgnoreKanaType |
387 CompareOptions.IgnoreNonSpace |
388 CompareOptions.IgnoreSymbols |
389 CompareOptions.IgnoreWidth |
390 CompareOptions.StringSort)) == 0) ||
391 (options == CompareOptions.Ordinal), "[CompareInfo.GetNativeCompareFlags]Expected all flags to be handled");
393 Contract.Assert((nativeCompareFlags & RESERVED_FIND_ASCII_STRING) == 0, "[CompareInfo.GetNativeCompareFlags] RESERVED_FIND_ASCII_STRING shouldn't be set here");
395 return nativeCompareFlags;
399 ////////////////////////////////////////////////////////////////////////
403 // Compares the two strings with the given options. Returns 0 if the
404 // two strings are equal, a number less than 0 if string1 is less
405 // than string2, and a number greater than 0 if string1 is greater
408 ////////////////////////////////////////////////////////////////////////
411 public virtual int Compare(String string1, String string2)
413 return (Compare(string1, string2, CompareOptions.None));
416 [System.Security.SecuritySafeCritical] // auto-generated
417 public unsafe virtual int Compare(String string1, String string2, CompareOptions options){
419 if (options == CompareOptions.OrdinalIgnoreCase)
421 return String.Compare(string1, string2, StringComparison.OrdinalIgnoreCase);
424 // Verify the options before we do any real comparison.
425 if ((options & CompareOptions.Ordinal) != 0)
427 if (options != CompareOptions.Ordinal)
429 throw new ArgumentException(Environment.GetResourceString("Argument_CompareOptionOrdinal"), "options");
431 return String.CompareOrdinal(string1, string2);
434 if ((options & ValidCompareMaskOffFlags) != 0)
436 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
439 //Our paradigm is that null sorts less than any other string and
440 //that two nulls sort as equal.
441 if (string1 == null) {
442 if (string2 == null) {
445 return (-1); // null < non-null
447 if (string2 == null) {
448 return (1); // non-null > null
452 return internal_compare_switch (string1, 0, string1.Length, string2, 0, string2.Length, options);
454 return InternalCompareString(m_dataHandle, m_handleOrigin, m_sortName, string1, 0, string1.Length, string2, 0, string2.Length, GetNativeCompareFlags(options));
459 ////////////////////////////////////////////////////////////////////////
463 // Compares the specified regions of the two strings with the given
465 // Returns 0 if the two strings are equal, a number less than 0 if
466 // string1 is less than string2, and a number greater than 0 if
467 // string1 is greater than string2.
469 ////////////////////////////////////////////////////////////////////////
472 public unsafe virtual int Compare(String string1, int offset1, int length1, String string2, int offset2, int length2)
474 return Compare(string1, offset1, length1, string2, offset2, length2, 0);
478 public unsafe virtual int Compare(String string1, int offset1, String string2, int offset2, CompareOptions options)
480 return Compare(string1, offset1, string1 == null ? 0 : string1.Length-offset1,
481 string2, offset2, string2 == null ? 0 : string2.Length-offset2, options);
485 public unsafe virtual int Compare(String string1, int offset1, String string2, int offset2)
487 return Compare(string1, offset1, string2, offset2, 0);
491 [System.Security.SecuritySafeCritical] // auto-generated
492 public unsafe virtual int Compare(String string1, int offset1, int length1, String string2, int offset2, int length2, CompareOptions options)
494 if (options == CompareOptions.OrdinalIgnoreCase)
496 int result = String.Compare(string1, offset1, string2, offset2, length1<length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase);
497 if ((length1 != length2) && result == 0)
498 return (length1 > length2? 1: -1);
503 if (length1 < 0 || length2 < 0)
505 throw new ArgumentOutOfRangeException((length1 < 0) ? "length1" : "length2", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
507 if (offset1 < 0 || offset2 < 0)
509 throw new ArgumentOutOfRangeException((offset1 < 0) ? "offset1" : "offset2", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
511 if (offset1 > (string1 == null ? 0 : string1.Length) - length1)
513 throw new ArgumentOutOfRangeException("string1", Environment.GetResourceString("ArgumentOutOfRange_OffsetLength"));
515 if (offset2 > (string2 == null ? 0 : string2.Length) - length2)
517 throw new ArgumentOutOfRangeException("string2", Environment.GetResourceString("ArgumentOutOfRange_OffsetLength"));
519 if ((options & CompareOptions.Ordinal) != 0)
521 if (options != CompareOptions.Ordinal)
523 throw new ArgumentException(Environment.GetResourceString("Argument_CompareOptionOrdinal"),
527 else if ((options & ValidCompareMaskOffFlags) != 0)
529 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
533 // Check for the null case.
548 if (options == CompareOptions.Ordinal)
550 return CompareOrdinal(string1, offset1, length1,
551 string2, offset2, length2);
554 return internal_compare_switch (string1, offset1, length1, string2, offset2, length2, options);
556 return InternalCompareString(this.m_dataHandle, this.m_handleOrigin, this.m_sortName,
557 string1, offset1, length1,
558 string2, offset2, length2,
559 GetNativeCompareFlags(options));
563 [System.Security.SecurityCritical]
564 private static int CompareOrdinal(string string1, int offset1, int length1, string string2, int offset2, int length2)
566 int result = String.nativeCompareOrdinalEx(string1, offset1, string2, offset2,
567 (length1 < length2 ? length1 : length2));
568 if ((length1 != length2) && result == 0)
570 return (length1 > length2 ? 1 : -1);
575 ////////////////////////////////////////////////////////////////////////
579 // Determines whether prefix is a prefix of string. If prefix equals
580 // String.Empty, true is returned.
582 ////////////////////////////////////////////////////////////////////////
585 [System.Security.SecuritySafeCritical] // auto-generated
586 public unsafe virtual bool IsPrefix(String source, String prefix, CompareOptions options)
588 if (source == null || prefix == null) {
589 throw new ArgumentNullException((source == null ? "source" : "prefix"),
590 Environment.GetResourceString("ArgumentNull_String"));
592 Contract.EndContractBlock();
593 int prefixLen = prefix.Length;
600 if (options == CompareOptions.OrdinalIgnoreCase)
602 return source.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
605 if (options == CompareOptions.Ordinal)
607 return source.StartsWith(prefix, StringComparison.Ordinal);
610 if ((options & ValidIndexMaskOffFlags) != 0) {
611 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
615 if (UseManagedCollation)
616 return GetCollator ().IsPrefix (source, prefix, options);
618 if(source.Length < prefix.Length)
620 return Compare (source, 0, prefix.Length, prefix, 0, prefix.Length, options) == 0;
622 // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
623 // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
625 return (InternalFindNLSStringEx(
626 m_dataHandle, m_handleOrigin, m_sortName,
627 GetNativeCompareFlags(options) | Win32Native.FIND_STARTSWITH | ((source.IsAscii() && prefix.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0),
628 source, source.Length, 0, prefix, prefix.Length) > -1);
632 public virtual bool IsPrefix(String source, String prefix)
634 return (IsPrefix(source, prefix, 0));
638 ////////////////////////////////////////////////////////////////////////
642 // Determines whether suffix is a suffix of string. If suffix equals
643 // String.Empty, true is returned.
645 ////////////////////////////////////////////////////////////////////////
648 [System.Security.SecuritySafeCritical] // auto-generated
649 public unsafe virtual bool IsSuffix(String source, String suffix, CompareOptions options)
651 if (source == null || suffix == null) {
652 throw new ArgumentNullException((source == null ? "source" : "suffix"),
653 Environment.GetResourceString("ArgumentNull_String"));
655 Contract.EndContractBlock();
656 int suffixLen = suffix.Length;
663 if (options == CompareOptions.OrdinalIgnoreCase) {
664 return source.EndsWith(suffix, StringComparison.OrdinalIgnoreCase);
667 if (options == CompareOptions.Ordinal) {
668 return source.EndsWith(suffix, StringComparison.Ordinal);
671 if ((options & ValidIndexMaskOffFlags) != 0) {
672 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
676 if (UseManagedCollation)
677 return GetCollator ().IsSuffix (source, suffix, options);
679 if (source.Length < suffix.Length)
682 return Compare (source, source.Length - suffix.Length, suffix.Length, suffix, 0, suffix.Length, options) == 0;
685 // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
686 // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
687 return InternalFindNLSStringEx(
688 m_dataHandle, m_handleOrigin, m_sortName,
689 GetNativeCompareFlags(options) | Win32Native.FIND_ENDSWITH | ((source.IsAscii() && suffix.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0),
690 source, source.Length, source.Length - 1, suffix, suffix.Length) >= 0;
695 public virtual bool IsSuffix(String source, String suffix)
697 return (IsSuffix(source, suffix, 0));
700 ////////////////////////////////////////////////////////////////////////
704 // Returns the first index where value is found in string. The
705 // search starts from startIndex and ends at endIndex. Returns -1 if
706 // the specified value is not found. If value equals String.Empty,
707 // startIndex is returned. Throws IndexOutOfRange if startIndex or
708 // endIndex is less than zero or greater than the length of string.
709 // Throws ArgumentException if value is null.
711 ////////////////////////////////////////////////////////////////////////
714 public unsafe virtual int IndexOf(String source, char value)
717 throw new ArgumentNullException("source");
718 Contract.EndContractBlock();
720 return IndexOf(source, value, 0, source.Length, CompareOptions.None);
724 public unsafe virtual int IndexOf(String source, String value)
727 throw new ArgumentNullException("source");
728 Contract.EndContractBlock();
730 return IndexOf(source, value, 0, source.Length, CompareOptions.None);
734 public unsafe virtual int IndexOf(String source, char value, CompareOptions options)
737 throw new ArgumentNullException("source");
738 Contract.EndContractBlock();
740 return IndexOf(source, value, 0, source.Length, options);
744 public unsafe virtual int IndexOf(String source, String value, CompareOptions options)
747 throw new ArgumentNullException("source");
748 Contract.EndContractBlock();
750 return IndexOf(source, value, 0, source.Length, options);
754 public unsafe virtual int IndexOf(String source, char value, int startIndex)
757 throw new ArgumentNullException("source");
758 Contract.EndContractBlock();
760 return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
764 public unsafe virtual int IndexOf(String source, String value, int startIndex)
767 throw new ArgumentNullException("source");
768 Contract.EndContractBlock();
770 return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
774 public unsafe virtual int IndexOf(String source, char value, int startIndex, CompareOptions options)
777 throw new ArgumentNullException("source");
778 Contract.EndContractBlock();
780 return IndexOf(source, value, startIndex, source.Length - startIndex, options);
784 public unsafe virtual int IndexOf(String source, String value, int startIndex, CompareOptions options)
787 throw new ArgumentNullException("source");
788 Contract.EndContractBlock();
790 return IndexOf(source, value, startIndex, source.Length - startIndex, options);
794 public unsafe virtual int IndexOf(String source, char value, int startIndex, int count)
796 return IndexOf(source, value, startIndex, count, CompareOptions.None);
800 public unsafe virtual int IndexOf(String source, String value, int startIndex, int count)
802 return IndexOf(source, value, startIndex, count, CompareOptions.None);
805 [System.Security.SecuritySafeCritical] // auto-generated
806 [ResourceExposure(ResourceScope.None)]
807 [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
808 public unsafe virtual int IndexOf(String source, char value, int startIndex, int count, CompareOptions options)
812 throw new ArgumentNullException("source");
814 if (startIndex < 0 || startIndex > source.Length)
815 throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
817 if (count < 0 || startIndex > source.Length - count)
818 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count"));
819 Contract.EndContractBlock();
821 if (options == CompareOptions.OrdinalIgnoreCase)
824 return source.IndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
827 // Validate CompareOptions
828 // Ordinal can't be selected with other flags
829 if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
830 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
833 return internal_index_switch (source, startIndex, count, value, options, true);
835 // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
836 // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
837 return InternalFindNLSStringEx(
838 m_dataHandle, m_handleOrigin, m_sortName,
839 GetNativeCompareFlags(options) | Win32Native.FIND_FROMSTART | ((source.IsAscii() && (value <= '\x007f')) ? RESERVED_FIND_ASCII_STRING : 0),
840 source, count, startIndex, new String(value, 1), 1);
845 [System.Security.SecuritySafeCritical] // auto-generated
846 public unsafe virtual int IndexOf(String source, String value, int startIndex, int count, CompareOptions options)
850 throw new ArgumentNullException("source");
852 throw new ArgumentNullException("value");
854 if (startIndex > source.Length)
856 throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
858 Contract.EndContractBlock();
860 // In Everett we used to return -1 for empty string even if startIndex is negative number so we keeping same behavior here.
861 // We return 0 if both source and value are empty strings for Everett compatibility too.
862 if (source.Length == 0)
864 if (value.Length == 0)
873 throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
876 if (count < 0 || startIndex > source.Length - count)
877 throw new ArgumentOutOfRangeException("count",Environment.GetResourceString("ArgumentOutOfRange_Count"));
879 if (options == CompareOptions.OrdinalIgnoreCase)
881 return source.IndexOf(value, startIndex, count, StringComparison.OrdinalIgnoreCase);
884 // Validate CompareOptions
885 // Ordinal can't be selected with other flags
886 if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal))
887 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
890 return internal_index_switch (source, startIndex, count, value, options, true);
892 // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
893 // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
894 return InternalFindNLSStringEx(
895 m_dataHandle, m_handleOrigin, m_sortName,
896 GetNativeCompareFlags(options) | Win32Native.FIND_FROMSTART | ((source.IsAscii() && value.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0),
897 source, count, startIndex, value, value.Length);
901 ////////////////////////////////////////////////////////////////////////
905 // Returns the last index where value is found in string. The
906 // search starts from startIndex and ends at endIndex. Returns -1 if
907 // the specified value is not found. If value equals String.Empty,
908 // endIndex is returned. Throws IndexOutOfRange if startIndex or
909 // endIndex is less than zero or greater than the length of string.
910 // Throws ArgumentException if value is null.
912 ////////////////////////////////////////////////////////////////////////
915 public unsafe virtual int LastIndexOf(String source, char value)
918 throw new ArgumentNullException("source");
919 Contract.EndContractBlock();
921 // Can't start at negative index, so make sure we check for the length == 0 case.
922 return LastIndexOf(source, value, source.Length - 1,
923 source.Length, CompareOptions.None);
927 public virtual int LastIndexOf(String source, String value)
930 throw new ArgumentNullException("source");
931 Contract.EndContractBlock();
933 // Can't start at negative index, so make sure we check for the length == 0 case.
934 return LastIndexOf(source, value, source.Length - 1,
935 source.Length, CompareOptions.None);
939 public virtual int LastIndexOf(String source, char value, CompareOptions options)
942 throw new ArgumentNullException("source");
943 Contract.EndContractBlock();
945 // Can't start at negative index, so make sure we check for the length == 0 case.
946 return LastIndexOf(source, value, source.Length - 1,
947 source.Length, options);
950 public unsafe virtual int LastIndexOf(String source, String value, CompareOptions options)
953 throw new ArgumentNullException("source");
954 Contract.EndContractBlock();
956 // Can't start at negative index, so make sure we check for the length == 0 case.
957 return LastIndexOf(source, value, source.Length - 1,
958 source.Length, options);
962 public unsafe virtual int LastIndexOf(String source, char value, int startIndex)
964 return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
968 public unsafe virtual int LastIndexOf(String source, String value, int startIndex)
970 return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
974 public unsafe virtual int LastIndexOf(String source, char value, int startIndex, CompareOptions options)
976 return LastIndexOf(source, value, startIndex, startIndex + 1, options);
980 public unsafe virtual int LastIndexOf(String source, String value, int startIndex, CompareOptions options)
982 return LastIndexOf(source, value, startIndex, startIndex + 1, options);
986 public unsafe virtual int LastIndexOf(String source, char value, int startIndex, int count)
988 return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
992 public unsafe virtual int LastIndexOf(String source, String value, int startIndex, int count)
994 return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
998 [System.Security.SecuritySafeCritical] // auto-generated
999 [ResourceExposure(ResourceScope.None)]
1000 [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
1001 public unsafe virtual int LastIndexOf(String source, char value, int startIndex, int count, CompareOptions options)
1005 throw new ArgumentNullException("source");
1006 Contract.EndContractBlock();
1008 // Validate CompareOptions
1009 // Ordinal can't be selected with other flags
1010 if ((options & ValidIndexMaskOffFlags) != 0 &&
1011 (options != CompareOptions.Ordinal) &&
1012 (options != CompareOptions.OrdinalIgnoreCase))
1013 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
1015 // Special case for 0 length input strings
1016 if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
1019 // Make sure we're not out of range
1020 if (startIndex < 0 || startIndex > source.Length)
1021 throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
1023 // Make sure that we allow startIndex == source.Length
1024 if (startIndex == source.Length)
1031 // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
1032 if (count < 0 || startIndex - count + 1 < 0)
1033 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count"));
1035 if (options == CompareOptions.OrdinalIgnoreCase)
1038 return source.LastIndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
1042 return internal_index_switch (source, startIndex, count, value, options, false);
1044 // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
1045 // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
1046 return InternalFindNLSStringEx(
1047 m_dataHandle, m_handleOrigin, m_sortName,
1048 GetNativeCompareFlags(options) | Win32Native.FIND_FROMEND | ((source.IsAscii() && (value <= '\x007f')) ? RESERVED_FIND_ASCII_STRING : 0),
1049 source, count, startIndex, new String(value, 1), 1);
1054 [System.Security.SecuritySafeCritical] // auto-generated
1055 public unsafe virtual int LastIndexOf(String source, String value, int startIndex, int count, CompareOptions options)
1059 throw new ArgumentNullException("source");
1061 throw new ArgumentNullException("value");
1062 Contract.EndContractBlock();
1064 // Validate CompareOptions
1065 // Ordinal can't be selected with other flags
1066 if ((options & ValidIndexMaskOffFlags) != 0 &&
1067 (options != CompareOptions.Ordinal) &&
1068 (options != CompareOptions.OrdinalIgnoreCase))
1069 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
1071 // Special case for 0 length input strings
1072 if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
1073 return (value.Length == 0) ? 0 : -1;
1075 // Make sure we're not out of range
1076 if (startIndex < 0 || startIndex > source.Length)
1077 throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
1079 // Make sure that we allow startIndex == source.Length
1080 if (startIndex == source.Length)
1086 // If we are looking for nothing, just return 0
1087 if (value.Length == 0 && count >= 0 && startIndex - count + 1 >= 0)
1091 // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
1092 if (count < 0 || startIndex - count + 1 < 0)
1093 throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count"));
1095 if (options == CompareOptions.OrdinalIgnoreCase)
1097 return source.LastIndexOf(value, startIndex, count, StringComparison.OrdinalIgnoreCase);
1101 return internal_index_switch (source, startIndex, count, value, options, false);
1103 // to let the sorting DLL do the call optimization in case of Ascii strings, we check if the strings are in Ascii and then send the flag RESERVED_FIND_ASCII_STRING to
1104 // the sorting DLL API SortFindString so sorting DLL don't have to check if the string is Ascii with every call to SortFindString.
1105 return InternalFindNLSStringEx(
1106 m_dataHandle, m_handleOrigin, m_sortName,
1107 GetNativeCompareFlags(options) | Win32Native.FIND_FROMEND | ((source.IsAscii() && value.IsAscii()) ? RESERVED_FIND_ASCII_STRING : 0),
1108 source, count, startIndex, value, value.Length);
1113 ////////////////////////////////////////////////////////////////////////
1117 // Gets the SortKey for the given string with the given options.
1119 ////////////////////////////////////////////////////////////////////////
1120 #if !FEATURE_PAL || MONO
1121 public unsafe virtual SortKey GetSortKey(String source, CompareOptions options)
1123 return CreateSortKey(source, options);
1127 public unsafe virtual SortKey GetSortKey(String source)
1129 return CreateSortKey(source, CompareOptions.None);
1132 [System.Security.SecuritySafeCritical]
1133 private SortKey CreateSortKey(String source, CompareOptions options)
1135 if (source==null) { throw new ArgumentNullException("source"); }
1136 Contract.EndContractBlock();
1138 // Mask used to check if we have the right flags.
1139 const CompareOptions ValidSortkeyCtorMaskOffFlags = ~(CompareOptions.IgnoreCase |
1140 CompareOptions.IgnoreSymbols |
1141 CompareOptions.IgnoreNonSpace |
1142 CompareOptions.IgnoreWidth |
1143 CompareOptions.IgnoreKanaType |
1144 CompareOptions.StringSort);
1146 if ((options & ValidSortkeyCtorMaskOffFlags) != 0)
1148 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
1152 byte[] keyData = null;
1154 // The OS doesn't have quite the same behavior so we have to test for empty inputs
1155 if (String.IsNullOrEmpty(source))
1158 // Empty strings get an empty sort key
1159 keyData = EmptyArray<Byte>.Value;
1161 // Fake value to test though so we can verify our flags
1165 return CreateSortKeyCore (source, options);
1167 int flags = GetNativeCompareFlags(options);
1169 // Go ahead and call the OS
1170 // First get the count
1171 int length = InternalGetSortKey(m_dataHandle, m_handleOrigin, m_sortName, flags, source, source.Length, null, 0);
1173 // If there was an error, return an error
1176 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "source");
1179 // If input was empty, return the empty byte[] we made earlier and skip this
1180 if (keyData == null)
1182 // Make an appropriate byte array
1183 keyData = new byte[length];
1185 // Fill up the array
1186 length = InternalGetSortKey(m_dataHandle, m_handleOrigin, m_sortName, flags, source, source.Length, keyData, keyData.Length);
1190 source = String.Empty; // back to original
1193 return new SortKey(Name, source, options, keyData);
1197 #endif // !FEATURE_PAL
1200 ////////////////////////////////////////////////////////////////////////
1204 // Implements Object.Equals(). Returns a boolean indicating whether
1205 // or not object refers to the same CompareInfo as the current
1208 ////////////////////////////////////////////////////////////////////////
1211 public override bool Equals(Object value)
1213 CompareInfo that = value as CompareInfo;
1217 return this.Name == that.Name;
1224 ////////////////////////////////////////////////////////////////////////
1228 // Implements Object.GetHashCode(). Returns the hash code for the
1229 // CompareInfo. The hash code is guaranteed to be the same for
1230 // CompareInfo A and B where A.Equals(B) is true.
1232 ////////////////////////////////////////////////////////////////////////
1235 public override int GetHashCode()
1237 return (this.Name.GetHashCode());
1241 // return hash value for the string according to the input CompareOptions
1244 public virtual int GetHashCode(string source, CompareOptions options)
1248 throw new ArgumentNullException("source");
1251 if (options == CompareOptions.Ordinal)
1253 return source.GetHashCode();
1256 if (options == CompareOptions.OrdinalIgnoreCase)
1258 return TextInfo.GetHashCodeOrdinalIgnoreCase(source);
1262 // GetHashCodeOfString does more parameters validation. basically will throw when
1263 // having Ordinal, OrdinalIgnoreCase and StringSort
1266 return GetHashCodeOfString(source, options, false, 0);
1269 ////////////////////////////////////////////////////////////////////////
1271 // GetHashCodeOfString
1273 // This internal method allows a method that allows the equivalent of creating a Sortkey for a
1274 // string from CompareInfo, and generate a hashcode value from it. It is not very convenient
1275 // to use this method as is and it creates an unnecessary Sortkey object that will be GC'ed.
1277 // The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both
1278 // the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects
1279 // treat the string the same way, this implementation will treat them differently (the same way that
1280 // Sortkey does at the moment).
1282 // This method will never be made public itself, but public consumers of it could be created, e.g.:
1284 // string.GetHashCode(CultureInfo)
1285 // string.GetHashCode(CompareInfo)
1286 // string.GetHashCode(CultureInfo, CompareOptions)
1287 // string.GetHashCode(CompareInfo, CompareOptions)
1290 // (the methods above that take a CultureInfo would use CultureInfo.CompareInfo)
1292 ////////////////////////////////////////////////////////////////////////
1293 internal int GetHashCodeOfString(string source, CompareOptions options)
1295 return GetHashCodeOfString(source, options, false, 0);
1298 [System.Security.SecuritySafeCritical] // auto-generated
1299 internal int GetHashCodeOfString(string source, CompareOptions options, bool forceRandomizedHashing, long additionalEntropy)
1302 // Parameter validation
1306 throw new ArgumentNullException("source");
1309 if ((options & ValidHashCodeOfStringMaskOffFlags) != 0)
1311 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
1313 Contract.EndContractBlock();
1315 if(0 == source.Length)
1321 return GetSortKey (source, options).GetHashCode ();
1324 ////////////////////////////////////////////////////////////////////////
1325 return (InternalGetGlobalizedHashCode(m_dataHandle, m_handleOrigin, this.m_sortName, source, source.Length, GetNativeCompareFlags(options), forceRandomizedHashing, additionalEntropy));
1329 ////////////////////////////////////////////////////////////////////////
1333 // Implements Object.ToString(). Returns a string describing the
1336 ////////////////////////////////////////////////////////////////////////
1339 public override String ToString()
1341 return ("CompareInfo - " + this.Name);
1344 #if FEATURE_USE_LCID
1349 return CultureInfo.GetCultureInfo(this.Name).LCID;
1355 [System.Security.SecuritySafeCritical]
1356 internal static IntPtr InternalInitSortHandle(String localeName, out IntPtr handleOrigin)
1358 return NativeInternalInitSortHandle(localeName, out handleOrigin);
1361 #if !FEATURE_CORECLR
1362 private const int SORT_VERSION_WHIDBEY = 0x00001000;
1363 private const int SORT_VERSION_V4 = 0x00060101;
1365 internal static bool IsLegacy20SortingBehaviorRequested
1369 return InternalSortVersion == SORT_VERSION_WHIDBEY;
1373 private static uint InternalSortVersion
1375 [System.Security.SecuritySafeCritical]
1378 return InternalGetSortVersion();
1382 [OptionalField(VersionAdded = 3)]
1383 private SortVersion m_SortVersion;
1385 public SortVersion Version
1387 [SecuritySafeCritical]
1390 if(m_SortVersion == null)
1392 Win32Native.NlsVersionInfoEx v = new Win32Native.NlsVersionInfoEx();
1393 v.dwNLSVersionInfoSize = Marshal.SizeOf(typeof(Win32Native.NlsVersionInfoEx));
1394 InternalGetNlsVersionEx(m_dataHandle, m_handleOrigin, m_sortName, ref v);
1395 m_SortVersion = new SortVersion(v.dwNLSVersion, (v.dwEffectiveId != 0) ? v.dwEffectiveId : LCID, v.guidCustomVersion);
1398 return m_SortVersion;
1402 [System.Security.SecurityCritical]
1403 [ResourceExposure(ResourceScope.None)]
1404 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
1405 [SuppressUnmanagedCodeSecurity]
1406 [return: MarshalAs(UnmanagedType.Bool)]
1407 private static extern bool InternalGetNlsVersionEx(IntPtr handle, IntPtr handleOrigin, String localeName, ref Win32Native.NlsVersionInfoEx lpNlsVersionInformation);
1409 [System.Security.SecurityCritical]
1410 [ResourceExposure(ResourceScope.None)]
1411 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
1412 [SuppressUnmanagedCodeSecurity]
1413 private static extern uint InternalGetSortVersion();
1416 [System.Security.SecurityCritical]
1417 [ResourceExposure(ResourceScope.None)]
1418 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
1419 [SuppressUnmanagedCodeSecurity]
1420 private static extern IntPtr NativeInternalInitSortHandle(String localeName, out IntPtr handleOrigin);
1422 // Get a locale sensitive sort hash code from native code -- COMNlsInfo::InternalGetGlobalizedHashCode
1423 [System.Security.SecurityCritical] // auto-generated
1424 [ResourceExposure(ResourceScope.None)]
1425 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
1426 [SuppressUnmanagedCodeSecurity]
1427 private static extern int InternalGetGlobalizedHashCode(IntPtr handle, IntPtr handleOrigin, string localeName, string source, int length, int dwFlags, bool forceRandomizedHashing, long additionalEntropy);
1429 // Use native API calls to see if this string is entirely defined -- COMNlsInfo::InternalIsSortable
1430 [System.Security.SecurityCritical] // auto-generated
1431 [ResourceExposure(ResourceScope.None)]
1432 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
1433 [SuppressUnmanagedCodeSecurity]
1434 [return: MarshalAs(UnmanagedType.Bool)]
1435 private static extern bool InternalIsSortable(IntPtr handle, IntPtr handleOrigin, String localeName, String source, int length);
1437 // Compare a string using the native API calls -- COMNlsInfo::InternalCompareString
1438 [System.Security.SecurityCritical] // auto-generated
1439 [ResourceExposure(ResourceScope.Process)]
1440 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
1441 [SuppressUnmanagedCodeSecurity]
1442 private static extern int InternalCompareString(IntPtr handle, IntPtr handleOrigin, String localeName, String string1, int offset1, int length1,
1443 String string2, int offset2, int length2, int flags);
1445 // InternalFindNLSStringEx parameters is not exactly matching kernel32::FindNLSStringEx parameters.
1446 // Call through to NewApis::FindNLSStringEx so we can get the right behavior
1447 [System.Security.SecurityCritical] // auto-generated
1448 [ResourceExposure(ResourceScope.None)]
1449 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
1450 [SuppressUnmanagedCodeSecurity]
1451 private static extern int InternalFindNLSStringEx(IntPtr handle, IntPtr handleOrigin, String localeName, int flags, String source, int sourceCount, int startIndex, string target, int targetCount);
1453 // Call through to NewAPis::LCMapStringEx so we can get appropriate behavior for all platforms
1454 [System.Security.SecurityCritical] // auto-generated
1455 [ResourceExposure(ResourceScope.None)]
1456 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
1457 [SuppressUnmanagedCodeSecurity]
1458 private static extern int InternalGetSortKey(IntPtr handle, IntPtr handleOrigin, String localeName, int flags, String source, int sourceCount, byte[] target, int targetCount);