| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544 |
- // 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)
- {
- if (value is CompareInfo that)
- {
- 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;
- }
- }
- }
- }
|