String.Manipulation.cs 69 KB

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