CompareInfo.cs 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. ////////////////////////////////////////////////////////////////////////////
  5. //
  6. //
  7. //
  8. // Purpose: This class implements a set of methods for comparing
  9. // strings.
  10. //
  11. //
  12. ////////////////////////////////////////////////////////////////////////////
  13. using System.Reflection;
  14. using System.Diagnostics;
  15. using System.Runtime.InteropServices;
  16. using System.Runtime.Serialization;
  17. using System.Text;
  18. using Internal.Runtime.CompilerServices;
  19. namespace System.Globalization
  20. {
  21. [Flags]
  22. public enum CompareOptions
  23. {
  24. None = 0x00000000,
  25. IgnoreCase = 0x00000001,
  26. IgnoreNonSpace = 0x00000002,
  27. IgnoreSymbols = 0x00000004,
  28. IgnoreKanaType = 0x00000008, // ignore kanatype
  29. IgnoreWidth = 0x00000010, // ignore width
  30. OrdinalIgnoreCase = 0x10000000, // This flag can not be used with other flags.
  31. StringSort = 0x20000000, // use string sort method
  32. Ordinal = 0x40000000, // This flag can not be used with other flags.
  33. }
  34. [Serializable]
  35. [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
  36. public partial class CompareInfo : IDeserializationCallback
  37. {
  38. // Mask used to check if IndexOf()/LastIndexOf()/IsPrefix()/IsPostfix() has the right flags.
  39. private const CompareOptions ValidIndexMaskOffFlags =
  40. ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
  41. CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
  42. // Mask used to check if Compare() has the right flags.
  43. private const CompareOptions ValidCompareMaskOffFlags =
  44. ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
  45. CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
  46. // Mask used to check if GetHashCodeOfString() has the right flags.
  47. private const CompareOptions ValidHashCodeOfStringMaskOffFlags =
  48. ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
  49. CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType);
  50. // Mask used to check if we have the right flags.
  51. private const CompareOptions ValidSortkeyCtorMaskOffFlags =
  52. ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace |
  53. CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
  54. // Cache the invariant CompareInfo
  55. internal static readonly CompareInfo Invariant = CultureInfo.InvariantCulture.CompareInfo;
  56. //
  57. // CompareInfos have an interesting identity. They are attached to the locale that created them,
  58. // ie: en-US would have an en-US sort. For haw-US (custom), then we serialize it as haw-US.
  59. // The interesting part is that since haw-US doesn't have its own sort, it has to point at another
  60. // locale, which is what SCOMPAREINFO does.
  61. [OptionalField(VersionAdded = 2)]
  62. private string m_name; // The name used to construct this CompareInfo. Do not rename (binary serialization)
  63. [NonSerialized]
  64. private string _sortName; // The name that defines our behavior
  65. [OptionalField(VersionAdded = 3)]
  66. private SortVersion m_SortVersion; // Do not rename (binary serialization)
  67. private int culture; // Do not rename (binary serialization). The fields sole purpose is to support Desktop serialization.
  68. internal CompareInfo(CultureInfo culture)
  69. {
  70. m_name = culture._name;
  71. InitSort(culture);
  72. }
  73. /*=================================GetCompareInfo==========================
  74. **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
  75. ** Warning: The assembly versioning mechanism is dead!
  76. **Returns: The CompareInfo for the specified culture.
  77. **Arguments:
  78. ** culture the ID of the culture
  79. ** assembly the assembly which contains the sorting table.
  80. **Exceptions:
  81. ** ArgumentNullException when the assembly is null
  82. ** ArgumentException if culture is invalid.
  83. ============================================================================*/
  84. // Assembly constructor should be deprecated, we don't act on the assembly information any more
  85. public static CompareInfo GetCompareInfo(int culture, Assembly assembly)
  86. {
  87. // Parameter checking.
  88. if (assembly == null)
  89. {
  90. throw new ArgumentNullException(nameof(assembly));
  91. }
  92. if (assembly != typeof(object).Module.Assembly)
  93. {
  94. throw new ArgumentException(SR.Argument_OnlyMscorlib);
  95. }
  96. return GetCompareInfo(culture);
  97. }
  98. /*=================================GetCompareInfo==========================
  99. **Action: Get the CompareInfo constructed from the data table in the specified assembly for the specified culture.
  100. ** The purpose of this method is to provide version for CompareInfo tables.
  101. **Returns: The CompareInfo for the specified culture.
  102. **Arguments:
  103. ** name the name of the culture
  104. ** assembly the assembly which contains the sorting table.
  105. **Exceptions:
  106. ** ArgumentNullException when the assembly is null
  107. ** ArgumentException if name is invalid.
  108. ============================================================================*/
  109. // Assembly constructor should be deprecated, we don't act on the assembly information any more
  110. public static CompareInfo GetCompareInfo(string name, Assembly assembly)
  111. {
  112. if (name == null || assembly == null)
  113. {
  114. throw new ArgumentNullException(name == null ? nameof(name) : nameof(assembly));
  115. }
  116. if (assembly != typeof(object).Module.Assembly)
  117. {
  118. throw new ArgumentException(SR.Argument_OnlyMscorlib);
  119. }
  120. return GetCompareInfo(name);
  121. }
  122. /*=================================GetCompareInfo==========================
  123. **Action: Get the CompareInfo for the specified culture.
  124. ** This method is provided for ease of integration with NLS-based software.
  125. **Returns: The CompareInfo for the specified culture.
  126. **Arguments:
  127. ** culture the ID of the culture.
  128. **Exceptions:
  129. ** ArgumentException if culture is invalid.
  130. ============================================================================*/
  131. // People really shouldn't be calling LCID versions, no custom support
  132. public static CompareInfo GetCompareInfo(int culture)
  133. {
  134. if (CultureData.IsCustomCultureId(culture))
  135. {
  136. // Customized culture cannot be created by the LCID.
  137. throw new ArgumentException(SR.Argument_CustomCultureCannotBePassedByNumber, nameof(culture));
  138. }
  139. return CultureInfo.GetCultureInfo(culture).CompareInfo;
  140. }
  141. /*=================================GetCompareInfo==========================
  142. **Action: Get the CompareInfo for the specified culture.
  143. **Returns: The CompareInfo for the specified culture.
  144. **Arguments:
  145. ** name the name of the culture.
  146. **Exceptions:
  147. ** ArgumentException if name is invalid.
  148. ============================================================================*/
  149. public static CompareInfo GetCompareInfo(string name)
  150. {
  151. if (name == null)
  152. {
  153. throw new ArgumentNullException(nameof(name));
  154. }
  155. return CultureInfo.GetCultureInfo(name).CompareInfo;
  156. }
  157. public static unsafe bool IsSortable(char ch)
  158. {
  159. if (GlobalizationMode.Invariant)
  160. {
  161. return true;
  162. }
  163. char *pChar = &ch;
  164. return IsSortable(pChar, 1);
  165. }
  166. public static unsafe bool IsSortable(string text)
  167. {
  168. if (text == null)
  169. {
  170. // A null param is invalid here.
  171. throw new ArgumentNullException(nameof(text));
  172. }
  173. if (text.Length == 0)
  174. {
  175. // A zero length string is not invalid, but it is also not sortable.
  176. return (false);
  177. }
  178. if (GlobalizationMode.Invariant)
  179. {
  180. return true;
  181. }
  182. fixed (char *pChar = text)
  183. {
  184. return IsSortable(pChar, text.Length);
  185. }
  186. }
  187. [OnDeserializing]
  188. private void OnDeserializing(StreamingContext ctx)
  189. {
  190. m_name = null;
  191. }
  192. void IDeserializationCallback.OnDeserialization(object sender)
  193. {
  194. OnDeserialized();
  195. }
  196. [OnDeserialized]
  197. private void OnDeserialized(StreamingContext ctx)
  198. {
  199. OnDeserialized();
  200. }
  201. private void OnDeserialized()
  202. {
  203. // If we didn't have a name, use the LCID
  204. if (m_name == null)
  205. {
  206. // From whidbey, didn't have a name
  207. CultureInfo ci = CultureInfo.GetCultureInfo(this.culture);
  208. m_name = ci._name;
  209. }
  210. else
  211. {
  212. InitSort(CultureInfo.GetCultureInfo(m_name));
  213. }
  214. }
  215. [OnSerializing]
  216. private void OnSerializing(StreamingContext ctx)
  217. {
  218. // This is merely for serialization compatibility with Whidbey/Orcas, it can go away when we don't want that compat any more.
  219. culture = CultureInfo.GetCultureInfo(this.Name).LCID; // This is the lcid of the constructing culture (still have to dereference to get target sort)
  220. Debug.Assert(m_name != null, "CompareInfo.OnSerializing - expected m_name to be set already");
  221. }
  222. ///////////////////////////----- Name -----/////////////////////////////////
  223. //
  224. // Returns the name of the culture (well actually, of the sort).
  225. // Very important for providing a non-LCID way of identifying
  226. // what the sort is.
  227. //
  228. // Note that this name isn't dereferenced in case the CompareInfo is a different locale
  229. // which is consistent with the behaviors of earlier versions. (so if you ask for a sort
  230. // and the locale's changed behavior, then you'll get changed behavior, which is like
  231. // what happens for a version update)
  232. //
  233. ////////////////////////////////////////////////////////////////////////
  234. public virtual string Name
  235. {
  236. get
  237. {
  238. Debug.Assert(m_name != null, "CompareInfo.Name Expected _name to be set");
  239. if (m_name == "zh-CHT" || m_name == "zh-CHS")
  240. {
  241. return m_name;
  242. }
  243. return _sortName;
  244. }
  245. }
  246. ////////////////////////////////////////////////////////////////////////
  247. //
  248. // Compare
  249. //
  250. // Compares the two strings with the given options. Returns 0 if the
  251. // two strings are equal, a number less than 0 if string1 is less
  252. // than string2, and a number greater than 0 if string1 is greater
  253. // than string2.
  254. //
  255. ////////////////////////////////////////////////////////////////////////
  256. public virtual int Compare(string string1, string string2)
  257. {
  258. return Compare(string1, string2, CompareOptions.None);
  259. }
  260. public virtual int Compare(string string1, string string2, CompareOptions options)
  261. {
  262. if (options == CompareOptions.OrdinalIgnoreCase)
  263. {
  264. return string.Compare(string1, string2, StringComparison.OrdinalIgnoreCase);
  265. }
  266. // Verify the options before we do any real comparison.
  267. if ((options & CompareOptions.Ordinal) != 0)
  268. {
  269. if (options != CompareOptions.Ordinal)
  270. {
  271. throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
  272. }
  273. return string.CompareOrdinal(string1, string2);
  274. }
  275. if ((options & ValidCompareMaskOffFlags) != 0)
  276. {
  277. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  278. }
  279. //Our paradigm is that null sorts less than any other string and
  280. //that two nulls sort as equal.
  281. if (string1 == null)
  282. {
  283. if (string2 == null)
  284. {
  285. return (0); // Equal
  286. }
  287. return (-1); // null < non-null
  288. }
  289. if (string2 == null)
  290. {
  291. return (1); // non-null > null
  292. }
  293. if (GlobalizationMode.Invariant)
  294. {
  295. if ((options & CompareOptions.IgnoreCase) != 0)
  296. return CompareOrdinalIgnoreCase(string1, string2);
  297. return string.CompareOrdinal(string1, string2);
  298. }
  299. return CompareString(string1.AsSpan(), string2.AsSpan(), options);
  300. }
  301. // TODO https://github.com/dotnet/coreclr/issues/13827:
  302. // This method shouldn't be necessary, as we should be able to just use the overload
  303. // that takes two spans. But due to this issue, that's adding significant overhead.
  304. internal int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions options)
  305. {
  306. if (options == CompareOptions.OrdinalIgnoreCase)
  307. {
  308. return CompareOrdinalIgnoreCase(string1, string2.AsSpan());
  309. }
  310. // Verify the options before we do any real comparison.
  311. if ((options & CompareOptions.Ordinal) != 0)
  312. {
  313. if (options != CompareOptions.Ordinal)
  314. {
  315. throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options));
  316. }
  317. return string.CompareOrdinal(string1, string2.AsSpan());
  318. }
  319. if ((options & ValidCompareMaskOffFlags) != 0)
  320. {
  321. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  322. }
  323. // null sorts less than any other string.
  324. if (string2 == null)
  325. {
  326. return 1;
  327. }
  328. if (GlobalizationMode.Invariant)
  329. {
  330. return (options & CompareOptions.IgnoreCase) != 0 ?
  331. CompareOrdinalIgnoreCase(string1, string2.AsSpan()) :
  332. string.CompareOrdinal(string1, string2.AsSpan());
  333. }
  334. return CompareString(string1, string2, options);
  335. }
  336. internal int CompareOptionNone(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
  337. {
  338. // Check for empty span or span from a null string
  339. if (string1.Length == 0 || string2.Length == 0)
  340. return string1.Length - string2.Length;
  341. return GlobalizationMode.Invariant ?
  342. string.CompareOrdinal(string1, string2) :
  343. CompareString(string1, string2, CompareOptions.None);
  344. }
  345. internal int CompareOptionIgnoreCase(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
  346. {
  347. // Check for empty span or span from a null string
  348. if (string1.Length == 0 || string2.Length == 0)
  349. return string1.Length - string2.Length;
  350. return GlobalizationMode.Invariant ?
  351. CompareOrdinalIgnoreCase(string1, string2) :
  352. CompareString(string1, string2, CompareOptions.IgnoreCase);
  353. }
  354. ////////////////////////////////////////////////////////////////////////
  355. //
  356. // Compare
  357. //
  358. // Compares the specified regions of the two strings with the given
  359. // options.
  360. // Returns 0 if the two strings are equal, a number less than 0 if
  361. // string1 is less than string2, and a number greater than 0 if
  362. // string1 is greater than string2.
  363. //
  364. ////////////////////////////////////////////////////////////////////////
  365. public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2)
  366. {
  367. return Compare(string1, offset1, length1, string2, offset2, length2, 0);
  368. }
  369. public virtual int Compare(string string1, int offset1, string string2, int offset2, CompareOptions options)
  370. {
  371. return Compare(string1, offset1, string1 == null ? 0 : string1.Length - offset1,
  372. string2, offset2, string2 == null ? 0 : string2.Length - offset2, options);
  373. }
  374. public virtual int Compare(string string1, int offset1, string string2, int offset2)
  375. {
  376. return Compare(string1, offset1, string2, offset2, 0);
  377. }
  378. public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options)
  379. {
  380. if (options == CompareOptions.OrdinalIgnoreCase)
  381. {
  382. int result = string.Compare(string1, offset1, string2, offset2, length1 < length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase);
  383. if ((length1 != length2) && result == 0)
  384. return (length1 > length2 ? 1 : -1);
  385. return (result);
  386. }
  387. // Verify inputs
  388. if (length1 < 0 || length2 < 0)
  389. {
  390. throw new ArgumentOutOfRangeException((length1 < 0) ? nameof(length1) : nameof(length2), SR.ArgumentOutOfRange_NeedPosNum);
  391. }
  392. if (offset1 < 0 || offset2 < 0)
  393. {
  394. throw new ArgumentOutOfRangeException((offset1 < 0) ? nameof(offset1) : nameof(offset2), SR.ArgumentOutOfRange_NeedPosNum);
  395. }
  396. if (offset1 > (string1 == null ? 0 : string1.Length) - length1)
  397. {
  398. throw new ArgumentOutOfRangeException(nameof(string1), SR.ArgumentOutOfRange_OffsetLength);
  399. }
  400. if (offset2 > (string2 == null ? 0 : string2.Length) - length2)
  401. {
  402. throw new ArgumentOutOfRangeException(nameof(string2), SR.ArgumentOutOfRange_OffsetLength);
  403. }
  404. if ((options & CompareOptions.Ordinal) != 0)
  405. {
  406. if (options != CompareOptions.Ordinal)
  407. {
  408. throw new ArgumentException(SR.Argument_CompareOptionOrdinal,
  409. nameof(options));
  410. }
  411. }
  412. else if ((options & ValidCompareMaskOffFlags) != 0)
  413. {
  414. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  415. }
  416. //
  417. // Check for the null case.
  418. //
  419. if (string1 == null)
  420. {
  421. if (string2 == null)
  422. {
  423. return (0);
  424. }
  425. return (-1);
  426. }
  427. if (string2 == null)
  428. {
  429. return (1);
  430. }
  431. ReadOnlySpan<char> span1 = string1.AsSpan(offset1, length1);
  432. ReadOnlySpan<char> span2 = string2.AsSpan(offset2, length2);
  433. if (options == CompareOptions.Ordinal)
  434. {
  435. return string.CompareOrdinal(span1, span2);
  436. }
  437. if (GlobalizationMode.Invariant)
  438. {
  439. if ((options & CompareOptions.IgnoreCase) != 0)
  440. return CompareOrdinalIgnoreCase(span1, span2);
  441. return string.CompareOrdinal(span1, span2);
  442. }
  443. return CompareString(span1, span2, options);
  444. }
  445. //
  446. // CompareOrdinalIgnoreCase compare two string ordinally with ignoring the case.
  447. // it assumes the strings are Ascii string till we hit non Ascii character in strA or strB and then we continue the comparison by
  448. // calling the OS.
  449. //
  450. internal static int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB)
  451. {
  452. Debug.Assert(indexA + lengthA <= strA.Length);
  453. Debug.Assert(indexB + lengthB <= strB.Length);
  454. return CompareOrdinalIgnoreCase(
  455. ref Unsafe.Add(ref strA.GetRawStringData(), indexA),
  456. lengthA,
  457. ref Unsafe.Add(ref strB.GetRawStringData(), indexB),
  458. lengthB);
  459. }
  460. internal static int CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
  461. {
  462. return CompareOrdinalIgnoreCase(ref MemoryMarshal.GetReference(strA), strA.Length, ref MemoryMarshal.GetReference(strB), strB.Length);
  463. }
  464. internal static int CompareOrdinalIgnoreCase(string strA, string strB)
  465. {
  466. return CompareOrdinalIgnoreCase(ref strA.GetRawStringData(), strA.Length, ref strB.GetRawStringData(), strB.Length);
  467. }
  468. internal static int CompareOrdinalIgnoreCase(ref char strA, int lengthA, ref char strB, int lengthB)
  469. {
  470. int length = Math.Min(lengthA, lengthB);
  471. int range = length;
  472. ref char charA = ref strA;
  473. ref char charB = ref strB;
  474. // in InvariantMode we support all range and not only the ascii characters.
  475. char maxChar = (GlobalizationMode.Invariant ? (char)0xFFFF : (char)0x7F);
  476. while (length != 0 && charA <= maxChar && charB <= maxChar)
  477. {
  478. // Ordinal equals or lowercase equals if the result ends up in the a-z range
  479. if (charA == charB ||
  480. ((charA | 0x20) == (charB | 0x20) &&
  481. (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a')))
  482. {
  483. length--;
  484. charA = ref Unsafe.Add(ref charA, 1);
  485. charB = ref Unsafe.Add(ref charB, 1);
  486. }
  487. else
  488. {
  489. int currentA = charA;
  490. int currentB = charB;
  491. // Uppercase both chars if needed
  492. if ((uint)(charA - 'a') <= 'z' - 'a')
  493. currentA -= 0x20;
  494. if ((uint)(charB - 'a') <= 'z' - 'a')
  495. currentB -= 0x20;
  496. // Return the (case-insensitive) difference between them.
  497. return currentA - currentB;
  498. }
  499. }
  500. if (length == 0)
  501. return lengthA - lengthB;
  502. Debug.Assert(!GlobalizationMode.Invariant);
  503. range -= length;
  504. return CompareStringOrdinalIgnoreCase(ref charA, lengthA - range, ref charB, lengthB - range);
  505. }
  506. internal static bool EqualsOrdinalIgnoreCase(ref char charA, ref char charB, int length)
  507. {
  508. IntPtr byteOffset = IntPtr.Zero;
  509. #if BIT64
  510. // Read 4 chars (64 bits) at a time from each string
  511. while ((uint)length >= 4)
  512. {
  513. ulong valueA = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<char, byte>(ref Unsafe.AddByteOffset(ref charA, byteOffset)));
  514. ulong valueB = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<char, byte>(ref Unsafe.AddByteOffset(ref charB, byteOffset)));
  515. // A 32-bit test - even with the bit-twiddling here - is more efficient than a 64-bit test.
  516. ulong temp = valueA | valueB;
  517. if (!Utf16Utility.AllCharsInUInt32AreAscii((uint)temp | (uint)(temp >> 32)))
  518. {
  519. goto NonAscii; // one of the inputs contains non-ASCII data
  520. }
  521. // Generally, the caller has likely performed a first-pass check that the input strings
  522. // are likely equal. Consider a dictionary which computes the hash code of its key before
  523. // performing a proper deep equality check of the string contents. We want to optimize for
  524. // the case where the equality check is likely to succeed, which means that we want to avoid
  525. // branching within this loop unless we're about to exit the loop, either due to failure or
  526. // due to us running out of input data.
  527. if (!Utf16Utility.UInt64OrdinalIgnoreCaseAscii(valueA, valueB))
  528. {
  529. return false;
  530. }
  531. byteOffset += 8;
  532. length -= 4;
  533. }
  534. #endif
  535. // Read 2 chars (32 bits) at a time from each string
  536. #if BIT64
  537. if ((uint)length >= 2)
  538. #else
  539. while ((uint)length >= 2)
  540. #endif
  541. {
  542. uint valueA = Unsafe.ReadUnaligned<uint>(ref Unsafe.As<char, byte>(ref Unsafe.AddByteOffset(ref charA, byteOffset)));
  543. uint valueB = Unsafe.ReadUnaligned<uint>(ref Unsafe.As<char, byte>(ref Unsafe.AddByteOffset(ref charB, byteOffset)));
  544. if (!Utf16Utility.AllCharsInUInt32AreAscii(valueA | valueB))
  545. {
  546. goto NonAscii; // one of the inputs contains non-ASCII data
  547. }
  548. // Generally, the caller has likely performed a first-pass check that the input strings
  549. // are likely equal. Consider a dictionary which computes the hash code of its key before
  550. // performing a proper deep equality check of the string contents. We want to optimize for
  551. // the case where the equality check is likely to succeed, which means that we want to avoid
  552. // branching within this loop unless we're about to exit the loop, either due to failure or
  553. // due to us running out of input data.
  554. if (!Utf16Utility.UInt32OrdinalIgnoreCaseAscii(valueA, valueB))
  555. {
  556. return false;
  557. }
  558. byteOffset += 4;
  559. length -= 2;
  560. }
  561. if (length != 0)
  562. {
  563. Debug.Assert(length == 1);
  564. uint valueA = Unsafe.AddByteOffset(ref charA, byteOffset);
  565. uint valueB = Unsafe.AddByteOffset(ref charB, byteOffset);
  566. if ((valueA | valueB) > 0x7Fu)
  567. {
  568. goto NonAscii; // one of the inputs contains non-ASCII data
  569. }
  570. if (valueA == valueB)
  571. {
  572. return true; // exact match
  573. }
  574. valueA |= 0x20u;
  575. if ((uint)(valueA - 'a') > (uint)('z' - 'a'))
  576. {
  577. return false; // not exact match, and first input isn't in [A-Za-z]
  578. }
  579. // The ternary operator below seems redundant but helps RyuJIT generate more optimal code.
  580. // See https://github.com/dotnet/coreclr/issues/914.
  581. return (valueA == (valueB | 0x20u)) ? true : false;
  582. }
  583. Debug.Assert(length == 0);
  584. return true;
  585. NonAscii:
  586. // The non-ASCII case is factored out into its own helper method so that the JIT
  587. // doesn't need to emit a complex prolog for its caller (this method).
  588. return EqualsOrdinalIgnoreCaseNonAscii(ref Unsafe.AddByteOffset(ref charA, byteOffset), ref Unsafe.AddByteOffset(ref charB, byteOffset), length);
  589. }
  590. private static bool EqualsOrdinalIgnoreCaseNonAscii(ref char charA, ref char charB, int length)
  591. {
  592. if (!GlobalizationMode.Invariant)
  593. {
  594. return CompareStringOrdinalIgnoreCase(ref charA, length, ref charB, length) == 0;
  595. }
  596. else
  597. {
  598. // If we don't have localization tables to consult, we'll still perform a case-insensitive
  599. // check for ASCII characters, but if we see anything outside the ASCII range we'll immediately
  600. // fail if it doesn't have true bitwise equality.
  601. IntPtr byteOffset = IntPtr.Zero;
  602. while (length != 0)
  603. {
  604. // Ordinal equals or lowercase equals if the result ends up in the a-z range
  605. uint valueA = Unsafe.AddByteOffset(ref charA, byteOffset);
  606. uint valueB = Unsafe.AddByteOffset(ref charB, byteOffset);
  607. if (valueA == valueB ||
  608. ((valueA | 0x20) == (valueB | 0x20) &&
  609. (uint)((valueA | 0x20) - 'a') <= (uint)('z' - 'a')))
  610. {
  611. byteOffset += 2;
  612. length--;
  613. }
  614. else
  615. {
  616. return false;
  617. }
  618. }
  619. return true;
  620. }
  621. }
  622. ////////////////////////////////////////////////////////////////////////
  623. //
  624. // IsPrefix
  625. //
  626. // Determines whether prefix is a prefix of string. If prefix equals
  627. // string.Empty, true is returned.
  628. //
  629. ////////////////////////////////////////////////////////////////////////
  630. public virtual bool IsPrefix(string source, string prefix, CompareOptions options)
  631. {
  632. if (source == null || prefix == null)
  633. {
  634. throw new ArgumentNullException((source == null ? nameof(source) : nameof(prefix)),
  635. SR.ArgumentNull_String);
  636. }
  637. if (prefix.Length == 0)
  638. {
  639. return (true);
  640. }
  641. if (source.Length == 0)
  642. {
  643. return false;
  644. }
  645. if (options == CompareOptions.OrdinalIgnoreCase)
  646. {
  647. return source.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
  648. }
  649. if (options == CompareOptions.Ordinal)
  650. {
  651. return source.StartsWith(prefix, StringComparison.Ordinal);
  652. }
  653. if ((options & ValidIndexMaskOffFlags) != 0)
  654. {
  655. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  656. }
  657. if (GlobalizationMode.Invariant)
  658. {
  659. return source.StartsWith(prefix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
  660. }
  661. return StartsWith(source, prefix, options);
  662. }
  663. internal bool IsPrefix(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
  664. {
  665. Debug.Assert(prefix.Length != 0);
  666. Debug.Assert(source.Length != 0);
  667. Debug.Assert((options & ValidIndexMaskOffFlags) == 0);
  668. Debug.Assert(!GlobalizationMode.Invariant);
  669. Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
  670. return StartsWith(source, prefix, options);
  671. }
  672. public virtual bool IsPrefix(string source, string prefix)
  673. {
  674. return (IsPrefix(source, prefix, 0));
  675. }
  676. ////////////////////////////////////////////////////////////////////////
  677. //
  678. // IsSuffix
  679. //
  680. // Determines whether suffix is a suffix of string. If suffix equals
  681. // string.Empty, true is returned.
  682. //
  683. ////////////////////////////////////////////////////////////////////////
  684. public virtual bool IsSuffix(string source, string suffix, CompareOptions options)
  685. {
  686. if (source == null || suffix == null)
  687. {
  688. throw new ArgumentNullException((source == null ? nameof(source) : nameof(suffix)),
  689. SR.ArgumentNull_String);
  690. }
  691. if (suffix.Length == 0)
  692. {
  693. return (true);
  694. }
  695. if (source.Length == 0)
  696. {
  697. return false;
  698. }
  699. if (options == CompareOptions.OrdinalIgnoreCase)
  700. {
  701. return source.EndsWith(suffix, StringComparison.OrdinalIgnoreCase);
  702. }
  703. if (options == CompareOptions.Ordinal)
  704. {
  705. return source.EndsWith(suffix, StringComparison.Ordinal);
  706. }
  707. if ((options & ValidIndexMaskOffFlags) != 0)
  708. {
  709. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  710. }
  711. if (GlobalizationMode.Invariant)
  712. {
  713. return source.EndsWith(suffix, (options & CompareOptions.IgnoreCase) != 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
  714. }
  715. return EndsWith(source, suffix, options);
  716. }
  717. internal bool IsSuffix(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
  718. {
  719. Debug.Assert(suffix.Length != 0);
  720. Debug.Assert(source.Length != 0);
  721. Debug.Assert((options & ValidIndexMaskOffFlags) == 0);
  722. Debug.Assert(!GlobalizationMode.Invariant);
  723. Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
  724. return EndsWith(source, suffix, options);
  725. }
  726. public virtual bool IsSuffix(string source, string suffix)
  727. {
  728. return (IsSuffix(source, suffix, 0));
  729. }
  730. ////////////////////////////////////////////////////////////////////////
  731. //
  732. // IndexOf
  733. //
  734. // Returns the first index where value is found in string. The
  735. // search starts from startIndex and ends at endIndex. Returns -1 if
  736. // the specified value is not found. If value equals string.Empty,
  737. // startIndex is returned. Throws IndexOutOfRange if startIndex or
  738. // endIndex is less than zero or greater than the length of string.
  739. // Throws ArgumentException if value is null.
  740. //
  741. ////////////////////////////////////////////////////////////////////////
  742. public virtual int IndexOf(string source, char value)
  743. {
  744. if (source == null)
  745. throw new ArgumentNullException(nameof(source));
  746. return IndexOf(source, value, 0, source.Length, CompareOptions.None);
  747. }
  748. public virtual int IndexOf(string source, string value)
  749. {
  750. if (source == null)
  751. throw new ArgumentNullException(nameof(source));
  752. return IndexOf(source, value, 0, source.Length, CompareOptions.None);
  753. }
  754. public virtual int IndexOf(string source, char value, CompareOptions options)
  755. {
  756. if (source == null)
  757. throw new ArgumentNullException(nameof(source));
  758. return IndexOf(source, value, 0, source.Length, options);
  759. }
  760. public virtual int IndexOf(string source, string value, CompareOptions options)
  761. {
  762. if (source == null)
  763. throw new ArgumentNullException(nameof(source));
  764. return IndexOf(source, value, 0, source.Length, options);
  765. }
  766. public virtual int IndexOf(string source, char value, int startIndex)
  767. {
  768. if (source == null)
  769. throw new ArgumentNullException(nameof(source));
  770. return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
  771. }
  772. public virtual int IndexOf(string source, string value, int startIndex)
  773. {
  774. if (source == null)
  775. throw new ArgumentNullException(nameof(source));
  776. return IndexOf(source, value, startIndex, source.Length - startIndex, CompareOptions.None);
  777. }
  778. public virtual int IndexOf(string source, char value, int startIndex, CompareOptions options)
  779. {
  780. if (source == null)
  781. throw new ArgumentNullException(nameof(source));
  782. return IndexOf(source, value, startIndex, source.Length - startIndex, options);
  783. }
  784. public virtual int IndexOf(string source, string value, int startIndex, CompareOptions options)
  785. {
  786. if (source == null)
  787. throw new ArgumentNullException(nameof(source));
  788. return IndexOf(source, value, startIndex, source.Length - startIndex, options);
  789. }
  790. public virtual int IndexOf(string source, char value, int startIndex, int count)
  791. {
  792. return IndexOf(source, value, startIndex, count, CompareOptions.None);
  793. }
  794. public virtual int IndexOf(string source, string value, int startIndex, int count)
  795. {
  796. return IndexOf(source, value, startIndex, count, CompareOptions.None);
  797. }
  798. public unsafe virtual int IndexOf(string source, char value, int startIndex, int count, CompareOptions options)
  799. {
  800. // Validate inputs
  801. if (source == null)
  802. throw new ArgumentNullException(nameof(source));
  803. if (startIndex < 0 || startIndex > source.Length)
  804. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
  805. if (count < 0 || startIndex > source.Length - count)
  806. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
  807. if (source.Length == 0)
  808. {
  809. return -1;
  810. }
  811. // Validate CompareOptions
  812. // Ordinal can't be selected with other flags
  813. if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal && options != CompareOptions.OrdinalIgnoreCase))
  814. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  815. return IndexOf(source, char.ToString(value), startIndex, count, options, null);
  816. }
  817. public unsafe virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options)
  818. {
  819. // Validate inputs
  820. if (source == null)
  821. throw new ArgumentNullException(nameof(source));
  822. if (value == null)
  823. throw new ArgumentNullException(nameof(value));
  824. if (startIndex > source.Length)
  825. {
  826. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
  827. }
  828. // In Everett we used to return -1 for empty string even if startIndex is negative number so we keeping same behavior here.
  829. // We return 0 if both source and value are empty strings for Everett compatibility too.
  830. if (source.Length == 0)
  831. {
  832. if (value.Length == 0)
  833. {
  834. return 0;
  835. }
  836. return -1;
  837. }
  838. if (startIndex < 0)
  839. {
  840. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
  841. }
  842. if (count < 0 || startIndex > source.Length - count)
  843. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
  844. // Validate CompareOptions
  845. // Ordinal can't be selected with other flags
  846. if ((options & ValidIndexMaskOffFlags) != 0 && (options != CompareOptions.Ordinal && options != CompareOptions.OrdinalIgnoreCase))
  847. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  848. return IndexOf(source, value, startIndex, count, options, null);
  849. }
  850. internal int IndexOfOrdinalIgnoreCase(ReadOnlySpan<char> source, ReadOnlySpan<char> value)
  851. {
  852. Debug.Assert(!GlobalizationMode.Invariant);
  853. Debug.Assert(!source.IsEmpty);
  854. Debug.Assert(!value.IsEmpty);
  855. return IndexOfOrdinalCore(source, value, ignoreCase: true, fromBeginning: true);
  856. }
  857. internal int LastIndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
  858. {
  859. Debug.Assert(!GlobalizationMode.Invariant);
  860. Debug.Assert(!source.IsEmpty);
  861. Debug.Assert(!value.IsEmpty);
  862. return IndexOfOrdinalCore(source, value, ignoreCase, fromBeginning: false);
  863. }
  864. internal unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
  865. {
  866. Debug.Assert(!GlobalizationMode.Invariant);
  867. Debug.Assert(!source.IsEmpty);
  868. Debug.Assert(!value.IsEmpty);
  869. return IndexOfCore(source, value, options, null, fromBeginning: true);
  870. }
  871. internal unsafe int LastIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
  872. {
  873. Debug.Assert(!GlobalizationMode.Invariant);
  874. Debug.Assert(!source.IsEmpty);
  875. Debug.Assert(!value.IsEmpty);
  876. return IndexOfCore(source, value, options, null, fromBeginning: false);
  877. }
  878. // The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated
  879. // and the caller is passing a valid matchLengthPtr pointer.
  880. internal unsafe int IndexOf(string source, string value, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
  881. {
  882. Debug.Assert(source != null);
  883. Debug.Assert(value != null);
  884. Debug.Assert(startIndex >= 0);
  885. if (matchLengthPtr != null)
  886. {
  887. *matchLengthPtr = 0;
  888. }
  889. if (value.Length == 0)
  890. {
  891. return startIndex;
  892. }
  893. if (startIndex >= source.Length)
  894. {
  895. return -1;
  896. }
  897. if (options == CompareOptions.OrdinalIgnoreCase)
  898. {
  899. int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
  900. if (res >= 0 && matchLengthPtr != null)
  901. {
  902. *matchLengthPtr = value.Length;
  903. }
  904. return res;
  905. }
  906. if (GlobalizationMode.Invariant)
  907. {
  908. int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
  909. if (res >= 0 && matchLengthPtr != null)
  910. {
  911. *matchLengthPtr = value.Length;
  912. }
  913. return res;
  914. }
  915. if (options == CompareOptions.Ordinal)
  916. {
  917. int retValue = SpanHelpers.IndexOf(
  918. ref Unsafe.Add(ref source.GetRawStringData(), startIndex),
  919. count,
  920. ref value.GetRawStringData(),
  921. value.Length);
  922. if (retValue >= 0)
  923. {
  924. retValue += startIndex;
  925. if (matchLengthPtr != null)
  926. *matchLengthPtr = value.Length;
  927. }
  928. return retValue;
  929. }
  930. else
  931. {
  932. return IndexOfCore(source, value, startIndex, count, options, matchLengthPtr);
  933. }
  934. }
  935. internal int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
  936. {
  937. if (!ignoreCase)
  938. {
  939. int result = SpanHelpers.IndexOf(
  940. ref Unsafe.Add(ref source.GetRawStringData(), startIndex),
  941. count,
  942. ref value.GetRawStringData(),
  943. value.Length);
  944. return (result >= 0 ? startIndex : 0) + result;
  945. }
  946. if (GlobalizationMode.Invariant)
  947. {
  948. return InvariantIndexOf(source, value, startIndex, count, ignoreCase);
  949. }
  950. return IndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
  951. }
  952. ////////////////////////////////////////////////////////////////////////
  953. //
  954. // LastIndexOf
  955. //
  956. // Returns the last index where value is found in string. The
  957. // search starts from startIndex and ends at endIndex. Returns -1 if
  958. // the specified value is not found. If value equals string.Empty,
  959. // endIndex is returned. Throws IndexOutOfRange if startIndex or
  960. // endIndex is less than zero or greater than the length of string.
  961. // Throws ArgumentException if value is null.
  962. //
  963. ////////////////////////////////////////////////////////////////////////
  964. public virtual int LastIndexOf(string source, char value)
  965. {
  966. if (source == null)
  967. throw new ArgumentNullException(nameof(source));
  968. // Can't start at negative index, so make sure we check for the length == 0 case.
  969. return LastIndexOf(source, value, source.Length - 1, source.Length, CompareOptions.None);
  970. }
  971. public virtual int LastIndexOf(string source, string value)
  972. {
  973. if (source == null)
  974. throw new ArgumentNullException(nameof(source));
  975. // Can't start at negative index, so make sure we check for the length == 0 case.
  976. return LastIndexOf(source, value, source.Length - 1,
  977. source.Length, CompareOptions.None);
  978. }
  979. public virtual int LastIndexOf(string source, char value, CompareOptions options)
  980. {
  981. if (source == null)
  982. throw new ArgumentNullException(nameof(source));
  983. // Can't start at negative index, so make sure we check for the length == 0 case.
  984. return LastIndexOf(source, value, source.Length - 1,
  985. source.Length, options);
  986. }
  987. public virtual int LastIndexOf(string source, string value, CompareOptions options)
  988. {
  989. if (source == null)
  990. throw new ArgumentNullException(nameof(source));
  991. // Can't start at negative index, so make sure we check for the length == 0 case.
  992. return LastIndexOf(source, value, source.Length - 1, source.Length, options);
  993. }
  994. public virtual int LastIndexOf(string source, char value, int startIndex)
  995. {
  996. return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
  997. }
  998. public virtual int LastIndexOf(string source, string value, int startIndex)
  999. {
  1000. return LastIndexOf(source, value, startIndex, startIndex + 1, CompareOptions.None);
  1001. }
  1002. public virtual int LastIndexOf(string source, char value, int startIndex, CompareOptions options)
  1003. {
  1004. return LastIndexOf(source, value, startIndex, startIndex + 1, options);
  1005. }
  1006. public virtual int LastIndexOf(string source, string value, int startIndex, CompareOptions options)
  1007. {
  1008. return LastIndexOf(source, value, startIndex, startIndex + 1, options);
  1009. }
  1010. public virtual int LastIndexOf(string source, char value, int startIndex, int count)
  1011. {
  1012. return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
  1013. }
  1014. public virtual int LastIndexOf(string source, string value, int startIndex, int count)
  1015. {
  1016. return LastIndexOf(source, value, startIndex, count, CompareOptions.None);
  1017. }
  1018. public virtual int LastIndexOf(string source, char value, int startIndex, int count, CompareOptions options)
  1019. {
  1020. // Verify Arguments
  1021. if (source == null)
  1022. throw new ArgumentNullException(nameof(source));
  1023. // Validate CompareOptions
  1024. // Ordinal can't be selected with other flags
  1025. if ((options & ValidIndexMaskOffFlags) != 0 &&
  1026. (options != CompareOptions.Ordinal) &&
  1027. (options != CompareOptions.OrdinalIgnoreCase))
  1028. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  1029. // Special case for 0 length input strings
  1030. if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
  1031. return -1;
  1032. // Make sure we're not out of range
  1033. if (startIndex < 0 || startIndex > source.Length)
  1034. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
  1035. // Make sure that we allow startIndex == source.Length
  1036. if (startIndex == source.Length)
  1037. {
  1038. startIndex--;
  1039. if (count > 0)
  1040. count--;
  1041. }
  1042. // 2nd have of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
  1043. if (count < 0 || startIndex - count + 1 < 0)
  1044. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
  1045. if (options == CompareOptions.OrdinalIgnoreCase)
  1046. {
  1047. return source.LastIndexOf(value.ToString(), startIndex, count, StringComparison.OrdinalIgnoreCase);
  1048. }
  1049. if (GlobalizationMode.Invariant)
  1050. return InvariantLastIndexOf(source, char.ToString(value), startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
  1051. return LastIndexOfCore(source, value.ToString(), startIndex, count, options);
  1052. }
  1053. public virtual int LastIndexOf(string source, string value, int startIndex, int count, CompareOptions options)
  1054. {
  1055. // Verify Arguments
  1056. if (source == null)
  1057. throw new ArgumentNullException(nameof(source));
  1058. if (value == null)
  1059. throw new ArgumentNullException(nameof(value));
  1060. // Validate CompareOptions
  1061. // Ordinal can't be selected with other flags
  1062. if ((options & ValidIndexMaskOffFlags) != 0 &&
  1063. (options != CompareOptions.Ordinal) &&
  1064. (options != CompareOptions.OrdinalIgnoreCase))
  1065. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  1066. // Special case for 0 length input strings
  1067. if (source.Length == 0 && (startIndex == -1 || startIndex == 0))
  1068. return (value.Length == 0) ? 0 : -1;
  1069. // Make sure we're not out of range
  1070. if (startIndex < 0 || startIndex > source.Length)
  1071. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
  1072. // Make sure that we allow startIndex == source.Length
  1073. if (startIndex == source.Length)
  1074. {
  1075. startIndex--;
  1076. if (count > 0)
  1077. count--;
  1078. // If we are looking for nothing, just return 0
  1079. if (value.Length == 0 && count >= 0 && startIndex - count + 1 >= 0)
  1080. return startIndex;
  1081. }
  1082. // 2nd half of this also catches when startIndex == MAXINT, so MAXINT - 0 + 1 == -1, which is < 0.
  1083. if (count < 0 || startIndex - count + 1 < 0)
  1084. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_Count);
  1085. if (options == CompareOptions.OrdinalIgnoreCase)
  1086. {
  1087. return LastIndexOfOrdinal(source, value, startIndex, count, ignoreCase: true);
  1088. }
  1089. if (GlobalizationMode.Invariant)
  1090. return InvariantLastIndexOf(source, value, startIndex, count, (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0);
  1091. return LastIndexOfCore(source, value, startIndex, count, options);
  1092. }
  1093. internal int LastIndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase)
  1094. {
  1095. if (GlobalizationMode.Invariant)
  1096. {
  1097. return InvariantLastIndexOf(source, value, startIndex, count, ignoreCase);
  1098. }
  1099. return LastIndexOfOrdinalCore(source, value, startIndex, count, ignoreCase);
  1100. }
  1101. ////////////////////////////////////////////////////////////////////////
  1102. //
  1103. // GetSortKey
  1104. //
  1105. // Gets the SortKey for the given string with the given options.
  1106. //
  1107. ////////////////////////////////////////////////////////////////////////
  1108. public virtual SortKey GetSortKey(string source, CompareOptions options)
  1109. {
  1110. if (GlobalizationMode.Invariant)
  1111. return InvariantCreateSortKey(source, options);
  1112. return CreateSortKey(source, options);
  1113. }
  1114. public virtual SortKey GetSortKey(string source)
  1115. {
  1116. if (GlobalizationMode.Invariant)
  1117. return InvariantCreateSortKey(source, CompareOptions.None);
  1118. return CreateSortKey(source, CompareOptions.None);
  1119. }
  1120. ////////////////////////////////////////////////////////////////////////
  1121. //
  1122. // Equals
  1123. //
  1124. // Implements Object.Equals(). Returns a boolean indicating whether
  1125. // or not object refers to the same CompareInfo as the current
  1126. // instance.
  1127. //
  1128. ////////////////////////////////////////////////////////////////////////
  1129. public override bool Equals(object value)
  1130. {
  1131. CompareInfo that = value as CompareInfo;
  1132. if (that != null)
  1133. {
  1134. return this.Name == that.Name;
  1135. }
  1136. return (false);
  1137. }
  1138. ////////////////////////////////////////////////////////////////////////
  1139. //
  1140. // GetHashCode
  1141. //
  1142. // Implements Object.GetHashCode(). Returns the hash code for the
  1143. // CompareInfo. The hash code is guaranteed to be the same for
  1144. // CompareInfo A and B where A.Equals(B) is true.
  1145. //
  1146. ////////////////////////////////////////////////////////////////////////
  1147. public override int GetHashCode()
  1148. {
  1149. return (this.Name.GetHashCode());
  1150. }
  1151. ////////////////////////////////////////////////////////////////////////
  1152. //
  1153. // GetHashCodeOfString
  1154. //
  1155. // This internal method allows a method that allows the equivalent of creating a Sortkey for a
  1156. // string from CompareInfo, and generate a hashcode value from it. It is not very convenient
  1157. // to use this method as is and it creates an unnecessary Sortkey object that will be GC'ed.
  1158. //
  1159. // The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both
  1160. // the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects
  1161. // treat the string the same way, this implementation will treat them differently (the same way that
  1162. // Sortkey does at the moment).
  1163. //
  1164. // This method will never be made public itself, but public consumers of it could be created, e.g.:
  1165. //
  1166. // string.GetHashCode(CultureInfo)
  1167. // string.GetHashCode(CompareInfo)
  1168. // string.GetHashCode(CultureInfo, CompareOptions)
  1169. // string.GetHashCode(CompareInfo, CompareOptions)
  1170. // etc.
  1171. //
  1172. // (the methods above that take a CultureInfo would use CultureInfo.CompareInfo)
  1173. //
  1174. ////////////////////////////////////////////////////////////////////////
  1175. internal int GetHashCodeOfString(string source, CompareOptions options)
  1176. {
  1177. //
  1178. // Parameter validation
  1179. //
  1180. if (null == source)
  1181. {
  1182. throw new ArgumentNullException(nameof(source));
  1183. }
  1184. if ((options & ValidHashCodeOfStringMaskOffFlags) == 0)
  1185. {
  1186. // No unsupported flags are set - continue on with the regular logic
  1187. if (GlobalizationMode.Invariant)
  1188. {
  1189. return ((options & CompareOptions.IgnoreCase) != 0) ? source.GetHashCodeOrdinalIgnoreCase() : source.GetHashCode();
  1190. }
  1191. return GetHashCodeOfStringCore(source, options);
  1192. }
  1193. else if (options == CompareOptions.Ordinal)
  1194. {
  1195. // We allow Ordinal in isolation
  1196. return source.GetHashCode();
  1197. }
  1198. else if (options == CompareOptions.OrdinalIgnoreCase)
  1199. {
  1200. // We allow OrdinalIgnoreCase in isolation
  1201. return source.GetHashCodeOrdinalIgnoreCase();
  1202. }
  1203. else
  1204. {
  1205. // Unsupported combination of flags specified
  1206. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  1207. }
  1208. }
  1209. public virtual int GetHashCode(string source, CompareOptions options)
  1210. {
  1211. // virtual method delegates to non-virtual method
  1212. return GetHashCodeOfString(source, options);
  1213. }
  1214. public int GetHashCode(ReadOnlySpan<char> source, CompareOptions options)
  1215. {
  1216. //
  1217. // Parameter validation
  1218. //
  1219. if ((options & ValidHashCodeOfStringMaskOffFlags) == 0)
  1220. {
  1221. // No unsupported flags are set - continue on with the regular logic
  1222. if (GlobalizationMode.Invariant)
  1223. {
  1224. return ((options & CompareOptions.IgnoreCase) != 0) ? string.GetHashCodeOrdinalIgnoreCase(source) : string.GetHashCode(source);
  1225. }
  1226. return GetHashCodeOfStringCore(source, options);
  1227. }
  1228. else if (options == CompareOptions.Ordinal)
  1229. {
  1230. // We allow Ordinal in isolation
  1231. return string.GetHashCode(source);
  1232. }
  1233. else if (options == CompareOptions.OrdinalIgnoreCase)
  1234. {
  1235. // We allow OrdinalIgnoreCase in isolation
  1236. return string.GetHashCodeOrdinalIgnoreCase(source);
  1237. }
  1238. else
  1239. {
  1240. // Unsupported combination of flags specified
  1241. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  1242. }
  1243. }
  1244. ////////////////////////////////////////////////////////////////////////
  1245. //
  1246. // ToString
  1247. //
  1248. // Implements Object.ToString(). Returns a string describing the
  1249. // CompareInfo.
  1250. //
  1251. ////////////////////////////////////////////////////////////////////////
  1252. public override string ToString()
  1253. {
  1254. return ("CompareInfo - " + this.Name);
  1255. }
  1256. public SortVersion Version
  1257. {
  1258. get
  1259. {
  1260. if (m_SortVersion == null)
  1261. {
  1262. if (GlobalizationMode.Invariant)
  1263. {
  1264. m_SortVersion = new SortVersion(0, CultureInfo.LOCALE_INVARIANT, new Guid(0, 0, 0, 0, 0, 0, 0,
  1265. (byte) (CultureInfo.LOCALE_INVARIANT >> 24),
  1266. (byte) ((CultureInfo.LOCALE_INVARIANT & 0x00FF0000) >> 16),
  1267. (byte) ((CultureInfo.LOCALE_INVARIANT & 0x0000FF00) >> 8),
  1268. (byte) (CultureInfo.LOCALE_INVARIANT & 0xFF)));
  1269. }
  1270. else
  1271. {
  1272. m_SortVersion = GetSortVersion();
  1273. }
  1274. }
  1275. return m_SortVersion;
  1276. }
  1277. }
  1278. public int LCID
  1279. {
  1280. get
  1281. {
  1282. return CultureInfo.GetCultureInfo(Name).LCID;
  1283. }
  1284. }
  1285. }
  1286. }