String.Manipulation.cs 68 KB

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