| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- ////////////////////////////////////////////////////////////////////////////
- //
- //
- //
- // Purpose: This class implements a set of methods for comparing
- // strings.
- //
- //
- ////////////////////////////////////////////////////////////////////////////
- using System.Reflection;
- using System.Diagnostics;
- using System.Runtime.InteropServices;
- using System.Runtime.Serialization;
- using System.Text;
- using Internal.Runtime.CompilerServices;
- namespace System.Globalization
- {
- [Flags]
- public enum CompareOptions
- {
- None = 0x00000000,
- IgnoreCase = 0x00000001,
- IgnoreNonSpace = 0x00000002,
- IgnoreSymbols = 0x00000004,
- IgnoreKanaType = 0x00000008, // ignore kanatype
- IgnoreWidth = 0x00000010, // ignore width
- OrdinalIgnoreCase = 0x10000000, // This flag can not be used with other flags.
- StringSort = 0x20000000, // use string sort method
- Ordinal = 0x40000000, // This flag can not be used with other flags.
- }
- [Serializable]
- [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
- public partial class CompareInfo : IDeserializationCallback
- {
- // Mask used to check if IndexOf()/LastIndexOf()/IsPrefix()/IsPostfix() has the right flags.
- private const CompareOptions ValidIndexMaskOffFlags =
- ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
- CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
- // Mask used to check if Compare() has the right flags.
- private const CompareOptions ValidCompareMaskOffFlags =
- ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
- CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
- // Mask used to check if GetHashCodeOfString() has the right flags.
- private const CompareOptions ValidHashCodeOfStringMaskOffFlags =
- ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
- CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
- // Mask used to check if we have the right flags.
- private const CompareOptions ValidSortkeyCtorMaskOffFlags =
- ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
- CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
- // Cache the invariant CompareInfo
- internal static readonly CompareInfo Invariant = CultureInfo.InvariantCulture.CompareInfo;
- //
- // CompareInfos have an interesting identity. They are attached to the locale that created them,
- // ie: en-US would have an en-US sort. For haw-US (custom), then we serialize it as haw-US.
- // The interesting part is that since haw-US doesn't have its own sort, it has to point at another
- // locale, which is what SCOMPAREINFO does.
- [OptionalField(VersionAdded = 2)]
- private string m_name; // The name used to construct this CompareInfo. Do not rename (binary serialization)
- [NonSerialized]
- private string _sortName; // The name that defines our behavior
- [OptionalField(VersionAdded = 3)]
- private SortVersion m_SortVersion; // Do not rename (binary serialization)
- private int culture; // Do not rename (binary serialization). The fields sole purpose is to support Desktop serialization.
- internal CompareInfo(CultureInfo culture)
- {
- m_name = culture._name;
- InitSort(culture);
- }
- /*=================================GetCompareInfo==========================
- **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
- ** Warning: The assembly versioning mechanism is dead!
- **Returns: The CompareInfo for the specified culture.
- **Arguments:
- ** culture the ID of the culture
- ** assembly the assembly which contains the sorting table.
- **Exceptions:
- ** ArgumentNullException when the assembly is null
- ** ArgumentException if culture is invalid.
- ============================================================================*/
- // Assembly constructor should be deprecated, we don't act on the assembly information any more
- public static CompareInfo GetCompareInfo(int culture, Assembly assembly)
- {
- // Parameter checking.
- if (assembly == null)
- {
- throw new ArgumentNullException(nameof(assembly));
- }
- if (assembly != typeof(object).Module.Assembly)
- {
- throw new ArgumentException(SR.Argument_OnlyMscorlib);
- }
- return GetCompareInfo(culture);
- }
- /*=================================GetCompareInfo==========================
- **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
- ** The purpose of this method is to provide version for CompareInfo tables.
- **Returns: The CompareInfo for the specified culture.
- **Arguments:
- ** name the name of the culture
- ** assembly the assembly which contains the sorting table.
- **Exceptions:
- ** ArgumentNullException when the assembly is null
- ** ArgumentException if name is invalid.
- ============================================================================*/
- // Assembly constructor should be deprecated, we don't act on the assembly information any more
- public static CompareInfo GetCompareInfo(string name, Assembly assembly)
- {
- if (name == null || assembly == null)
- {
- throw new ArgumentNullException(name == null ? nameof(name) : nameof(assembly));
- }
- if (assembly != typeof(object).Module.Assembly)
- {
- throw new ArgumentException(SR.Argument_OnlyMscorlib);
- }
- return GetCompareInfo(name);
- }
- /*=================================GetCompareInfo==========================
- **Action: Get the CompareInfo for the specified culture.
- ** This method is provided for ease of integration with NLS-based software.
- **Returns: The CompareInfo for the specified culture.
- **Arguments:
- ** culture the ID of the culture.
- **Exceptions:
- ** ArgumentException if culture is invalid.
- ============================================================================*/
- // People really shouldn't be calling LCID versions, no custom support
- public static CompareInfo GetCompareInfo(int culture)
- {
- if (CultureData.IsCustomCultureId(culture))
- {
- // Customized culture cannot be created by the LCID.
- throw new ArgumentException(SR.Argument_CustomCultureCannotBePassedByNumber, nameof(culture));
- }
- return CultureInfo.GetCultureInfo(culture).CompareInfo;
- }
- /*=================================GetCompareInfo==========================
- **Action: Get the CompareInfo for the specified culture.
- **Returns: The CompareInfo for the specified culture.
- **Arguments:
- ** name the name of the culture.
- **Exceptions:
- ** ArgumentException if name is invalid.
- ============================================================================*/
- public static CompareInfo GetCompareInfo(string name)
- {
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
- return CultureInfo.GetCultureInfo(name).CompareInfo;
- }
- public static unsafe bool IsSortable(char ch)
- {
- if (GlobalizationMode.Invariant)
- {
- return true;
- }
- char *pChar = &ch;
- return IsSortable(pChar, 1);
- }
- public static unsafe bool IsSortable(string text)
- {
- if (text == null)
- {
- // A null param is invalid here.
- throw new ArgumentNullException(nameof(text));
- }
- if (text.Length == 0)
- {
- // A zero length string is not invalid, but it is also not sortable.
- return (false);
- }
- if (GlobalizationMode.Invariant)
- {
- return true;
- }
- fixed (char *pChar = text)
- {
- return IsSortable(pChar, text.Length);
- }
- }
- [OnDeserializing]
- private void OnDeserializing(StreamingContext ctx)
- {
- m_name = null;
- }
- void IDeserializationCallback.OnDeserialization(object sender)
- {
- OnDeserialized();
- }
- [OnDeserialized]
- private void OnDeserialized(StreamingContext ctx)
- {
- OnDeserialized();
- }
- private void OnDeserialized()
- {
- // If we didn't have a name, use the LCID
- if (m_name == null)
- {
- // From whidbey, didn't have a name
- CultureInfo ci = CultureInfo.GetCultureInfo(this.culture);
- m_name = ci._name;
- }
- else
- {
- InitSort(CultureInfo.GetCultureInfo(m_name));
- }
- }
- [OnSerializing]
- private void OnSerializing(StreamingContext ctx)
- {
- // This is merely for serialization compatibility with Whidbey/Orcas, it can go away when we don't want that compat any more.
- culture = CultureInfo.GetCultureInfo(this.Name).LCID; // This is the lcid of the constructing culture (still have to dereference to get target sort)
- Debug.Assert(m_name != null, "CompareInfo.OnSerializing - expected m_name to be set already");
- }
- ///////////////////////////----- Name -----/////////////////////////////////
- //
- // Returns the name of the culture (well actually, of the sort).
- // Very important for providing a non-LCID way of identifying
- // what the sort is.
- //
- // Note that this name isn't dereferenced in case the CompareInfo is a different locale
- // which is consistent with the behaviors of earlier versions. (so if you ask for a sort
- // and the locale's changed behavior, then you'll get changed behavior, which is like
- // what happens for a version update)
- //
- ////////////////////////////////////////////////////////////////////////
- public virtual string Name
- {
- get
- {
- Debug.Assert(m_name != null, "CompareInfo.Name Expected _name to be set");
- if (m_name == "zh-CHT" || m_name == "zh-CHS")
- {
- return m_name;
- }
- return _sortName;
- }
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // Compare
- //
- // Compares the two strings with the given options. Returns 0 if the
- // two strings are equal, a number less than 0 if string1 is less
- // than string2, and a number greater than 0 if string1 is greater
- // than string2.
- //
- ////////////////////////////////////////////////////////////////////////
- public virtual int Compare(string string1, string string2)
- {
- return Compare(string1, string2, CompareOptions.None);
- }
- public virtual int Compare(string string1, string string2, CompareOptions options)
- {
- if (options == CompareOptions.OrdinalIgnoreCase)
- {
- return string.Compare(string1, string2, StringComparison.OrdinalIgnoreCase);
- }
- // Verify the options before we do any real comparison.
- if ((options & CompareOptions.Ordinal) != 0)
- {
- if (options != CompareOptions.Ordinal)
- {
- throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
- }
- return string.CompareOrdinal(string1, string2);
- }
- if ((options & ValidCompareMaskOffFlags) != 0)
- {
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- }
- //Our paradigm is that null sorts less than any other string and
- //that two nulls sort as equal.
- if (string1 == null)
- {
- if (string2 == null)
- {
- return (0); // Equal
- }
- return (-1); // null < non-null
- }
- if (string2 == null)
- {
- return (1); // non-null > null
- }
- if (GlobalizationMode.Invariant)
- {
- if ((options & CompareOptions.IgnoreCase) != 0)
- return CompareOrdinalIgnoreCase(string1, string2);
- return string.CompareOrdinal(string1, string2);
- }
- return CompareString(string1.AsSpan(), string2.AsSpan(), options);
- }
- // TODO https://github.com/dotnet/coreclr/issues/13827:
- // This method shouldn't be necessary, as we should be able to just use the overload
- // that takes two spans. But due to this issue, that's adding significant overhead.
- internal int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions options)
- {
- if (options == CompareOptions.OrdinalIgnoreCase)
- {
- return CompareOrdinalIgnoreCase(string1, string2.AsSpan());
- }
- // Verify the options before we do any real comparison.
- if ((options & CompareOptions.Ordinal) != 0)
- {
- if (options != CompareOptions.Ordinal)
- {
- throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
- }
- return string.CompareOrdinal(string1, string2.AsSpan());
- }
- if ((options & ValidCompareMaskOffFlags) != 0)
- {
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- }
- // null sorts less than any other string.
- if (string2 == null)
- {
- return 1;
- }
- if (GlobalizationMode.Invariant)
- {
- return (options & CompareOptions.IgnoreCase) != 0 ?
- CompareOrdinalIgnoreCase(string1, string2.AsSpan()) :
- string.CompareOrdinal(string1, string2.AsSpan());
- }
- return CompareString(string1, string2, options);
- }
- internal int CompareOptionNone(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
- {
- // Check for empty span or span from a null string
- if (string1.Length == 0 || string2.Length == 0)
- return string1.Length - string2.Length;
- return GlobalizationMode.Invariant ?
- string.CompareOrdinal(string1, string2) :
- CompareString(string1, string2, CompareOptions.None);
- }
- internal int CompareOptionIgnoreCase(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
- {
- // Check for empty span or span from a null string
- if (string1.Length == 0 || string2.Length == 0)
- return string1.Length - string2.Length;
- return GlobalizationMode.Invariant ?
- CompareOrdinalIgnoreCase(string1, string2) :
- CompareString(string1, string2, CompareOptions.IgnoreCase);
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // Compare
- //
- // Compares the specified regions of the two strings with the given
- // options.
- // Returns 0 if the two strings are equal, a number less than 0 if
- // string1 is less than string2, and a number greater than 0 if
- // string1 is greater than string2.
- //
- ////////////////////////////////////////////////////////////////////////
- public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2)
- {
- return Compare(string1, offset1, length1, string2, offset2, length2, 0);
- }
- public virtual int Compare(string string1, int offset1, string string2, int offset2, CompareOptions options)
- {
- return Compare(string1, offset1, string1 == null ? 0 : string1.Length - offset1,
- string2, offset2, string2 == null ? 0 : string2.Length - offset2, options);
- }
- public virtual int Compare(string string1, int offset1, string string2, int offset2)
- {
- return Compare(string1, offset1, string2, offset2, 0);
- }
- public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options)
- {
- if (options == CompareOptions.OrdinalIgnoreCase)
- {
- int result = string.Compare(string1, offset1, string2, offset2, length1 < length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase);
- if ((length1 != length2) && result == 0)
- return (length1 > length2 ? 1 : -1);
- return (result);
- }
- // Verify inputs
- if (length1 < 0 || length2 < 0)
- {
- throw new ArgumentOutOfRangeException((length1 < 0) ? nameof(length1) : nameof(length2), SR.ArgumentOutOfRange_NeedPosNum);
- }
- if (offset1 < 0 || offset2 < 0)
- {
- throw new ArgumentOutOfRangeException((offset1 < 0) ? nameof(offset1) : nameof(offset2), SR.ArgumentOutOfRange_NeedPosNum);
- }
- if (offset1 > (string1 == null ? 0 : string1.Length) - length1)
- {
- throw new ArgumentOutOfRangeException(nameof(string1), SR.ArgumentOutOfRange_OffsetLength);
- }
- if (offset2 > (string2 == null ? 0 : string2.Length) - length2)
- {
- throw new ArgumentOutOfRangeException(nameof(string2), SR.ArgumentOutOfRange_OffsetLength);
- }
- if ((options & CompareOptions.Ordinal) != 0)
- {
- if (options != CompareOptions.Ordinal)
- {
- throw new ArgumentException(SR.Argument_CompareOptionOrdinal,
- nameof(options));
- }
- }
- else if ((options & ValidCompareMaskOffFlags) != 0)
- {
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- }
- //
- // Check for the null case.
- //
- if (string1 == null)
- {
- if (string2 == null)
- {
- return (0);
- }
- return (-1);
- }
- if (string2 == null)
- {
- return (1);
- }
- ReadOnlySpan<char> span1 = string1.AsSpan(offset1, length1);
- ReadOnlySpan<char> span2 = string2.AsSpan(offset2, length2);
- if (options == CompareOptions.Ordinal)
- {
- return string.CompareOrdinal(span1, span2);
- }
- if (GlobalizationMode.Invariant)
- {
- if ((options & CompareOptions.IgnoreCase) != 0)
- return CompareOrdinalIgnoreCase(span1, span2);
- return string.CompareOrdinal(span1, span2);
- }
- return CompareString(span1, span2, options);
- }
- //
- // CompareOrdinalIgnoreCase compare two string ordinally with ignoring the case.
- // it assumes the strings are Ascii string till we hit non Ascii character in strA or strB and then we continue the comparison by
- // calling the OS.
- //
- internal static int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB)
- {
- Debug.Assert(indexA + lengthA <= strA.Length);
- Debug.Assert(indexB + lengthB <= strB.Length);
- return CompareOrdinalIgnoreCase(
- ref Unsafe.Add(ref strA.GetRawStringData(), indexA),
- lengthA,
- ref Unsafe.Add(ref strB.GetRawStringData(), indexB),
- lengthB);
- }
- internal static int CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
- {
- return CompareOrdinalIgnoreCase(ref MemoryMarshal.GetReference(strA), strA.Length, ref MemoryMarshal.GetReference(strB), strB.Length);
- }
- internal static int CompareOrdinalIgnoreCase(string strA, string strB)
- {
- return CompareOrdinalIgnoreCase(ref strA.GetRawStringData(), strA.Length, ref strB.GetRawStringData(), strB.Length);
- }
- internal static int CompareOrdinalIgnoreCase(ref char strA, int lengthA, ref char strB, int lengthB)
- {
- int length = Math.Min(lengthA, lengthB);
- int range = length;
- ref char charA = ref strA;
- ref char charB = ref strB;
- // in InvariantMode we support all range and not only the ascii characters.
- char maxChar = (GlobalizationMode.Invariant ? (char)0xFFFF : (char)0x7F);
- while (length != 0 && charA <= maxChar && charB <= maxChar)
- {
- // Ordinal equals or lowercase equals if the result ends up in the a-z range
- if (charA == charB ||
- ((charA | 0x20) == (charB | 0x20) &&
- (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a')))
- {
- length--;
- charA = ref Unsafe.Add(ref charA, 1);
- charB = ref Unsafe.Add(ref charB, 1);
- }
- else
- {
- int currentA = charA;
- int currentB = charB;
- // Uppercase both chars if needed
- if ((uint)(charA - 'a') <= 'z' - 'a')
- currentA -= 0x20;
- if ((uint)(charB - 'a') <= 'z' - 'a')
- currentB -= 0x20;
- // Return the (case-insensitive) difference between them.
- return currentA - currentB;
- }
- }
- if (length == 0)
- return lengthA - lengthB;
- Debug.Assert(!GlobalizationMode.Invariant);
- range -= length;
- return CompareStringOrdinalIgnoreCase(ref charA, lengthA - range, ref charB, lengthB - range);
- }
- internal static bool EqualsOrdinalIgnoreCase(ref char charA, ref char charB, int length)
- {
- IntPtr byteOffset = IntPtr.Zero;
- #if BIT64
- // Read 4 chars (64 bits) at a time from each string
- while ((uint)length >= 4)
- {
- ulong valueA = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<char, byte>(ref Unsafe.AddByteOffset(ref charA, byteOffset)));
- ulong valueB = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<char, byte>(ref Unsafe.AddByteOffset(ref charB, byteOffset)));
- // A 32-bit test - even with the bit-twiddling here - is more efficient than a 64-bit test.
- ulong temp = valueA | valueB;
- if (!Utf16Utility.AllCharsInUInt32AreAscii((uint)temp | (uint)(temp >> 32)))
- {
- goto NonAscii; // one of the inputs contains non-ASCII data
- }
- // Generally, the caller has likely performed a first-pass check that the input strings
- // are likely equal. Consider a dictionary which computes the hash code of its key before
- // performing a proper deep equality check of the string contents. We want to optimize for
- // the case where the equality check is likely to succeed, which means that we want to avoid
- // branching within this loop unless we're about to exit the loop, either due to failure or
- // due to us running out of input data.
- if (!Utf16Utility.UInt64OrdinalIgnoreCaseAscii(valueA, valueB))
- {
- return false;
- }
- byteOffset += 8;
- length -= 4;
- }
- #endif
- // Read 2 chars (32 bits) at a time from each string
- #if BIT64
- if ((uint)length >= 2)
- #else
- while ((uint)length >= 2)
- #endif
- {
- uint valueA = Unsafe.ReadUnaligned<uint>(ref Unsafe.As<char, byte>(ref Unsafe.AddByteOffset(ref charA, byteOffset)));
- uint valueB = Unsafe.ReadUnaligned<uint>(ref Unsafe.As<char, byte>(ref Unsafe.AddByteOffset(ref charB, byteOffset)));
- if (!Utf16Utility.AllCharsInUInt32AreAscii(valueA | valueB))
- {
- goto NonAscii; // one of the inputs contains non-ASCII data
- }
- // Generally, the caller has likely performed a first-pass check that the input strings
- // are likely equal. Consider a dictionary which computes the hash code of its key before
- // performing a proper deep equality check of the string contents. We want to optimize for
- // the case where the equality check is likely to succeed, which means that we want to avoid
- // branching within this loop unless we're about to exit the loop, either due to failure or
- // due to us running out of input data.
- if (!Utf16Utility.UInt32OrdinalIgnoreCaseAscii(valueA, valueB))
- {
- return false;
- }
- byteOffset += 4;
- length -= 2;
- }
- if (length != 0)
- {
- Debug.Assert(length == 1);
- uint valueA = Unsafe.AddByteOffset(ref charA, byteOffset);
- uint valueB = Unsafe.AddByteOffset(ref charB, byteOffset);
- if ((valueA | valueB) > 0x7Fu)
- {
- goto NonAscii; // one of the inputs contains non-ASCII data
- }
- if (valueA == valueB)
- {
- return true; // exact match
- }
- valueA |= 0x20u;
- if ((uint)(valueA - 'a') > (uint)('z' - 'a'))
- {
- return false; // not exact match, and first input isn't in [A-Za-z]
- }
- // The ternary operator below seems redundant but helps RyuJIT generate more optimal code.
- // See https://github.com/dotnet/coreclr/issues/914.
- return (valueA == (valueB | 0x20u)) ? true : false;
- }
- Debug.Assert(length == 0);
- return true;
- NonAscii:
- // The non-ASCII case is factored out into its own helper method so that the JIT
- // doesn't need to emit a complex prolog for its caller (this method).
- return EqualsOrdinalIgnoreCaseNonAscii(ref Unsafe.AddByteOffset(ref charA, byteOffset), ref Unsafe.AddByteOffset(ref charB, byteOffset), length);
- }
- private static bool EqualsOrdinalIgnoreCaseNonAscii(ref char charA, ref char charB, int length)
- {
- if (!GlobalizationMode.Invariant)
- {
- return CompareStringOrdinalIgnoreCase(ref charA, length, ref charB, length) == 0;
- }
- else
- {
- // If we don't have localization tables to consult, we'll still perform a case-insensitive
- // check for ASCII characters, but if we see anything outside the ASCII range we'll immediately
- // fail if it doesn't have true bitwise equality.
- IntPtr byteOffset = IntPtr.Zero;
- while (length != 0)
- {
- // Ordinal equals or lowercase equals if the result ends up in the a-z range
- uint valueA = Unsafe.AddByteOffset(ref charA, byteOffset);
- uint valueB = Unsafe.AddByteOffset(ref charB, byteOffset);
- if (valueA == valueB ||
- ((valueA | 0x20) == (valueB | 0x20) &&
- (uint)((valueA | 0x20) - 'a') <= (uint)('z' - 'a')))
- {
- byteOffset += 2;
- length--;
- }
- else
- {
- return false;
- }
- }
- return true;
- }
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // IsPrefix
- //
- // Determines whether prefix is a prefix of string. If prefix equals
- // string.Empty, true is returned.
- //
- ////////////////////////////////////////////////////////////////////////
- public virtual bool IsPrefix(string source, string prefix, CompareOptions options)
- {
- if (source == null || prefix == null)
- {
- throw new ArgumentNullException((source == null ? nameof(source) : nameof(prefix)),
- SR.ArgumentNull_String);
- }
- if (prefix.Length == 0)
- {
- return (true);
- }
- if (source.Length == 0)
- {
- return false;
- }
- if (options == CompareOptions.OrdinalIgnoreCase)
- {
- return source.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
- }
- if (options == CompareOptions.Ordinal)
- {
- return source.StartsWith(prefix, StringComparison.Ordinal);
- }
- if ((options & ValidIndexMaskOffFlags) != 0)
- {
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- }
- if (GlobalizationMode.Invariant)
- {
- return source.StartsWith(prefix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
- }
- return StartsWith(source, prefix, options);
- }
- internal bool IsPrefix(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
- {
- Debug.Assert(prefix.Length != 0);
- Debug.Assert(source.Length != 0);
- Debug.Assert((options & ValidIndexMaskOffFlags) == 0);
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
- return StartsWith(source, prefix, options);
- }
- public virtual bool IsPrefix(string source, string prefix)
- {
- return (IsPrefix(source, prefix, 0));
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // IsSuffix
- //
- // Determines whether suffix is a suffix of string. If suffix equals
- // string.Empty, true is returned.
- //
- ////////////////////////////////////////////////////////////////////////
- public virtual bool IsSuffix(string source, string suffix, CompareOptions options)
- {
- if (source == null || suffix == null)
- {
- throw new ArgumentNullException((source == null ? nameof(source) : nameof(suffix)),
- SR.ArgumentNull_String);
- }
- if (suffix.Length == 0)
- {
- return (true);
- }
- if (source.Length == 0)
- {
- return false;
- }
- if (options == CompareOptions.OrdinalIgnoreCase)
- {
- return source.EndsWith(suffix, StringComparison.OrdinalIgnoreCase);
- }
- if (options == CompareOptions.Ordinal)
- {
- return source.EndsWith(suffix, StringComparison.Ordinal);
- }
- if ((options & ValidIndexMaskOffFlags) != 0)
- {
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- }
- if (GlobalizationMode.Invariant)
- {
- return source.EndsWith(suffix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
- }
- return EndsWith(source, suffix, options);
- }
- internal bool IsSuffix(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
- {
- Debug.Assert(suffix.Length != 0);
- Debug.Assert(source.Length != 0);
- Debug.Assert((options & ValidIndexMaskOffFlags) == 0);
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
- return EndsWith(source, suffix, options);
- }
- public virtual bool IsSuffix(string source, string suffix)
- {
- return (IsSuffix(source, suffix, 0));
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // IndexOf
- //
- // Returns the first index where value is found in string. The
- // search starts from startIndex and ends at endIndex. Returns -1 if
- // the specified value is not found. If value equals string.Empty,
- // startIndex is returned. Throws IndexOutOfRange if startIndex or
- // endIndex is less than zero or greater than the length of string.
- // Throws ArgumentException if value is null.
- //
- ////////////////////////////////////////////////////////////////////////
- public virtual int IndexOf(string source, char value)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- return IndexOf(source, value, 0, source.Length, CompareOptions.None);
- }
- public virtual int IndexOf(string source, string value)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- return IndexOf(source, value, 0, source.Length, CompareOptions.None);
- }
- public virtual int IndexOf(string source, char value, CompareOptions options)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- return IndexOf(source, value, 0, source.Length, options);
- }
- public virtual int IndexOf(string source, string value, CompareOptions options)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- return IndexOf(source, value, 0, source.Length, options);
- }
- public virtual int IndexOf(string source, char value, int startIndex)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
- }
- public virtual int IndexOf(string source, string value, int startIndex)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
- }
- public virtual int IndexOf(string source, char value, int startIndex, CompareOptions options)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- return IndexOf(source, value, startIndex, source.Length - startIndex, options);
- }
- public virtual int IndexOf(string source, string value, int startIndex, CompareOptions options)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- return IndexOf(source, value, startIndex, source.Length - startIndex, options);
- }
- public virtual int IndexOf(string source, char value, int startIndex, int count)
- {
- return IndexOf(source, value, startIndex, count, CompareOptions.None);
- }
- public virtual int IndexOf(string source, string value, int startIndex, int count)
- {
- return IndexOf(source, value, startIndex, count, CompareOptions.None);
- }
- public unsafe virtual int IndexOf(string source, char value, int startIndex, int count, CompareOptions options)
- {
- // Validate inputs
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- if (startIndex < 0 || startIndex > source.Length)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
- if (count < 0 || startIndex > source.Length - count)
- throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
- if (source.Length == 0)
- {
- return -1;
- }
- // Validate CompareOptions
- // Ordinal can't be selected with other flags
- if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal && options != CompareOptions.OrdinalIgnoreCase))
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- return IndexOf(source, char.ToString(value), startIndex, count, options, null);
- }
- public unsafe virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options)
- {
- // Validate inputs
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- if (startIndex > source.Length)
- {
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
- }
- // In Everett we used to return -1 for empty string even if startIndex is negative number so we keeping same behavior here.
- // We return 0 if both source and value are empty strings for Everett compatibility too.
- if (source.Length == 0)
- {
- if (value.Length == 0)
- {
- return 0;
- }
- return -1;
- }
- if (startIndex < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
- }
- if (count < 0 || startIndex > source.Length - count)
- throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
- // Validate CompareOptions
- // Ordinal can't be selected with other flags
- if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal && options != CompareOptions.OrdinalIgnoreCase))
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- return IndexOf(source, value, startIndex, count, options, null);
- }
- internal int IndexOfOrdinalIgnoreCase(ReadOnlySpan<char> source, ReadOnlySpan<char> value)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!value.IsEmpty);
- return IndexOfOrdinalCore(source, value, ignoreCase: true, fromBeginning: true);
- }
- internal int LastIndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!value.IsEmpty);
- return IndexOfOrdinalCore(source, value, ignoreCase, fromBeginning: false);
- }
- internal unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!value.IsEmpty);
- return IndexOfCore(source, value, options, null, fromBeginning: true);
- }
- internal unsafe int LastIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!value.IsEmpty);
- return IndexOfCore(source, value, options, null, fromBeginning: false);
- }
- // The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated
- // and the caller is passing a valid matchLengthPtr pointer.
- internal unsafe int IndexOf(string source, string value, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
- {
- Debug.Assert(source != null);
- Debug.Assert(value != null);
- Debug.Assert(startIndex >= 0);
- if (matchLengthPtr != null)
- {
- *matchLengthPtr = 0;
- }
- if (value.Length == 0)
- {
- return startIndex;
- }
- if (startIndex >= source.Length)
- {
- return -1;
- }
- if (options == CompareOptions.OrdinalIgnoreCase)
- {
- int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
- if (res >= 0 && matchLengthPtr != null)
- {
- *matchLengthPtr = value.Length;
- }
- return res;
- }
- if (GlobalizationMode.Invariant)
- {
- int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
- if (res >= 0 && matchLengthPtr != null)
- {
- *matchLengthPtr = value.Length;
- }
- return res;
- }
- if (options == CompareOptions.Ordinal)
- {
- int retValue = SpanHelpers.IndexOf(
- ref Unsafe.Add(ref source.GetRawStringData(), startIndex),
- count,
- ref value.GetRawStringData(),
- value.Length);
- if (retValue >= 0)
- {
- retValue += startIndex;
- if (matchLengthPtr != null)
- *matchLengthPtr = value.Length;
- }
- return retValue;
- }
- else
- {
- return IndexOfCore(source, value, startIndex, count, options, matchLengthPtr);
- }
- }
- internal int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
- {
- if (!ignoreCase)
- {
- int result = SpanHelpers.IndexOf(
- ref Unsafe.Add(ref source.GetRawStringData(), startIndex),
- count,
- ref value.GetRawStringData(),
- value.Length);
- return (result >= 0 ? startIndex : 0) + result;
- }
- if (GlobalizationMode.Invariant)
- {
- return InvariantIndexOf(source, value, startIndex, count, ignoreCase);
- }
- return IndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // LastIndexOf
- //
- // Returns the last index where value is found in string. The
- // search starts from startIndex and ends at endIndex. Returns -1 if
- // the specified value is not found. If value equals string.Empty,
- // endIndex is returned. Throws IndexOutOfRange if startIndex or
- // endIndex is less than zero or greater than the length of string.
- // Throws ArgumentException if value is null.
- //
- ////////////////////////////////////////////////////////////////////////
- public virtual int LastIndexOf(string source, char value)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- // Can't start at negative index, so make sure we check for the length == 0 case.
- return LastIndexOf(source, value, source.Length - 1, source.Length, CompareOptions.None);
- }
- public virtual int LastIndexOf(string source, string value)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- // Can't start at negative index, so make sure we check for the length == 0 case.
- return LastIndexOf(source, value, source.Length - 1,
- source.Length, CompareOptions.None);
- }
- public virtual int LastIndexOf(string source, char value, CompareOptions options)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- // Can't start at negative index, so make sure we check for the length == 0 case.
- return LastIndexOf(source, value, source.Length - 1,
- source.Length, options);
- }
- public virtual int LastIndexOf(string source, string value, CompareOptions options)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- // Can't start at negative index, so make sure we check for the length == 0 case.
- return LastIndexOf(source, value, source.Length - 1, source.Length, options);
- }
- public virtual int LastIndexOf(string source, char value, int startIndex)
- {
- return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
- }
- public virtual int LastIndexOf(string source, string value, int startIndex)
- {
- return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
- }
- public virtual int LastIndexOf(string source, char value, int startIndex, CompareOptions options)
- {
- return LastIndexOf(source, value, startIndex, startIndex + 1, options);
- }
- public virtual int LastIndexOf(string source, string value, int startIndex, CompareOptions options)
- {
- return LastIndexOf(source, value, startIndex, startIndex + 1, options);
- }
- public virtual int LastIndexOf(string source, char value, int startIndex, int count)
- {
- return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
- }
- public virtual int LastIndexOf(string source, string value, int startIndex, int count)
- {
- return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
- }
- public virtual int LastIndexOf(string source, char value, int startIndex, int count, CompareOptions options)
- {
- // Verify Arguments
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- // Validate CompareOptions
- // Ordinal can't be selected with other flags
- if ((options & ValidIndexMaskOffFlags) != 0 &&
- (options != CompareOptions.Ordinal) &&
- (options != CompareOptions.OrdinalIgnoreCase))
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- // Special case for 0 length input strings
- if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
- return -1;
- // Make sure we're not out of range
- if (startIndex < 0 || startIndex > source.Length)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
- // Make sure that we allow startIndex == source.Length
- if (startIndex == source.Length)
- {
- startIndex--;
- if (count > 0)
- count--;
- }
- // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
- if (count < 0 || startIndex - count + 1 < 0)
- throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
- if (options == CompareOptions.OrdinalIgnoreCase)
- {
- return source.LastIndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
- }
- if (GlobalizationMode.Invariant)
- return InvariantLastIndexOf(source, char.ToString(value), startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
- return LastIndexOfCore(source, value.ToString(), startIndex, count, options);
- }
- public virtual int LastIndexOf(string source, string value, int startIndex, int count, CompareOptions options)
- {
- // Verify Arguments
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- // Validate CompareOptions
- // Ordinal can't be selected with other flags
- if ((options & ValidIndexMaskOffFlags) != 0 &&
- (options != CompareOptions.Ordinal) &&
- (options != CompareOptions.OrdinalIgnoreCase))
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- // Special case for 0 length input strings
- if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
- return (value.Length == 0) ? 0 : -1;
- // Make sure we're not out of range
- if (startIndex < 0 || startIndex > source.Length)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
- // Make sure that we allow startIndex == source.Length
- if (startIndex == source.Length)
- {
- startIndex--;
- if (count > 0)
- count--;
- // If we are looking for nothing, just return 0
- if (value.Length == 0 && count >= 0 && startIndex - count + 1 >= 0)
- return startIndex;
- }
- // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
- if (count < 0 || startIndex - count + 1 < 0)
- throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
- if (options == CompareOptions.OrdinalIgnoreCase)
- {
- return LastIndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
- }
- if (GlobalizationMode.Invariant)
- return InvariantLastIndexOf(source, value, startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
- return LastIndexOfCore(source, value, startIndex, count, options);
- }
- internal int LastIndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
- {
- if (GlobalizationMode.Invariant)
- {
- return InvariantLastIndexOf(source, value, startIndex, count, ignoreCase);
- }
- return LastIndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // GetSortKey
- //
- // Gets the SortKey for the given string with the given options.
- //
- ////////////////////////////////////////////////////////////////////////
- public virtual SortKey GetSortKey(string source, CompareOptions options)
- {
- if (GlobalizationMode.Invariant)
- return InvariantCreateSortKey(source, options);
- return CreateSortKey(source, options);
- }
- public virtual SortKey GetSortKey(string source)
- {
- if (GlobalizationMode.Invariant)
- return InvariantCreateSortKey(source, CompareOptions.None);
- return CreateSortKey(source, CompareOptions.None);
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // Equals
- //
- // Implements Object.Equals(). Returns a boolean indicating whether
- // or not object refers to the same CompareInfo as the current
- // instance.
- //
- ////////////////////////////////////////////////////////////////////////
- public override bool Equals(object value)
- {
- CompareInfo that = value as CompareInfo;
- if (that != null)
- {
- return this.Name == that.Name;
- }
- return (false);
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // GetHashCode
- //
- // Implements Object.GetHashCode(). Returns the hash code for the
- // CompareInfo. The hash code is guaranteed to be the same for
- // CompareInfo A and B where A.Equals(B) is true.
- //
- ////////////////////////////////////////////////////////////////////////
- public override int GetHashCode()
- {
- return (this.Name.GetHashCode());
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // GetHashCodeOfString
- //
- // This internal method allows a method that allows the equivalent of creating a Sortkey for a
- // string from CompareInfo, and generate a hashcode value from it. It is not very convenient
- // to use this method as is and it creates an unnecessary Sortkey object that will be GC'ed.
- //
- // The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both
- // the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects
- // treat the string the same way, this implementation will treat them differently (the same way that
- // Sortkey does at the moment).
- //
- // This method will never be made public itself, but public consumers of it could be created, e.g.:
- //
- // string.GetHashCode(CultureInfo)
- // string.GetHashCode(CompareInfo)
- // string.GetHashCode(CultureInfo, CompareOptions)
- // string.GetHashCode(CompareInfo, CompareOptions)
- // etc.
- //
- // (the methods above that take a CultureInfo would use CultureInfo.CompareInfo)
- //
- ////////////////////////////////////////////////////////////////////////
- internal int GetHashCodeOfString(string source, CompareOptions options)
- {
- //
- // Parameter validation
- //
- if (null == source)
- {
- throw new ArgumentNullException(nameof(source));
- }
- if ((options & ValidHashCodeOfStringMaskOffFlags) == 0)
- {
- // No unsupported flags are set - continue on with the regular logic
- if (GlobalizationMode.Invariant)
- {
- return ((options & CompareOptions.IgnoreCase) != 0) ? source.GetHashCodeOrdinalIgnoreCase() : source.GetHashCode();
- }
- return GetHashCodeOfStringCore(source, options);
- }
- else if (options == CompareOptions.Ordinal)
- {
- // We allow Ordinal in isolation
- return source.GetHashCode();
- }
- else if (options == CompareOptions.OrdinalIgnoreCase)
- {
- // We allow OrdinalIgnoreCase in isolation
- return source.GetHashCodeOrdinalIgnoreCase();
- }
- else
- {
- // Unsupported combination of flags specified
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- }
- }
- public virtual int GetHashCode(string source, CompareOptions options)
- {
- // virtual method delegates to non-virtual method
- return GetHashCodeOfString(source, options);
- }
- public int GetHashCode(ReadOnlySpan<char> source, CompareOptions options)
- {
- //
- // Parameter validation
- //
- if ((options & ValidHashCodeOfStringMaskOffFlags) == 0)
- {
- // No unsupported flags are set - continue on with the regular logic
- if (GlobalizationMode.Invariant)
- {
- return ((options & CompareOptions.IgnoreCase) != 0) ? string.GetHashCodeOrdinalIgnoreCase(source) : string.GetHashCode(source);
- }
- return GetHashCodeOfStringCore(source, options);
- }
- else if (options == CompareOptions.Ordinal)
- {
- // We allow Ordinal in isolation
- return string.GetHashCode(source);
- }
- else if (options == CompareOptions.OrdinalIgnoreCase)
- {
- // We allow OrdinalIgnoreCase in isolation
- return string.GetHashCodeOrdinalIgnoreCase(source);
- }
- else
- {
- // Unsupported combination of flags specified
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- }
- }
- ////////////////////////////////////////////////////////////////////////
- //
- // ToString
- //
- // Implements Object.ToString(). Returns a string describing the
- // CompareInfo.
- //
- ////////////////////////////////////////////////////////////////////////
- public override string ToString()
- {
- return ("CompareInfo - " + this.Name);
- }
- public SortVersion Version
- {
- get
- {
- if (m_SortVersion == null)
- {
- if (GlobalizationMode.Invariant)
- {
- m_SortVersion = new SortVersion(0, CultureInfo.LOCALE_INVARIANT, new Guid(0, 0, 0, 0, 0, 0, 0,
- (byte) (CultureInfo.LOCALE_INVARIANT >> 24),
- (byte) ((CultureInfo.LOCALE_INVARIANT & 0x00FF0000) >> 16),
- (byte) ((CultureInfo.LOCALE_INVARIANT & 0x0000FF00) >> 8),
- (byte) (CultureInfo.LOCALE_INVARIANT & 0xFF)));
- }
- else
- {
- m_SortVersion = GetSortVersion();
- }
- }
- return m_SortVersion;
- }
- }
- public int LCID
- {
- get
- {
- return CultureInfo.GetCultureInfo(Name).LCID;
- }
- }
- }
- }
|