String.Manipulation.cs 68 KB

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