CompareInfo.cs 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544
  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. if (value is CompareInfo that)
  1132. {
  1133. return this.Name == that.Name;
  1134. }
  1135. return (false);
  1136. }
  1137. ////////////////////////////////////////////////////////////////////////
  1138. //
  1139. // GetHashCode
  1140. //
  1141. // Implements Object.GetHashCode(). Returns the hash code for the
  1142. // CompareInfo. The hash code is guaranteed to be the same for
  1143. // CompareInfo A and B where A.Equals(B) is true.
  1144. //
  1145. ////////////////////////////////////////////////////////////////////////
  1146. public override int GetHashCode()
  1147. {
  1148. return (this.Name.GetHashCode());
  1149. }
  1150. ////////////////////////////////////////////////////////////////////////
  1151. //
  1152. // GetHashCodeOfString
  1153. //
  1154. // This internal method allows a method that allows the equivalent of creating a Sortkey for a
  1155. // string from CompareInfo, and generate a hashcode value from it. It is not very convenient
  1156. // to use this method as is and it creates an unnecessary Sortkey object that will be GC'ed.
  1157. //
  1158. // The hash code is guaranteed to be the same for string A and B where A.Equals(B) is true and both
  1159. // the CompareInfo and the CompareOptions are the same. If two different CompareInfo objects
  1160. // treat the string the same way, this implementation will treat them differently (the same way that
  1161. // Sortkey does at the moment).
  1162. //
  1163. // This method will never be made public itself, but public consumers of it could be created, e.g.:
  1164. //
  1165. // string.GetHashCode(CultureInfo)
  1166. // string.GetHashCode(CompareInfo)
  1167. // string.GetHashCode(CultureInfo, CompareOptions)
  1168. // string.GetHashCode(CompareInfo, CompareOptions)
  1169. // etc.
  1170. //
  1171. // (the methods above that take a CultureInfo would use CultureInfo.CompareInfo)
  1172. //
  1173. ////////////////////////////////////////////////////////////////////////
  1174. internal int GetHashCodeOfString(string source, CompareOptions options)
  1175. {
  1176. //
  1177. // Parameter validation
  1178. //
  1179. if (null == source)
  1180. {
  1181. throw new ArgumentNullException(nameof(source));
  1182. }
  1183. if ((options & ValidHashCodeOfStringMaskOffFlags) == 0)
  1184. {
  1185. // No unsupported flags are set - continue on with the regular logic
  1186. if (GlobalizationMode.Invariant)
  1187. {
  1188. return ((options & CompareOptions.IgnoreCase) != 0) ? source.GetHashCodeOrdinalIgnoreCase() : source.GetHashCode();
  1189. }
  1190. return GetHashCodeOfStringCore(source, options);
  1191. }
  1192. else if (options == CompareOptions.Ordinal)
  1193. {
  1194. // We allow Ordinal in isolation
  1195. return source.GetHashCode();
  1196. }
  1197. else if (options == CompareOptions.OrdinalIgnoreCase)
  1198. {
  1199. // We allow OrdinalIgnoreCase in isolation
  1200. return source.GetHashCodeOrdinalIgnoreCase();
  1201. }
  1202. else
  1203. {
  1204. // Unsupported combination of flags specified
  1205. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  1206. }
  1207. }
  1208. public virtual int GetHashCode(string source, CompareOptions options)
  1209. {
  1210. // virtual method delegates to non-virtual method
  1211. return GetHashCodeOfString(source, options);
  1212. }
  1213. public int GetHashCode(ReadOnlySpan<char> source, CompareOptions options)
  1214. {
  1215. //
  1216. // Parameter validation
  1217. //
  1218. if ((options & ValidHashCodeOfStringMaskOffFlags) == 0)
  1219. {
  1220. // No unsupported flags are set - continue on with the regular logic
  1221. if (GlobalizationMode.Invariant)
  1222. {
  1223. return ((options & CompareOptions.IgnoreCase) != 0) ? string.GetHashCodeOrdinalIgnoreCase(source) : string.GetHashCode(source);
  1224. }
  1225. return GetHashCodeOfStringCore(source, options);
  1226. }
  1227. else if (options == CompareOptions.Ordinal)
  1228. {
  1229. // We allow Ordinal in isolation
  1230. return string.GetHashCode(source);
  1231. }
  1232. else if (options == CompareOptions.OrdinalIgnoreCase)
  1233. {
  1234. // We allow OrdinalIgnoreCase in isolation
  1235. return string.GetHashCodeOrdinalIgnoreCase(source);
  1236. }
  1237. else
  1238. {
  1239. // Unsupported combination of flags specified
  1240. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  1241. }
  1242. }
  1243. ////////////////////////////////////////////////////////////////////////
  1244. //
  1245. // ToString
  1246. //
  1247. // Implements Object.ToString(). Returns a string describing the
  1248. // CompareInfo.
  1249. //
  1250. ////////////////////////////////////////////////////////////////////////
  1251. public override string ToString()
  1252. {
  1253. return ("CompareInfo - " + this.Name);
  1254. }
  1255. public SortVersion Version
  1256. {
  1257. get
  1258. {
  1259. if (m_SortVersion == null)
  1260. {
  1261. if (GlobalizationMode.Invariant)
  1262. {
  1263. m_SortVersion = new SortVersion(0, CultureInfo.LOCALE_INVARIANT, new Guid(0, 0, 0, 0, 0, 0, 0,
  1264. (byte) (CultureInfo.LOCALE_INVARIANT >> 24),
  1265. (byte) ((CultureInfo.LOCALE_INVARIANT & 0x00FF0000) >> 16),
  1266. (byte) ((CultureInfo.LOCALE_INVARIANT & 0x0000FF00) >> 8),
  1267. (byte) (CultureInfo.LOCALE_INVARIANT & 0xFF)));
  1268. }
  1269. else
  1270. {
  1271. m_SortVersion = GetSortVersion();
  1272. }
  1273. }
  1274. return m_SortVersion;
  1275. }
  1276. }
  1277. public int LCID
  1278. {
  1279. get
  1280. {
  1281. return CultureInfo.GetCultureInfo(Name).LCID;
  1282. }
  1283. }
  1284. }
  1285. }