String.Manipulation.cs 67 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848
  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. using System.Buffers;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.Globalization;
  8. using System.Runtime.CompilerServices;
  9. using System.Runtime.InteropServices;
  10. using System.Text;
  11. using Internal.Runtime.CompilerServices;
  12. namespace System
  13. {
  14. public partial class String
  15. {
  16. private const int StackallocIntBufferSizeLimit = 128;
  17. private static unsafe void FillStringChecked(string dest, int destPos, string src)
  18. {
  19. Debug.Assert(dest != null);
  20. Debug.Assert(src != null);
  21. if (src.Length > dest.Length - destPos)
  22. {
  23. throw new IndexOutOfRangeException();
  24. }
  25. fixed (char* pDest = &dest._firstChar)
  26. fixed (char* pSrc = &src._firstChar)
  27. {
  28. wstrcpy(pDest + destPos, pSrc, src.Length);
  29. }
  30. }
  31. public static string Concat(object arg0)
  32. {
  33. if (arg0 == null)
  34. {
  35. return string.Empty;
  36. }
  37. return arg0.ToString();
  38. }
  39. public static string Concat(object arg0, object arg1)
  40. {
  41. if (arg0 == null)
  42. {
  43. arg0 = string.Empty;
  44. }
  45. if (arg1 == null)
  46. {
  47. arg1 = string.Empty;
  48. }
  49. return Concat(arg0.ToString(), arg1.ToString());
  50. }
  51. public static string Concat(object arg0, object arg1, object arg2)
  52. {
  53. if (arg0 == null)
  54. {
  55. arg0 = string.Empty;
  56. }
  57. if (arg1 == null)
  58. {
  59. arg1 = string.Empty;
  60. }
  61. if (arg2 == null)
  62. {
  63. arg2 = string.Empty;
  64. }
  65. return Concat(arg0.ToString(), arg1.ToString(), arg2.ToString());
  66. }
  67. public static string Concat(params object[] args)
  68. {
  69. if (args == null)
  70. {
  71. throw new ArgumentNullException(nameof(args));
  72. }
  73. if (args.Length <= 1)
  74. {
  75. return args.Length == 0 ?
  76. string.Empty :
  77. args[0]?.ToString() ?? string.Empty;
  78. }
  79. // We need to get an intermediary string array
  80. // to fill with each of the args' ToString(),
  81. // and then just concat that in one operation.
  82. // This way we avoid any intermediary string representations,
  83. // or buffer resizing if we use StringBuilder (although the
  84. // latter case is partially alleviated due to StringBuilder's
  85. // linked-list style implementation)
  86. var strings = new string[args.Length];
  87. int totalLength = 0;
  88. for (int i = 0; i < args.Length; i++)
  89. {
  90. object value = args[i];
  91. string toString = value?.ToString() ?? string.Empty; // We need to handle both the cases when value or value.ToString() is null
  92. strings[i] = toString;
  93. totalLength += toString.Length;
  94. if (totalLength < 0) // Check for a positive overflow
  95. {
  96. throw new OutOfMemoryException();
  97. }
  98. }
  99. // If all of the ToStrings are null/empty, just return string.Empty
  100. if (totalLength == 0)
  101. {
  102. return string.Empty;
  103. }
  104. string result = FastAllocateString(totalLength);
  105. int position = 0; // How many characters we've copied so far
  106. for (int i = 0; i < strings.Length; i++)
  107. {
  108. string s = strings[i];
  109. Debug.Assert(s != null);
  110. Debug.Assert(position <= totalLength - s.Length, "We didn't allocate enough space for the result string!");
  111. FillStringChecked(result, position, s);
  112. position += s.Length;
  113. }
  114. return result;
  115. }
  116. public static string Concat<T>(IEnumerable<T> values)
  117. {
  118. if (values == null)
  119. throw new ArgumentNullException(nameof(values));
  120. if (typeof(T) == typeof(char))
  121. {
  122. // Special-case T==char, as we can handle that case much more efficiently,
  123. // and string.Concat(IEnumerable<char>) can be used as an efficient
  124. // enumerable-based equivalent of new string(char[]).
  125. using (IEnumerator<char> en = Unsafe.As<IEnumerable<char>>(values).GetEnumerator())
  126. {
  127. if (!en.MoveNext())
  128. {
  129. // There weren't any chars. Return the empty string.
  130. return Empty;
  131. }
  132. char c = en.Current; // save the first char
  133. if (!en.MoveNext())
  134. {
  135. // There was only one char. Return a string from it directly.
  136. return CreateFromChar(c);
  137. }
  138. // Create the StringBuilder, add the chars we've already enumerated,
  139. // add the rest, and then get the resulting string.
  140. StringBuilder result = StringBuilderCache.Acquire();
  141. result.Append(c); // first value
  142. do
  143. {
  144. c = en.Current;
  145. result.Append(c);
  146. }
  147. while (en.MoveNext());
  148. return StringBuilderCache.GetStringAndRelease(result);
  149. }
  150. }
  151. else
  152. {
  153. using (IEnumerator<T> en = values.GetEnumerator())
  154. {
  155. if (!en.MoveNext())
  156. return string.Empty;
  157. // We called MoveNext once, so this will be the first item
  158. T currentValue = en.Current;
  159. // Call ToString before calling MoveNext again, since
  160. // we want to stay consistent with the below loop
  161. // Everything should be called in the order
  162. // MoveNext-Current-ToString, unless further optimizations
  163. // can be made, to avoid breaking changes
  164. string firstString = currentValue?.ToString();
  165. // If there's only 1 item, simply call ToString on that
  166. if (!en.MoveNext())
  167. {
  168. // We have to handle the case of either currentValue
  169. // or its ToString being null
  170. return firstString ?? string.Empty;
  171. }
  172. StringBuilder result = StringBuilderCache.Acquire();
  173. result.Append(firstString);
  174. do
  175. {
  176. currentValue = en.Current;
  177. if (currentValue != null)
  178. {
  179. result.Append(currentValue.ToString());
  180. }
  181. }
  182. while (en.MoveNext());
  183. return StringBuilderCache.GetStringAndRelease(result);
  184. }
  185. }
  186. }
  187. public static string Concat(IEnumerable<string> values)
  188. {
  189. if (values == null)
  190. throw new ArgumentNullException(nameof(values));
  191. using (IEnumerator<string> en = values.GetEnumerator())
  192. {
  193. if (!en.MoveNext())
  194. return string.Empty;
  195. string firstValue = en.Current;
  196. if (!en.MoveNext())
  197. {
  198. return firstValue ?? string.Empty;
  199. }
  200. StringBuilder result = StringBuilderCache.Acquire();
  201. result.Append(firstValue);
  202. do
  203. {
  204. result.Append(en.Current);
  205. }
  206. while (en.MoveNext());
  207. return StringBuilderCache.GetStringAndRelease(result);
  208. }
  209. }
  210. public static string Concat(string str0, string str1)
  211. {
  212. if (IsNullOrEmpty(str0))
  213. {
  214. if (IsNullOrEmpty(str1))
  215. {
  216. return string.Empty;
  217. }
  218. return str1;
  219. }
  220. if (IsNullOrEmpty(str1))
  221. {
  222. return str0;
  223. }
  224. int str0Length = str0.Length;
  225. string result = FastAllocateString(str0Length + str1.Length);
  226. FillStringChecked(result, 0, str0);
  227. FillStringChecked(result, str0Length, str1);
  228. return result;
  229. }
  230. public static string Concat(string str0, string str1, string str2)
  231. {
  232. if (IsNullOrEmpty(str0))
  233. {
  234. return Concat(str1, str2);
  235. }
  236. if (IsNullOrEmpty(str1))
  237. {
  238. return Concat(str0, str2);
  239. }
  240. if (IsNullOrEmpty(str2))
  241. {
  242. return Concat(str0, str1);
  243. }
  244. int totalLength = str0.Length + str1.Length + str2.Length;
  245. string result = FastAllocateString(totalLength);
  246. FillStringChecked(result, 0, str0);
  247. FillStringChecked(result, str0.Length, str1);
  248. FillStringChecked(result, str0.Length + str1.Length, str2);
  249. return result;
  250. }
  251. public static string Concat(string str0, string str1, string str2, string str3)
  252. {
  253. if (IsNullOrEmpty(str0))
  254. {
  255. return Concat(str1, str2, str3);
  256. }
  257. if (IsNullOrEmpty(str1))
  258. {
  259. return Concat(str0, str2, str3);
  260. }
  261. if (IsNullOrEmpty(str2))
  262. {
  263. return Concat(str0, str1, str3);
  264. }
  265. if (IsNullOrEmpty(str3))
  266. {
  267. return Concat(str0, str1, str2);
  268. }
  269. int totalLength = str0.Length + str1.Length + str2.Length + str3.Length;
  270. string result = FastAllocateString(totalLength);
  271. FillStringChecked(result, 0, str0);
  272. FillStringChecked(result, str0.Length, str1);
  273. FillStringChecked(result, str0.Length + str1.Length, str2);
  274. FillStringChecked(result, str0.Length + str1.Length + str2.Length, str3);
  275. return result;
  276. }
  277. public static string Concat(params string[] values)
  278. {
  279. if (values == null)
  280. throw new ArgumentNullException(nameof(values));
  281. if (values.Length <= 1)
  282. {
  283. return values.Length == 0 ?
  284. string.Empty :
  285. values[0] ?? string.Empty;
  286. }
  287. // It's possible that the input values array could be changed concurrently on another
  288. // thread, such that we can't trust that each read of values[i] will be equivalent.
  289. // Worst case, we can make a defensive copy of the array and use that, but we first
  290. // optimistically try the allocation and copies assuming that the array isn't changing,
  291. // which represents the 99.999% case, in particular since string.Concat is used for
  292. // string concatenation by the languages, with the input array being a params array.
  293. // Sum the lengths of all input strings
  294. long totalLengthLong = 0;
  295. for (int i = 0; i < values.Length; i++)
  296. {
  297. string value = values[i];
  298. if (value != null)
  299. {
  300. totalLengthLong += value.Length;
  301. }
  302. }
  303. // If it's too long, fail, or if it's empty, return an empty string.
  304. if (totalLengthLong > int.MaxValue)
  305. {
  306. throw new OutOfMemoryException();
  307. }
  308. int totalLength = (int)totalLengthLong;
  309. if (totalLength == 0)
  310. {
  311. return string.Empty;
  312. }
  313. // Allocate a new string and copy each input string into it
  314. string result = FastAllocateString(totalLength);
  315. int copiedLength = 0;
  316. for (int i = 0; i < values.Length; i++)
  317. {
  318. string value = values[i];
  319. if (!string.IsNullOrEmpty(value))
  320. {
  321. int valueLen = value.Length;
  322. if (valueLen > totalLength - copiedLength)
  323. {
  324. copiedLength = -1;
  325. break;
  326. }
  327. FillStringChecked(result, copiedLength, value);
  328. copiedLength += valueLen;
  329. }
  330. }
  331. // If we copied exactly the right amount, return the new string. Otherwise,
  332. // something changed concurrently to mutate the input array: fall back to
  333. // doing the concatenation again, but this time with a defensive copy. This
  334. // fall back should be extremely rare.
  335. return copiedLength == totalLength ? result : Concat((string[])values.Clone());
  336. }
  337. public static string Format(string format, object arg0)
  338. {
  339. return FormatHelper(null, format, new ParamsArray(arg0));
  340. }
  341. public static string Format(string format, object arg0, object arg1)
  342. {
  343. return FormatHelper(null, format, new ParamsArray(arg0, arg1));
  344. }
  345. public static string Format(string format, object arg0, object arg1, object arg2)
  346. {
  347. return FormatHelper(null, format, new ParamsArray(arg0, arg1, arg2));
  348. }
  349. public static string Format(string format, params object[] args)
  350. {
  351. if (args == null)
  352. {
  353. // To preserve the original exception behavior, throw an exception about format if both
  354. // args and format are null. The actual null check for format is in FormatHelper.
  355. throw new ArgumentNullException((format == null) ? nameof(format) : nameof(args));
  356. }
  357. return FormatHelper(null, format, new ParamsArray(args));
  358. }
  359. public static string Format(IFormatProvider provider, string format, object arg0)
  360. {
  361. return FormatHelper(provider, format, new ParamsArray(arg0));
  362. }
  363. public static string Format(IFormatProvider provider, string format, object arg0, object arg1)
  364. {
  365. return FormatHelper(provider, format, new ParamsArray(arg0, arg1));
  366. }
  367. public static string Format(IFormatProvider provider, string format, object arg0, object arg1, object arg2)
  368. {
  369. return FormatHelper(provider, format, new ParamsArray(arg0, arg1, arg2));
  370. }
  371. public static string Format(IFormatProvider provider, string format, params object[] args)
  372. {
  373. if (args == null)
  374. {
  375. // To preserve the original exception behavior, throw an exception about format if both
  376. // args and format are null. The actual null check for format is in FormatHelper.
  377. throw new ArgumentNullException((format == null) ? nameof(format) : nameof(args));
  378. }
  379. return FormatHelper(provider, format, new ParamsArray(args));
  380. }
  381. private static string FormatHelper(IFormatProvider provider, string format, ParamsArray args)
  382. {
  383. if (format == null)
  384. throw new ArgumentNullException(nameof(format));
  385. return StringBuilderCache.GetStringAndRelease(
  386. StringBuilderCache
  387. .Acquire(format.Length + args.Length * 8)
  388. .AppendFormatHelper(provider, format, args));
  389. }
  390. public string Insert(int startIndex, string value)
  391. {
  392. if (value == null)
  393. throw new ArgumentNullException(nameof(value));
  394. if (startIndex < 0 || startIndex > this.Length)
  395. throw new ArgumentOutOfRangeException(nameof(startIndex));
  396. int oldLength = Length;
  397. int insertLength = value.Length;
  398. if (oldLength == 0)
  399. return value;
  400. if (insertLength == 0)
  401. return this;
  402. // In case this computation overflows, newLength will be negative and FastAllocateString throws OutOfMemoryException
  403. int newLength = oldLength + insertLength;
  404. string result = FastAllocateString(newLength);
  405. unsafe
  406. {
  407. fixed (char* srcThis = &_firstChar)
  408. {
  409. fixed (char* srcInsert = &value._firstChar)
  410. {
  411. fixed (char* dst = &result._firstChar)
  412. {
  413. wstrcpy(dst, srcThis, startIndex);
  414. wstrcpy(dst + startIndex, srcInsert, insertLength);
  415. wstrcpy(dst + startIndex + insertLength, srcThis + startIndex, oldLength - startIndex);
  416. }
  417. }
  418. }
  419. }
  420. return result;
  421. }
  422. public static string Join(char separator, params string[] value)
  423. {
  424. if (value == null)
  425. {
  426. throw new ArgumentNullException(nameof(value));
  427. }
  428. return Join(separator, value, 0, value.Length);
  429. }
  430. public static unsafe string Join(char separator, params object[] values)
  431. {
  432. // Defer argument validation to the internal function
  433. return JoinCore(&separator, 1, values);
  434. }
  435. public static unsafe string Join<T>(char separator, IEnumerable<T> values)
  436. {
  437. // Defer argument validation to the internal function
  438. return JoinCore(&separator, 1, values);
  439. }
  440. public static unsafe string Join(char separator, string[] value, int startIndex, int count)
  441. {
  442. // Defer argument validation to the internal function
  443. return JoinCore(&separator, 1, value, startIndex, count);
  444. }
  445. // Joins an array of strings together as one string with a separator between each original string.
  446. //
  447. public static string Join(string separator, params string[] value)
  448. {
  449. if (value == null)
  450. {
  451. throw new ArgumentNullException(nameof(value));
  452. }
  453. return Join(separator, value, 0, value.Length);
  454. }
  455. public static unsafe string Join(string separator, params object[] values)
  456. {
  457. separator = separator ?? string.Empty;
  458. fixed (char* pSeparator = &separator._firstChar)
  459. {
  460. // Defer argument validation to the internal function
  461. return JoinCore(pSeparator, separator.Length, values);
  462. }
  463. }
  464. public static unsafe string Join<T>(string separator, IEnumerable<T> values)
  465. {
  466. separator = separator ?? string.Empty;
  467. fixed (char* pSeparator = &separator._firstChar)
  468. {
  469. // Defer argument validation to the internal function
  470. return JoinCore(pSeparator, separator.Length, values);
  471. }
  472. }
  473. public static string Join(string separator, IEnumerable<string> values)
  474. {
  475. if (values == null)
  476. {
  477. throw new ArgumentNullException(nameof(values));
  478. }
  479. using (IEnumerator<string> en = values.GetEnumerator())
  480. {
  481. if (!en.MoveNext())
  482. {
  483. return string.Empty;
  484. }
  485. string firstValue = en.Current;
  486. if (!en.MoveNext())
  487. {
  488. // Only one value available
  489. return firstValue ?? string.Empty;
  490. }
  491. // Null separator and values are handled by the StringBuilder
  492. StringBuilder result = StringBuilderCache.Acquire();
  493. result.Append(firstValue);
  494. do
  495. {
  496. result.Append(separator);
  497. result.Append(en.Current);
  498. }
  499. while (en.MoveNext());
  500. return StringBuilderCache.GetStringAndRelease(result);
  501. }
  502. }
  503. // Joins an array of strings together as one string with a separator between each original string.
  504. //
  505. public static unsafe string Join(string separator, string[] value, int startIndex, int count)
  506. {
  507. separator = separator ?? string.Empty;
  508. fixed (char* pSeparator = &separator._firstChar)
  509. {
  510. // Defer argument validation to the internal function
  511. return JoinCore(pSeparator, separator.Length, value, startIndex, count);
  512. }
  513. }
  514. private static unsafe string JoinCore(char* separator, int separatorLength, object[] values)
  515. {
  516. if (values == null)
  517. {
  518. throw new ArgumentNullException(nameof(values));
  519. }
  520. if (values.Length == 0)
  521. {
  522. return string.Empty;
  523. }
  524. string firstString = values[0]?.ToString();
  525. if (values.Length == 1)
  526. {
  527. return firstString ?? string.Empty;
  528. }
  529. StringBuilder result = StringBuilderCache.Acquire();
  530. result.Append(firstString);
  531. for (int i = 1; i < values.Length; i++)
  532. {
  533. result.Append(separator, separatorLength);
  534. object value = values[i];
  535. if (value != null)
  536. {
  537. result.Append(value.ToString());
  538. }
  539. }
  540. return StringBuilderCache.GetStringAndRelease(result);
  541. }
  542. private static unsafe string JoinCore<T>(char* separator, int separatorLength, IEnumerable<T> values)
  543. {
  544. if (values == null)
  545. {
  546. throw new ArgumentNullException(nameof(values));
  547. }
  548. using (IEnumerator<T> en = values.GetEnumerator())
  549. {
  550. if (!en.MoveNext())
  551. {
  552. return string.Empty;
  553. }
  554. // We called MoveNext once, so this will be the first item
  555. T currentValue = en.Current;
  556. // Call ToString before calling MoveNext again, since
  557. // we want to stay consistent with the below loop
  558. // Everything should be called in the order
  559. // MoveNext-Current-ToString, unless further optimizations
  560. // can be made, to avoid breaking changes
  561. string firstString = currentValue?.ToString();
  562. // If there's only 1 item, simply call ToString on that
  563. if (!en.MoveNext())
  564. {
  565. // We have to handle the case of either currentValue
  566. // or its ToString being null
  567. return firstString ?? string.Empty;
  568. }
  569. StringBuilder result = StringBuilderCache.Acquire();
  570. result.Append(firstString);
  571. do
  572. {
  573. currentValue = en.Current;
  574. result.Append(separator, separatorLength);
  575. if (currentValue != null)
  576. {
  577. result.Append(currentValue.ToString());
  578. }
  579. }
  580. while (en.MoveNext());
  581. return StringBuilderCache.GetStringAndRelease(result);
  582. }
  583. }
  584. private static unsafe string JoinCore(char* separator, int separatorLength, string[] value, int startIndex, int count)
  585. {
  586. // If the separator is null, it is converted to an empty string before entering this function.
  587. // Even for empty strings, fixed should never return null (it should return a pointer to a null char).
  588. Debug.Assert(separator != null);
  589. Debug.Assert(separatorLength >= 0);
  590. if (value == null)
  591. {
  592. throw new ArgumentNullException(nameof(value));
  593. }
  594. if (startIndex < 0)
  595. {
  596. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
  597. }
  598. if (count < 0)
  599. {
  600. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NegativeCount);
  601. }
  602. if (startIndex > value.Length - count)
  603. {
  604. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_IndexCountBuffer);
  605. }
  606. if (count <= 1)
  607. {
  608. return count == 0 ?
  609. string.Empty :
  610. value[startIndex] ?? string.Empty;
  611. }
  612. long totalSeparatorsLength = (long)(count - 1) * separatorLength;
  613. if (totalSeparatorsLength > int.MaxValue)
  614. {
  615. throw new OutOfMemoryException();
  616. }
  617. int totalLength = (int)totalSeparatorsLength;
  618. // Calculate the length of the resultant string so we know how much space to allocate.
  619. for (int i = startIndex, end = startIndex + count; i < end; i++)
  620. {
  621. string currentValue = value[i];
  622. if (currentValue != null)
  623. {
  624. totalLength += currentValue.Length;
  625. if (totalLength < 0) // Check for overflow
  626. {
  627. throw new OutOfMemoryException();
  628. }
  629. }
  630. }
  631. // Copy each of the strings into the resultant buffer, interleaving with the separator.
  632. string result = FastAllocateString(totalLength);
  633. int copiedLength = 0;
  634. for (int i = startIndex, end = startIndex + count; i < end; i++)
  635. {
  636. // It's possible that another thread may have mutated the input array
  637. // such that our second read of an index will not be the same string
  638. // we got during the first read.
  639. // We range check again to avoid buffer overflows if this happens.
  640. string currentValue = value[i];
  641. if (currentValue != null)
  642. {
  643. int valueLen = currentValue.Length;
  644. if (valueLen > totalLength - copiedLength)
  645. {
  646. copiedLength = -1;
  647. break;
  648. }
  649. // Fill in the value.
  650. FillStringChecked(result, copiedLength, currentValue);
  651. copiedLength += valueLen;
  652. }
  653. if (i < end - 1)
  654. {
  655. // Fill in the separator.
  656. fixed (char* pResult = &result._firstChar)
  657. {
  658. // If we are called from the char-based overload, we will not
  659. // want to call MemoryCopy each time we fill in the separator. So
  660. // specialize for 1-length separators.
  661. if (separatorLength == 1)
  662. {
  663. pResult[copiedLength] = *separator;
  664. }
  665. else
  666. {
  667. wstrcpy(pResult + copiedLength, separator, separatorLength);
  668. }
  669. }
  670. copiedLength += separatorLength;
  671. }
  672. }
  673. // If we copied exactly the right amount, return the new string. Otherwise,
  674. // something changed concurrently to mutate the input array: fall back to
  675. // doing the concatenation again, but this time with a defensive copy. This
  676. // fall back should be extremely rare.
  677. return copiedLength == totalLength ?
  678. result :
  679. JoinCore(separator, separatorLength, (string[])value.Clone(), startIndex, count);
  680. }
  681. public string PadLeft(int totalWidth) => PadLeft(totalWidth, ' ');
  682. public string PadLeft(int totalWidth, char paddingChar)
  683. {
  684. if (totalWidth < 0)
  685. throw new ArgumentOutOfRangeException(nameof(totalWidth), SR.ArgumentOutOfRange_NeedNonNegNum);
  686. int oldLength = Length;
  687. int count = totalWidth - oldLength;
  688. if (count <= 0)
  689. return this;
  690. string result = FastAllocateString(totalWidth);
  691. unsafe
  692. {
  693. fixed (char* dst = &result._firstChar)
  694. {
  695. for (int i = 0; i < count; i++)
  696. dst[i] = paddingChar;
  697. fixed (char* src = &_firstChar)
  698. {
  699. wstrcpy(dst + count, src, oldLength);
  700. }
  701. }
  702. }
  703. return result;
  704. }
  705. public string PadRight(int totalWidth) => PadRight(totalWidth, ' ');
  706. public string PadRight(int totalWidth, char paddingChar)
  707. {
  708. if (totalWidth < 0)
  709. throw new ArgumentOutOfRangeException(nameof(totalWidth), SR.ArgumentOutOfRange_NeedNonNegNum);
  710. int oldLength = Length;
  711. int count = totalWidth - oldLength;
  712. if (count <= 0)
  713. return this;
  714. string result = FastAllocateString(totalWidth);
  715. unsafe
  716. {
  717. fixed (char* dst = &result._firstChar)
  718. {
  719. fixed (char* src = &_firstChar)
  720. {
  721. wstrcpy(dst, src, oldLength);
  722. }
  723. for (int i = 0; i < count; i++)
  724. dst[oldLength + i] = paddingChar;
  725. }
  726. }
  727. return result;
  728. }
  729. public string Remove(int startIndex, int count)
  730. {
  731. if (startIndex < 0)
  732. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
  733. if (count < 0)
  734. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NegativeCount);
  735. int oldLength = this.Length;
  736. if (count > oldLength - startIndex)
  737. throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_IndexCount);
  738. if (count == 0)
  739. return this;
  740. int newLength = oldLength - count;
  741. if (newLength == 0)
  742. return string.Empty;
  743. string result = FastAllocateString(newLength);
  744. unsafe
  745. {
  746. fixed (char* src = &_firstChar)
  747. {
  748. fixed (char* dst = &result._firstChar)
  749. {
  750. wstrcpy(dst, src, startIndex);
  751. wstrcpy(dst + startIndex, src + startIndex + count, newLength - startIndex);
  752. }
  753. }
  754. }
  755. return result;
  756. }
  757. // a remove that just takes a startindex.
  758. public string Remove(int startIndex)
  759. {
  760. if (startIndex < 0)
  761. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
  762. if (startIndex >= Length)
  763. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndexLessThanLength);
  764. return Substring(0, startIndex);
  765. }
  766. public string Replace(string oldValue, string newValue, bool ignoreCase, CultureInfo culture)
  767. {
  768. return ReplaceCore(oldValue, newValue, culture, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
  769. }
  770. public string Replace(string oldValue, string newValue, StringComparison comparisonType)
  771. {
  772. switch (comparisonType)
  773. {
  774. case StringComparison.CurrentCulture:
  775. case StringComparison.CurrentCultureIgnoreCase:
  776. return ReplaceCore(oldValue, newValue, CultureInfo.CurrentCulture, GetCaseCompareOfComparisonCulture(comparisonType));
  777. case StringComparison.InvariantCulture:
  778. case StringComparison.InvariantCultureIgnoreCase:
  779. return ReplaceCore(oldValue, newValue, CultureInfo.InvariantCulture, GetCaseCompareOfComparisonCulture(comparisonType));
  780. case StringComparison.Ordinal:
  781. return Replace(oldValue, newValue);
  782. case StringComparison.OrdinalIgnoreCase:
  783. return ReplaceCore(oldValue, newValue, CultureInfo.InvariantCulture, CompareOptions.OrdinalIgnoreCase);
  784. default:
  785. throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
  786. }
  787. }
  788. private unsafe string ReplaceCore(string oldValue, string newValue, CultureInfo culture, CompareOptions options)
  789. {
  790. if (oldValue == null)
  791. throw new ArgumentNullException(nameof(oldValue));
  792. if (oldValue.Length == 0)
  793. throw new ArgumentException(SR.Argument_StringZeroLength, nameof(oldValue));
  794. // If they asked to replace oldValue with a null, replace all occurrences
  795. // with the empty string.
  796. if (newValue == null)
  797. newValue = string.Empty;
  798. CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
  799. StringBuilder result = StringBuilderCache.Acquire();
  800. int startIndex = 0;
  801. int index = 0;
  802. int matchLength = 0;
  803. bool hasDoneAnyReplacements = false;
  804. CompareInfo ci = referenceCulture.CompareInfo;
  805. do
  806. {
  807. index = ci.IndexOf(this, oldValue, startIndex, this.Length - startIndex, options, &matchLength);
  808. if (index >= 0)
  809. {
  810. // append the unmodified portion of string
  811. result.Append(this, startIndex, index - startIndex);
  812. // append the replacement
  813. result.Append(newValue);
  814. startIndex = index + matchLength;
  815. hasDoneAnyReplacements = true;
  816. }
  817. else if (!hasDoneAnyReplacements)
  818. {
  819. // small optimization,
  820. // if we have not done any replacements,
  821. // we will return the original string
  822. StringBuilderCache.Release(result);
  823. return this;
  824. }
  825. else
  826. {
  827. result.Append(this, startIndex, this.Length - startIndex);
  828. }
  829. } while (index >= 0);
  830. return StringBuilderCache.GetStringAndRelease(result);
  831. }
  832. // Replaces all instances of oldChar with newChar.
  833. //
  834. public string Replace(char oldChar, char newChar)
  835. {
  836. if (oldChar == newChar)
  837. return this;
  838. unsafe
  839. {
  840. int remainingLength = Length;
  841. fixed (char* pChars = &_firstChar)
  842. {
  843. char* pSrc = pChars;
  844. while (remainingLength > 0)
  845. {
  846. if (*pSrc == oldChar)
  847. {
  848. break;
  849. }
  850. remainingLength--;
  851. pSrc++;
  852. }
  853. }
  854. if (remainingLength == 0)
  855. return this;
  856. string result = FastAllocateString(Length);
  857. fixed (char* pChars = &_firstChar)
  858. {
  859. fixed (char* pResult = &result._firstChar)
  860. {
  861. int copyLength = Length - remainingLength;
  862. //Copy the characters already proven not to match.
  863. if (copyLength > 0)
  864. {
  865. wstrcpy(pResult, pChars, copyLength);
  866. }
  867. //Copy the remaining characters, doing the replacement as we go.
  868. char* pSrc = pChars + copyLength;
  869. char* pDst = pResult + copyLength;
  870. do
  871. {
  872. char currentChar = *pSrc;
  873. if (currentChar == oldChar)
  874. currentChar = newChar;
  875. *pDst = currentChar;
  876. remainingLength--;
  877. pSrc++;
  878. pDst++;
  879. } while (remainingLength > 0);
  880. }
  881. }
  882. return result;
  883. }
  884. }
  885. public string Replace(string oldValue, string newValue)
  886. {
  887. if (oldValue == null)
  888. throw new ArgumentNullException(nameof(oldValue));
  889. if (oldValue.Length == 0)
  890. throw new ArgumentException(SR.Argument_StringZeroLength, nameof(oldValue));
  891. // Api behavior: if newValue is null, instances of oldValue are to be removed.
  892. if (newValue == null)
  893. newValue = string.Empty;
  894. Span<int> initialSpan = stackalloc int[StackallocIntBufferSizeLimit];
  895. var replacementIndices = new ValueListBuilder<int>(initialSpan);
  896. unsafe
  897. {
  898. fixed (char* pThis = &_firstChar)
  899. {
  900. int matchIdx = 0;
  901. int lastPossibleMatchIdx = this.Length - oldValue.Length;
  902. while (matchIdx <= lastPossibleMatchIdx)
  903. {
  904. char* pMatch = pThis + matchIdx;
  905. for (int probeIdx = 0; probeIdx < oldValue.Length; probeIdx++)
  906. {
  907. if (pMatch[probeIdx] != oldValue[probeIdx])
  908. {
  909. goto Next;
  910. }
  911. }
  912. // Found a match for the string. Record the location of the match and skip over the "oldValue."
  913. replacementIndices.Append(matchIdx);
  914. matchIdx += oldValue.Length;
  915. continue;
  916. Next:
  917. matchIdx++;
  918. }
  919. }
  920. }
  921. if (replacementIndices.Length == 0)
  922. return this;
  923. // String allocation and copying is in separate method to make this method faster for the case where
  924. // nothing needs replacing.
  925. string dst = ReplaceHelper(oldValue.Length, newValue, replacementIndices.AsSpan());
  926. replacementIndices.Dispose();
  927. return dst;
  928. }
  929. private string ReplaceHelper(int oldValueLength, string newValue, ReadOnlySpan<int> indices)
  930. {
  931. Debug.Assert(indices.Length > 0);
  932. long dstLength = this.Length + ((long)(newValue.Length - oldValueLength)) * indices.Length;
  933. if (dstLength > int.MaxValue)
  934. throw new OutOfMemoryException();
  935. string dst = FastAllocateString((int)dstLength);
  936. Span<char> dstSpan = new Span<char>(ref dst.GetRawStringData(), dst.Length);
  937. int thisIdx = 0;
  938. int dstIdx = 0;
  939. for (int r = 0; r < indices.Length; r++)
  940. {
  941. int replacementIdx = indices[r];
  942. // Copy over the non-matching portion of the original that precedes this occurrence of oldValue.
  943. int count = replacementIdx - thisIdx;
  944. if (count != 0)
  945. {
  946. this.AsSpan(thisIdx, count).CopyTo(dstSpan.Slice(dstIdx));
  947. dstIdx += count;
  948. }
  949. thisIdx = replacementIdx + oldValueLength;
  950. // Copy over newValue to replace the oldValue.
  951. newValue.AsSpan().CopyTo(dstSpan.Slice(dstIdx));
  952. dstIdx += newValue.Length;
  953. }
  954. // Copy over the final non-matching portion at the end of the string.
  955. Debug.Assert(this.Length - thisIdx == dstSpan.Length - dstIdx);
  956. this.AsSpan(thisIdx).CopyTo(dstSpan.Slice(dstIdx));
  957. return dst;
  958. }
  959. public string[] Split(char separator, StringSplitOptions options = StringSplitOptions.None)
  960. {
  961. return SplitInternal(new ReadOnlySpan<char>(ref separator, 1), int.MaxValue, options);
  962. }
  963. public string[] Split(char separator, int count, StringSplitOptions options = StringSplitOptions.None)
  964. {
  965. return SplitInternal(new ReadOnlySpan<char>(ref separator, 1), count, options);
  966. }
  967. // Creates an array of strings by splitting this string at each
  968. // occurrence of a separator. The separator is searched for, and if found,
  969. // the substring preceding the occurrence is stored as the first element in
  970. // the array of strings. We then continue in this manner by searching
  971. // the substring that follows the occurrence. On the other hand, if the separator
  972. // is not found, the array of strings will contain this instance as its only element.
  973. // If the separator is null
  974. // whitespace (i.e., Character.IsWhitespace) is used as the separator.
  975. //
  976. public string[] Split(params char[] separator)
  977. {
  978. return SplitInternal(separator, int.MaxValue, StringSplitOptions.None);
  979. }
  980. // Creates an array of strings by splitting this string at each
  981. // occurrence of a separator. The separator is searched for, and if found,
  982. // the substring preceding the occurrence is stored as the first element in
  983. // the array of strings. We then continue in this manner by searching
  984. // the substring that follows the occurrence. On the other hand, if the separator
  985. // is not found, the array of strings will contain this instance as its only element.
  986. // If the separator is the empty string (i.e., string.Empty), then
  987. // whitespace (i.e., Character.IsWhitespace) is used as the separator.
  988. // If there are more than count different strings, the last n-(count-1)
  989. // elements are concatenated and added as the last string.
  990. //
  991. public string[] Split(char[] separator, int count)
  992. {
  993. return SplitInternal(separator, count, StringSplitOptions.None);
  994. }
  995. public string[] Split(char[] separator, StringSplitOptions options)
  996. {
  997. return SplitInternal(separator, int.MaxValue, options);
  998. }
  999. public string[] Split(char[] separator, int count, StringSplitOptions options)
  1000. {
  1001. return SplitInternal(separator, count, options);
  1002. }
  1003. private string[] SplitInternal(ReadOnlySpan<char> separators, int count, StringSplitOptions options)
  1004. {
  1005. if (count < 0)
  1006. throw new ArgumentOutOfRangeException(nameof(count),
  1007. SR.ArgumentOutOfRange_NegativeCount);
  1008. if (options < StringSplitOptions.None || options > StringSplitOptions.RemoveEmptyEntries)
  1009. throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, options));
  1010. bool omitEmptyEntries = (options == StringSplitOptions.RemoveEmptyEntries);
  1011. if ((count == 0) || (omitEmptyEntries && Length == 0))
  1012. {
  1013. return Array.Empty<string>();
  1014. }
  1015. if (count == 1)
  1016. {
  1017. return new string[] { this };
  1018. }
  1019. Span<int> initialSpan = stackalloc int[StackallocIntBufferSizeLimit];
  1020. var sepListBuilder = new ValueListBuilder<int>(initialSpan);
  1021. MakeSeparatorList(separators, ref sepListBuilder);
  1022. ReadOnlySpan<int> sepList = sepListBuilder.AsSpan();
  1023. // Handle the special case of no replaces.
  1024. if (sepList.Length == 0)
  1025. {
  1026. return new string[] { this };
  1027. }
  1028. string[] result = omitEmptyEntries
  1029. ? SplitOmitEmptyEntries(sepList, default, 1, count)
  1030. : SplitKeepEmptyEntries(sepList, default, 1, count);
  1031. sepListBuilder.Dispose();
  1032. return result;
  1033. }
  1034. public string[] Split(string separator, StringSplitOptions options = StringSplitOptions.None)
  1035. {
  1036. return SplitInternal(separator ?? string.Empty, null, int.MaxValue, options);
  1037. }
  1038. public string[] Split(string separator, int count, StringSplitOptions options = StringSplitOptions.None)
  1039. {
  1040. return SplitInternal(separator ?? string.Empty, null, count, options);
  1041. }
  1042. public string[] Split(string[] separator, StringSplitOptions options)
  1043. {
  1044. return SplitInternal(null, separator, int.MaxValue, options);
  1045. }
  1046. public string[] Split(string[] separator, int count, StringSplitOptions options)
  1047. {
  1048. return SplitInternal(null, separator, count, options);
  1049. }
  1050. private string[] SplitInternal(string separator, string[] separators, int count, StringSplitOptions options)
  1051. {
  1052. if (count < 0)
  1053. {
  1054. throw new ArgumentOutOfRangeException(nameof(count),
  1055. SR.ArgumentOutOfRange_NegativeCount);
  1056. }
  1057. if (options < StringSplitOptions.None || options > StringSplitOptions.RemoveEmptyEntries)
  1058. {
  1059. throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, (int)options));
  1060. }
  1061. bool omitEmptyEntries = (options == StringSplitOptions.RemoveEmptyEntries);
  1062. bool singleSeparator = separator != null;
  1063. if (!singleSeparator && (separators == null || separators.Length == 0))
  1064. {
  1065. return SplitInternal((char[])null, count, options);
  1066. }
  1067. if ((count == 0) || (omitEmptyEntries && Length == 0))
  1068. {
  1069. return Array.Empty<string>();
  1070. }
  1071. if (count == 1 || (singleSeparator && separator.Length == 0))
  1072. {
  1073. return new string[] { this };
  1074. }
  1075. if (singleSeparator)
  1076. {
  1077. return SplitInternal(separator, count, options);
  1078. }
  1079. Span<int> sepListInitialSpan = stackalloc int[StackallocIntBufferSizeLimit];
  1080. var sepListBuilder = new ValueListBuilder<int>(sepListInitialSpan);
  1081. Span<int> lengthListInitialSpan = stackalloc int[StackallocIntBufferSizeLimit];
  1082. var lengthListBuilder = new ValueListBuilder<int>(lengthListInitialSpan);
  1083. MakeSeparatorList(separators, ref sepListBuilder, ref lengthListBuilder);
  1084. ReadOnlySpan<int> sepList = sepListBuilder.AsSpan();
  1085. ReadOnlySpan<int> lengthList = lengthListBuilder.AsSpan();
  1086. // Handle the special case of no replaces.
  1087. if (sepList.Length == 0)
  1088. {
  1089. return new string[] { this };
  1090. }
  1091. string[] result = omitEmptyEntries
  1092. ? SplitOmitEmptyEntries(sepList, lengthList, 0, count)
  1093. : SplitKeepEmptyEntries(sepList, lengthList, 0, count);
  1094. sepListBuilder.Dispose();
  1095. lengthListBuilder.Dispose();
  1096. return result;
  1097. }
  1098. private string[] SplitInternal(string separator, int count, StringSplitOptions options)
  1099. {
  1100. Span<int> sepListInitialSpan = stackalloc int[StackallocIntBufferSizeLimit];
  1101. var sepListBuilder = new ValueListBuilder<int>(sepListInitialSpan);
  1102. MakeSeparatorList(separator, ref sepListBuilder);
  1103. ReadOnlySpan<int> sepList = sepListBuilder.AsSpan();
  1104. if (sepList.Length == 0)
  1105. {
  1106. // there are no separators so sepListBuilder did not rent an array from pool and there is no need to dispose it
  1107. return new string[] { this };
  1108. }
  1109. string[] result = options == StringSplitOptions.RemoveEmptyEntries
  1110. ? SplitOmitEmptyEntries(sepList, default, separator.Length, count)
  1111. : SplitKeepEmptyEntries(sepList, default, separator.Length, count);
  1112. sepListBuilder.Dispose();
  1113. return result;
  1114. }
  1115. private string[] SplitKeepEmptyEntries(ReadOnlySpan<int> sepList, ReadOnlySpan<int> lengthList, int defaultLength, int count)
  1116. {
  1117. Debug.Assert(count >= 2);
  1118. int currIndex = 0;
  1119. int arrIndex = 0;
  1120. count--;
  1121. int numActualReplaces = (sepList.Length < count) ? sepList.Length : count;
  1122. //Allocate space for the new array.
  1123. //+1 for the string from the end of the last replace to the end of the string.
  1124. string[] splitStrings = new string[numActualReplaces + 1];
  1125. for (int i = 0; i < numActualReplaces && currIndex < Length; i++)
  1126. {
  1127. splitStrings[arrIndex++] = Substring(currIndex, sepList[i] - currIndex);
  1128. currIndex = sepList[i] + (lengthList.IsEmpty ? defaultLength : lengthList[i]);
  1129. }
  1130. //Handle the last string at the end of the array if there is one.
  1131. if (currIndex < Length && numActualReplaces >= 0)
  1132. {
  1133. splitStrings[arrIndex] = Substring(currIndex);
  1134. }
  1135. else if (arrIndex == numActualReplaces)
  1136. {
  1137. //We had a separator character at the end of a string. Rather than just allowing
  1138. //a null character, we'll replace the last element in the array with an empty string.
  1139. splitStrings[arrIndex] = string.Empty;
  1140. }
  1141. return splitStrings;
  1142. }
  1143. // This function will not keep the Empty string
  1144. private string[] SplitOmitEmptyEntries(ReadOnlySpan<int> sepList, ReadOnlySpan<int> lengthList, int defaultLength, int count)
  1145. {
  1146. Debug.Assert(count >= 2);
  1147. int numReplaces = sepList.Length;
  1148. // Allocate array to hold items. This array may not be
  1149. // filled completely in this function, we will create a
  1150. // new array and copy string references to that new array.
  1151. int maxItems = (numReplaces < count) ? (numReplaces + 1) : count;
  1152. string[] splitStrings = new string[maxItems];
  1153. int currIndex = 0;
  1154. int arrIndex = 0;
  1155. for (int i = 0; i < numReplaces && currIndex < Length; i++)
  1156. {
  1157. if (sepList[i] - currIndex > 0)
  1158. {
  1159. splitStrings[arrIndex++] = Substring(currIndex, sepList[i] - currIndex);
  1160. }
  1161. currIndex = sepList[i] + (lengthList.IsEmpty ? defaultLength : lengthList[i]);
  1162. if (arrIndex == count - 1)
  1163. {
  1164. // If all the remaining entries at the end are empty, skip them
  1165. while (i < numReplaces - 1 && currIndex == sepList[++i])
  1166. {
  1167. currIndex += (lengthList.IsEmpty ? defaultLength : lengthList[i]);
  1168. }
  1169. break;
  1170. }
  1171. }
  1172. // we must have at least one slot left to fill in the last string.
  1173. Debug.Assert(arrIndex < maxItems);
  1174. //Handle the last string at the end of the array if there is one.
  1175. if (currIndex < Length)
  1176. {
  1177. splitStrings[arrIndex++] = Substring(currIndex);
  1178. }
  1179. string[] stringArray = splitStrings;
  1180. if (arrIndex != maxItems)
  1181. {
  1182. stringArray = new string[arrIndex];
  1183. for (int j = 0; j < arrIndex; j++)
  1184. {
  1185. stringArray[j] = splitStrings[j];
  1186. }
  1187. }
  1188. return stringArray;
  1189. }
  1190. /// <summary>
  1191. /// Uses ValueListBuilder to create list that holds indexes of separators in string.
  1192. /// </summary>
  1193. /// <param name="separators"><see cref="ReadOnlySpan{T}"/> of separator chars</param>
  1194. /// <param name="sepListBuilder"><see cref="ValueListBuilder{T}"/> to store indexes</param>
  1195. /// <returns></returns>
  1196. private void MakeSeparatorList(ReadOnlySpan<char> separators, ref ValueListBuilder<int> sepListBuilder)
  1197. {
  1198. char sep0, sep1, sep2;
  1199. switch (separators.Length)
  1200. {
  1201. // Special-case no separators to mean any whitespace is a separator.
  1202. case 0:
  1203. for (int i = 0; i < Length; i++)
  1204. {
  1205. if (char.IsWhiteSpace(this[i]))
  1206. {
  1207. sepListBuilder.Append(i);
  1208. }
  1209. }
  1210. break;
  1211. // Special-case the common cases of 1, 2, and 3 separators, with manual comparisons against each separator.
  1212. case 1:
  1213. sep0 = separators[0];
  1214. for (int i = 0; i < Length; i++)
  1215. {
  1216. if (this[i] == sep0)
  1217. {
  1218. sepListBuilder.Append(i);
  1219. }
  1220. }
  1221. break;
  1222. case 2:
  1223. sep0 = separators[0];
  1224. sep1 = separators[1];
  1225. for (int i = 0; i < Length; i++)
  1226. {
  1227. char c = this[i];
  1228. if (c == sep0 || c == sep1)
  1229. {
  1230. sepListBuilder.Append(i);
  1231. }
  1232. }
  1233. break;
  1234. case 3:
  1235. sep0 = separators[0];
  1236. sep1 = separators[1];
  1237. sep2 = separators[2];
  1238. for (int i = 0; i < Length; i++)
  1239. {
  1240. char c = this[i];
  1241. if (c == sep0 || c == sep1 || c == sep2)
  1242. {
  1243. sepListBuilder.Append(i);
  1244. }
  1245. }
  1246. break;
  1247. // Handle > 3 separators with a probabilistic map, ala IndexOfAny.
  1248. // This optimizes for chars being unlikely to match a separator.
  1249. default:
  1250. unsafe
  1251. {
  1252. ProbabilisticMap map = default;
  1253. uint* charMap = (uint*)&map;
  1254. InitializeProbabilisticMap(charMap, separators);
  1255. for (int i = 0; i < Length; i++)
  1256. {
  1257. char c = this[i];
  1258. if (IsCharBitSet(charMap, (byte)c) && IsCharBitSet(charMap, (byte)(c >> 8)) &&
  1259. separators.Contains(c))
  1260. {
  1261. sepListBuilder.Append(i);
  1262. }
  1263. }
  1264. }
  1265. break;
  1266. }
  1267. }
  1268. /// <summary>
  1269. /// Uses ValueListBuilder to create list that holds indexes of separators in string.
  1270. /// </summary>
  1271. /// <param name="separator">separator string</param>
  1272. /// <param name="sepListBuilder"><see cref="ValueListBuilder{T}"/> to store indexes</param>
  1273. /// <returns></returns>
  1274. private void MakeSeparatorList(string separator, ref ValueListBuilder<int> sepListBuilder)
  1275. {
  1276. Debug.Assert(!IsNullOrEmpty(separator), "!string.IsNullOrEmpty(separator)");
  1277. int currentSepLength = separator.Length;
  1278. for (int i = 0; i < Length; i++)
  1279. {
  1280. if (this[i] == separator[0] && currentSepLength <= Length - i)
  1281. {
  1282. if (currentSepLength == 1
  1283. || this.AsSpan(i, currentSepLength).SequenceEqual(separator))
  1284. {
  1285. sepListBuilder.Append(i);
  1286. i += currentSepLength - 1;
  1287. }
  1288. }
  1289. }
  1290. }
  1291. /// <summary>
  1292. /// Uses ValueListBuilder to create list that holds indexes of separators in string and list that holds length of separator strings.
  1293. /// </summary>
  1294. /// <param name="separators">separator strngs</param>
  1295. /// <param name="sepListBuilder"><see cref="ValueListBuilder{T}"/> for separator indexes</param>
  1296. /// <param name="lengthListBuilder"><see cref="ValueListBuilder{T}"/> for separator length values</param>
  1297. private void MakeSeparatorList(string[] separators, ref ValueListBuilder<int> sepListBuilder, ref ValueListBuilder<int> lengthListBuilder)
  1298. {
  1299. Debug.Assert(separators != null && separators.Length > 0, "separators != null && separators.Length > 0");
  1300. int sepCount = separators.Length;
  1301. for (int i = 0; i < Length; i++)
  1302. {
  1303. for (int j = 0; j < separators.Length; j++)
  1304. {
  1305. string separator = separators[j];
  1306. if (IsNullOrEmpty(separator))
  1307. {
  1308. continue;
  1309. }
  1310. int currentSepLength = separator.Length;
  1311. if (this[i] == separator[0] && currentSepLength <= Length - i)
  1312. {
  1313. if (currentSepLength == 1
  1314. || this.AsSpan(i, currentSepLength).SequenceEqual(separator))
  1315. {
  1316. sepListBuilder.Append(i);
  1317. lengthListBuilder.Append(currentSepLength);
  1318. i += currentSepLength - 1;
  1319. break;
  1320. }
  1321. }
  1322. }
  1323. }
  1324. }
  1325. // Returns a substring of this string.
  1326. //
  1327. public string Substring(int startIndex) => Substring(startIndex, Length - startIndex);
  1328. public string Substring(int startIndex, int length)
  1329. {
  1330. if (startIndex < 0)
  1331. {
  1332. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
  1333. }
  1334. if (startIndex > Length)
  1335. {
  1336. throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndexLargerThanLength);
  1337. }
  1338. if (length < 0)
  1339. {
  1340. throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
  1341. }
  1342. if (startIndex > Length - length)
  1343. {
  1344. throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_IndexLength);
  1345. }
  1346. if (length == 0)
  1347. {
  1348. return string.Empty;
  1349. }
  1350. if (startIndex == 0 && length == this.Length)
  1351. {
  1352. return this;
  1353. }
  1354. return InternalSubString(startIndex, length);
  1355. }
  1356. private unsafe string InternalSubString(int startIndex, int length)
  1357. {
  1358. Debug.Assert(startIndex >= 0 && startIndex <= this.Length, "StartIndex is out of range!");
  1359. Debug.Assert(length >= 0 && startIndex <= this.Length - length, "length is out of range!");
  1360. string result = FastAllocateString(length);
  1361. fixed (char* dest = &result._firstChar)
  1362. fixed (char* src = &_firstChar)
  1363. {
  1364. wstrcpy(dest, src + startIndex, length);
  1365. }
  1366. return result;
  1367. }
  1368. // Creates a copy of this string in lower case. The culture is set by culture.
  1369. public string ToLower()
  1370. {
  1371. return CultureInfo.CurrentCulture.TextInfo.ToLower(this);
  1372. }
  1373. // Creates a copy of this string in lower case. The culture is set by culture.
  1374. public string ToLower(CultureInfo culture)
  1375. {
  1376. if (culture == null)
  1377. {
  1378. throw new ArgumentNullException(nameof(culture));
  1379. }
  1380. return culture.TextInfo.ToLower(this);
  1381. }
  1382. // Creates a copy of this string in lower case based on invariant culture.
  1383. public string ToLowerInvariant()
  1384. {
  1385. return CultureInfo.InvariantCulture.TextInfo.ToLower(this);
  1386. }
  1387. public string ToUpper()
  1388. {
  1389. return CultureInfo.CurrentCulture.TextInfo.ToUpper(this);
  1390. }
  1391. // Creates a copy of this string in upper case. The culture is set by culture.
  1392. public string ToUpper(CultureInfo culture)
  1393. {
  1394. if (culture == null)
  1395. {
  1396. throw new ArgumentNullException(nameof(culture));
  1397. }
  1398. return culture.TextInfo.ToUpper(this);
  1399. }
  1400. //Creates a copy of this string in upper case based on invariant culture.
  1401. public string ToUpperInvariant()
  1402. {
  1403. return CultureInfo.InvariantCulture.TextInfo.ToUpper(this);
  1404. }
  1405. // Trims the whitespace from both ends of the string. Whitespace is defined by
  1406. // char.IsWhiteSpace.
  1407. //
  1408. public string Trim() => TrimWhiteSpaceHelper(TrimType.Both);
  1409. // Removes a set of characters from the beginning and end of this string.
  1410. public unsafe string Trim(char trimChar) => TrimHelper(&trimChar, 1, TrimType.Both);
  1411. // Removes a set of characters from the beginning and end of this string.
  1412. public unsafe string Trim(params char[] trimChars)
  1413. {
  1414. if (trimChars == null || trimChars.Length == 0)
  1415. {
  1416. return TrimWhiteSpaceHelper(TrimType.Both);
  1417. }
  1418. fixed (char* pTrimChars = &trimChars[0])
  1419. {
  1420. return TrimHelper(pTrimChars, trimChars.Length, TrimType.Both);
  1421. }
  1422. }
  1423. // Removes a set of characters from the beginning of this string.
  1424. public string TrimStart() => TrimWhiteSpaceHelper(TrimType.Head);
  1425. // Removes a set of characters from the beginning of this string.
  1426. public unsafe string TrimStart(char trimChar) => TrimHelper(&trimChar, 1, TrimType.Head);
  1427. // Removes a set of characters from the beginning of this string.
  1428. public unsafe string TrimStart(params char[] trimChars)
  1429. {
  1430. if (trimChars == null || trimChars.Length == 0)
  1431. {
  1432. return TrimWhiteSpaceHelper(TrimType.Head);
  1433. }
  1434. fixed (char* pTrimChars = &trimChars[0])
  1435. {
  1436. return TrimHelper(pTrimChars, trimChars.Length, TrimType.Head);
  1437. }
  1438. }
  1439. // Removes a set of characters from the end of this string.
  1440. public string TrimEnd() => TrimWhiteSpaceHelper(TrimType.Tail);
  1441. // Removes a set of characters from the end of this string.
  1442. public unsafe string TrimEnd(char trimChar) => TrimHelper(&trimChar, 1, TrimType.Tail);
  1443. // Removes a set of characters from the end of this string.
  1444. public unsafe string TrimEnd(params char[] trimChars)
  1445. {
  1446. if (trimChars == null || trimChars.Length == 0)
  1447. {
  1448. return TrimWhiteSpaceHelper(TrimType.Tail);
  1449. }
  1450. fixed (char* pTrimChars = &trimChars[0])
  1451. {
  1452. return TrimHelper(pTrimChars, trimChars.Length, TrimType.Tail);
  1453. }
  1454. }
  1455. private string TrimWhiteSpaceHelper(TrimType trimType)
  1456. {
  1457. // end will point to the first non-trimmed character on the right.
  1458. // start will point to the first non-trimmed character on the left.
  1459. int end = Length - 1;
  1460. int start = 0;
  1461. // Trim specified characters.
  1462. if (trimType != TrimType.Tail)
  1463. {
  1464. for (start = 0; start < Length; start++)
  1465. {
  1466. if (!char.IsWhiteSpace(this[start]))
  1467. {
  1468. break;
  1469. }
  1470. }
  1471. }
  1472. if (trimType != TrimType.Head)
  1473. {
  1474. for (end = Length - 1; end >= start; end--)
  1475. {
  1476. if (!char.IsWhiteSpace(this[end]))
  1477. {
  1478. break;
  1479. }
  1480. }
  1481. }
  1482. return CreateTrimmedString(start, end);
  1483. }
  1484. private unsafe string TrimHelper(char* trimChars, int trimCharsLength, TrimType trimType)
  1485. {
  1486. Debug.Assert(trimChars != null);
  1487. Debug.Assert(trimCharsLength > 0);
  1488. // end will point to the first non-trimmed character on the right.
  1489. // start will point to the first non-trimmed character on the left.
  1490. int end = Length - 1;
  1491. int start = 0;
  1492. // Trim specified characters.
  1493. if (trimType != TrimType.Tail)
  1494. {
  1495. for (start = 0; start < Length; start++)
  1496. {
  1497. int i = 0;
  1498. char ch = this[start];
  1499. for (i = 0; i < trimCharsLength; i++)
  1500. {
  1501. if (trimChars[i] == ch)
  1502. {
  1503. break;
  1504. }
  1505. }
  1506. if (i == trimCharsLength)
  1507. {
  1508. // The character is not in trimChars, so stop trimming.
  1509. break;
  1510. }
  1511. }
  1512. }
  1513. if (trimType != TrimType.Head)
  1514. {
  1515. for (end = Length - 1; end >= start; end--)
  1516. {
  1517. int i = 0;
  1518. char ch = this[end];
  1519. for (i = 0; i < trimCharsLength; i++)
  1520. {
  1521. if (trimChars[i] == ch)
  1522. {
  1523. break;
  1524. }
  1525. }
  1526. if (i == trimCharsLength)
  1527. {
  1528. // The character is not in trimChars, so stop trimming.
  1529. break;
  1530. }
  1531. }
  1532. }
  1533. return CreateTrimmedString(start, end);
  1534. }
  1535. private string CreateTrimmedString(int start, int end)
  1536. {
  1537. int len = end - start + 1;
  1538. return
  1539. len == Length ? this :
  1540. len == 0 ? string.Empty :
  1541. InternalSubString(start, len);
  1542. }
  1543. private enum TrimType
  1544. {
  1545. Head = 0,
  1546. Tail = 1,
  1547. Both = 2
  1548. }
  1549. }
  1550. }