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