| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467 |
- // 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.
- using System.Diagnostics;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- using System.Runtime.Serialization;
- using System.Text.Unicode;
- using Internal.Runtime.CompilerServices;
- namespace System.Globalization
- {
- /// <summary>
- /// This class implements a set of methods for comparing strings.
- /// </summary>
- [Serializable]
- [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 = null!; // 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);
- }
- /// <summary>
- /// Get the CompareInfo constructed from the data table in the specified
- /// assembly for the specified culture.
- /// Warning: The assembly versioning mechanism is dead!
- /// </summary>
- 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, nameof(assembly));
- }
- return GetCompareInfo(culture);
- }
- /// <summary>
- /// 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.
- /// </summary>
- public static CompareInfo GetCompareInfo(string name, Assembly assembly)
- {
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
- if (assembly == null)
- {
- throw new ArgumentNullException(nameof(assembly));
- }
- if (assembly != typeof(object).Module.Assembly)
- {
- throw new ArgumentException(SR.Argument_OnlyMscorlib, nameof(assembly));
- }
- return GetCompareInfo(name);
- }
- /// <summary>
- /// Get the CompareInfo for the specified culture.
- /// This method is provided for ease of integration with NLS-based software.
- /// </summary>
- public static CompareInfo GetCompareInfo(int culture)
- {
- if (CultureData.IsCustomCultureId(culture))
- {
- throw new ArgumentException(SR.Argument_CustomCultureCannotBePassedByNumber, nameof(culture));
- }
- return CultureInfo.GetCultureInfo(culture).CompareInfo;
- }
- /// <summary>
- /// Get the CompareInfo for the specified culture.
- /// </summary>
- 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)
- {
- throw new ArgumentNullException(nameof(text));
- }
- if (text.Length == 0)
- {
- return false;
- }
- if (GlobalizationMode.Invariant)
- {
- return true;
- }
- fixed (char *pChar = text)
- {
- return IsSortable(pChar, text.Length);
- }
- }
- [OnDeserializing]
- private void OnDeserializing(StreamingContext ctx)
- {
- // this becomes null for a brief moment before deserialization
- // after serialization is finished it is never null.
- 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
- m_name = CultureInfo.GetCultureInfo(culture)._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(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");
- }
- /// <summary>
- /// 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)
- /// </summary>
- 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;
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- 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);
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- 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));
- }
- 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);
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- }
- /// <summary>
- /// Determines whether prefix is a prefix of string. If prefix equals
- /// string.Empty, true is returned.
- /// </summary>
- public virtual bool IsPrefix(string source, string prefix, CompareOptions options)
- {
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
- if (prefix == null)
- {
- throw new ArgumentNullException(nameof(prefix));
- }
- 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);
- }
- /// <summary>
- /// Determines whether suffix is a suffix of string. If suffix equals
- /// string.Empty, true is returned.
- /// </summary>
- public virtual bool IsSuffix(string source, string suffix, CompareOptions options)
- {
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
- if (suffix == null)
- {
- throw new ArgumentNullException(nameof(suffix));
- }
- 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);
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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)
- {
- 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)
- {
- 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);
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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);
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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)
- {
- 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)
- {
- 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);
- }
- /// <summary>
- /// Gets the SortKey for the given string with the given options.
- /// </summary>
- 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);
- }
- public override bool Equals(object? value)
- {
- return value is CompareInfo otherCompareInfo
- && Name == otherCompareInfo.Name;
- }
- public override int GetHashCode() => Name.GetHashCode();
- /// <summary>
- /// 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)
- /// </summary>
- internal int GetHashCodeOfString(string source, CompareOptions options)
- {
- if (source == null)
- {
- 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)
- {
- 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));
- }
- }
- public override string ToString() => "CompareInfo - " + 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 => CultureInfo.GetCultureInfo(Name).LCID;
- }
- }
|