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