move FrameworkName from corlib to System
[mcs.git] / class / corlib / System / String.cs
blob8d05c9187659899ba5aa8c4b308942df4cea552e
1 //
2 // System.String.cs
3 //
4 // Authors:
5 // Patrik Torstensson
6 // Jeffrey Stedfast (fejj@ximian.com)
7 // Dan Lewis (dihlewis@yahoo.co.uk)
8 // Sebastien Pouliot <sebastien@ximian.com>
9 // Marek Safar (marek.safar@seznam.cz)
10 // Andreas Nahr (Classdevelopment@A-SoftTech.com)
12 // (C) 2001 Ximian, Inc. http://www.ximian.com
13 // Copyright (C) 2004-2005 Novell (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 //
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 //
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 // This class contains all implementation for culture-insensitive methods.
37 // Culture-sensitive methods are implemented in the System.Globalization or
38 // Mono.Globalization namespace.
40 // Ensure that argument checks on methods don't overflow
43 using System.Text;
44 using System.Collections;
45 using System.Globalization;
46 using System.Runtime.CompilerServices;
48 using System.Collections.Generic;
49 using System.Runtime.ConstrainedExecution;
50 using System.Runtime.InteropServices;
51 using Mono.Globalization.Unicode;
54 namespace System
56 [Serializable]
57 [ComVisible (true)]
58 public sealed class String : IConvertible, ICloneable, IEnumerable, IComparable, IComparable<String>, IEquatable <String>, IEnumerable<char>
60 [NonSerialized] private int length;
61 [NonSerialized] private char start_char;
63 public static readonly String Empty = "";
65 internal static readonly int LOS_limit = GetLOSLimit ();
67 public static unsafe bool Equals (string a, string b)
69 if ((a as object) == (b as object))
70 return true;
72 if (a == null || b == null)
73 return false;
75 int len = a.length;
77 if (len != b.length)
78 return false;
80 fixed (char* s1 = &a.start_char, s2 = &b.start_char) {
81 char* s1_ptr = s1;
82 char* s2_ptr = s2;
84 while (len >= 8) {
85 if (((int*)s1_ptr)[0] != ((int*)s2_ptr)[0] ||
86 ((int*)s1_ptr)[1] != ((int*)s2_ptr)[1] ||
87 ((int*)s1_ptr)[2] != ((int*)s2_ptr)[2] ||
88 ((int*)s1_ptr)[3] != ((int*)s2_ptr)[3])
89 return false;
91 s1_ptr += 8;
92 s2_ptr += 8;
93 len -= 8;
96 if (len >= 4) {
97 if (((int*)s1_ptr)[0] != ((int*)s2_ptr)[0] ||
98 ((int*)s1_ptr)[1] != ((int*)s2_ptr)[1])
99 return false;
101 s1_ptr += 4;
102 s2_ptr += 4;
103 len -= 4;
106 if (len > 1) {
107 if (((int*)s1_ptr)[0] != ((int*)s2_ptr)[0])
108 return false;
110 s1_ptr += 2;
111 s2_ptr += 2;
112 len -= 2;
115 return len == 0 || *s1_ptr == *s2_ptr;
119 public static bool operator == (String a, String b)
121 return Equals (a, b);
124 public static bool operator != (String a, String b)
126 return !Equals (a, b);
129 [ReliabilityContractAttribute (Consistency.WillNotCorruptState, Cer.MayFail)]
130 public override bool Equals (Object obj)
132 return Equals (this, obj as String);
135 [ReliabilityContractAttribute (Consistency.WillNotCorruptState, Cer.MayFail)]
136 public bool Equals (String value)
138 return Equals (this, value);
141 [IndexerName ("Chars")]
142 public unsafe char this [int index] {
143 get {
144 if (index < 0 || index >= length)
145 throw new IndexOutOfRangeException ();
146 fixed (char* c = &start_char)
147 return c[index];
151 public Object Clone ()
153 return this;
156 public TypeCode GetTypeCode ()
158 return TypeCode.String;
161 public unsafe void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count)
163 if (destination == null)
164 throw new ArgumentNullException ("destination");
165 if (sourceIndex < 0)
166 throw new ArgumentOutOfRangeException ("sourceIndex", "Cannot be negative");
167 if (destinationIndex < 0)
168 throw new ArgumentOutOfRangeException ("destinationIndex", "Cannot be negative.");
169 if (count < 0)
170 throw new ArgumentOutOfRangeException ("count", "Cannot be negative.");
171 if (sourceIndex > Length - count)
172 throw new ArgumentOutOfRangeException ("sourceIndex", "sourceIndex + count > Length");
173 if (destinationIndex > destination.Length - count)
174 throw new ArgumentOutOfRangeException ("destinationIndex", "destinationIndex + count > destination.Length");
176 fixed (char* dest = destination, src = this)
177 CharCopy (dest + destinationIndex, src + sourceIndex, count);
180 public char[] ToCharArray ()
182 return ToCharArray (0, length);
185 public unsafe char[] ToCharArray (int startIndex, int length)
187 if (startIndex < 0)
188 throw new ArgumentOutOfRangeException ("startIndex", "< 0");
189 if (length < 0)
190 throw new ArgumentOutOfRangeException ("length", "< 0");
191 if (startIndex > this.length - length)
192 throw new ArgumentOutOfRangeException ("startIndex", "Must be greater than the length of the string.");
194 char[] tmp = new char [length];
195 fixed (char* dest = tmp, src = this)
196 CharCopy (dest, src + startIndex, length);
197 return tmp;
200 public String [] Split (params char [] separator)
202 return Split (separator, Int32.MaxValue);
205 public String[] Split (char[] separator, int count)
207 if (separator == null || separator.Length == 0)
208 separator = WhiteChars;
210 if (count < 0)
211 throw new ArgumentOutOfRangeException ("count");
213 if (count == 0)
214 return new String[0];
216 if (count == 1)
217 return new String[1] { this };
219 return InternalSplit (separator, count, 0);
222 [ComVisible (false)]
223 [MonoDocumentationNote ("code should be moved to managed")]
224 public String[] Split (char[] separator, int count, StringSplitOptions options)
226 if (separator == null || separator.Length == 0)
227 return Split (WhiteChars, count, options);
229 if (count < 0)
230 throw new ArgumentOutOfRangeException ("count", "Count cannot be less than zero.");
231 if ((options != StringSplitOptions.None) && (options != StringSplitOptions.RemoveEmptyEntries))
232 throw new ArgumentException ("Illegal enum value: " + options + ".");
234 if (count == 0)
235 return new string [0];
237 return InternalSplit (separator, count, (int)options);
240 [ComVisible (false)]
241 public String[] Split (string[] separator, int count, StringSplitOptions options)
243 if (separator == null || separator.Length == 0)
244 return Split (WhiteChars, count, options);
246 if (count < 0)
247 throw new ArgumentOutOfRangeException ("count", "Count cannot be less than zero.");
248 if ((options != StringSplitOptions.None) && (options != StringSplitOptions.RemoveEmptyEntries))
249 throw new ArgumentException ("Illegal enum value: " + options + ".");
251 bool removeEmpty = (options & StringSplitOptions.RemoveEmptyEntries) == StringSplitOptions.RemoveEmptyEntries;
253 if (count == 0 || (this == String.Empty && removeEmpty))
254 return new String [0];
256 ArrayList arr = new ArrayList ();
258 int pos = 0;
259 int matchCount = 0;
260 while (pos < this.Length) {
261 int matchIndex = -1;
262 int matchPos = Int32.MaxValue;
264 // Find the first position where any of the separators matches
265 for (int i = 0; i < separator.Length; ++i) {
266 string sep = separator [i];
267 if (sep == null || sep == String.Empty)
268 continue;
270 int match = IndexOf (sep, pos);
271 if (match > -1 && match < matchPos) {
272 matchIndex = i;
273 matchPos = match;
277 if (matchIndex == -1)
278 break;
280 if (!(matchPos == pos && removeEmpty))
281 arr.Add (this.Substring (pos, matchPos - pos));
283 pos = matchPos + separator [matchIndex].Length;
285 matchCount ++;
287 if (matchCount == count - 1)
288 break;
291 if (matchCount == 0)
292 return new String [] { this };
293 else {
294 if (removeEmpty && pos == this.Length) {
295 String[] res = new String [arr.Count];
296 arr.CopyTo (0, res, 0, arr.Count);
298 return res;
300 else {
301 String[] res = new String [arr.Count + 1];
302 arr.CopyTo (0, res, 0, arr.Count);
303 res [arr.Count] = this.Substring (pos);
305 return res;
310 [ComVisible (false)]
311 public String[] Split (char[] separator, StringSplitOptions options)
313 return Split (separator, Int32.MaxValue, options);
316 [ComVisible (false)]
317 public String[] Split (String[] separator, StringSplitOptions options)
319 return Split (separator, Int32.MaxValue, options);
322 public String Substring (int startIndex)
324 if (startIndex == 0)
325 return this;
326 if (startIndex < 0 || startIndex > this.length)
327 throw new ArgumentOutOfRangeException ("startIndex");
329 return SubstringUnchecked (startIndex, this.length - startIndex);
332 public String Substring (int startIndex, int length)
334 if (length < 0)
335 throw new ArgumentOutOfRangeException ("length", "Cannot be negative.");
336 if (startIndex < 0)
337 throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative.");
338 if (startIndex > this.length)
339 throw new ArgumentOutOfRangeException ("startIndex", "Cannot exceed length of string.");
340 if (startIndex > this.length - length)
341 throw new ArgumentOutOfRangeException ("length", "startIndex + length > this.length");
342 if (startIndex == 0 && length == this.length)
343 return this;
345 return SubstringUnchecked (startIndex, length);
348 // This method is used by StringBuilder.ToString() and is expected to
349 // always create a new string object (or return String.Empty).
350 internal unsafe String SubstringUnchecked (int startIndex, int length)
352 if (length == 0)
353 return String.Empty;
355 string tmp = InternalAllocateStr (length);
356 fixed (char* dest = tmp, src = this) {
357 CharCopy (dest, src + startIndex, length);
359 return tmp;
362 private static readonly char[] WhiteChars = {
363 (char) 0x9, (char) 0xA, (char) 0xB, (char) 0xC, (char) 0xD,
364 (char) 0x85, (char) 0x1680, (char) 0x2028, (char) 0x2029,
365 (char) 0x20, (char) 0xA0, (char) 0x2000, (char) 0x2001, (char) 0x2002, (char) 0x2003, (char) 0x2004,
366 (char) 0x2005, (char) 0x2006, (char) 0x2007, (char) 0x2008, (char) 0x2009, (char) 0x200A, (char) 0x200B,
367 (char) 0x3000, (char) 0xFEFF,
368 #if NET_2_1
369 // Silverlight
370 (char) 0x202f, (char) 0x205f,
371 #endif
374 public String Trim ()
376 if (length == 0)
377 return String.Empty;
378 int start = FindNotWhiteSpace (0, length, 1);
380 if (start == length)
381 return String.Empty;
383 int end = FindNotWhiteSpace (length - 1, start, -1);
385 int newLength = end - start + 1;
386 if (newLength == length)
387 return this;
389 return SubstringUnchecked (start, newLength);
392 public String Trim (params char[] trimChars)
394 if (trimChars == null || trimChars.Length == 0)
395 return Trim ();
397 if (length == 0)
398 return String.Empty;
399 int start = FindNotInTable (0, length, 1, trimChars);
401 if (start == length)
402 return String.Empty;
404 int end = FindNotInTable (length - 1, start, -1, trimChars);
406 int newLength = end - start + 1;
407 if (newLength == length)
408 return this;
410 return SubstringUnchecked (start, newLength);
413 public String TrimStart (params char[] trimChars)
415 if (length == 0)
416 return String.Empty;
417 int start;
418 if (trimChars == null || trimChars.Length == 0)
419 start = FindNotWhiteSpace (0, length, 1);
420 else
421 start = FindNotInTable (0, length, 1, trimChars);
423 if (start == 0)
424 return this;
426 return SubstringUnchecked (start, length - start);
429 public String TrimEnd (params char[] trimChars)
431 if (length == 0)
432 return String.Empty;
433 int end;
434 if (trimChars == null || trimChars.Length == 0)
435 end = FindNotWhiteSpace (length - 1, -1, -1);
436 else
437 end = FindNotInTable (length - 1, -1, -1, trimChars);
439 end++;
440 if (end == length)
441 return this;
443 return SubstringUnchecked (0, end);
446 private int FindNotWhiteSpace (int pos, int target, int change)
448 while (pos != target) {
449 char c = this[pos];
450 if (c < 0x85) {
451 if (c != 0x20) {
452 if (c < 0x9 || c > 0xD)
453 return pos;
456 else {
457 if (c != 0xA0 && c != 0xFEFF && c != 0x3000) {
458 if (c != 0x85 && c != 0x1680 && c != 0x2028 && c != 0x2029
459 #if NET_2_1
460 // On Silverlight this whitespace participates in Trim
461 && c != 0x202f && c != 0x205f
462 #endif
464 if (c < 0x2000 || c > 0x200B)
465 return pos;
468 pos += change;
470 return pos;
473 private unsafe int FindNotInTable (int pos, int target, int change, char[] table)
475 fixed (char* tablePtr = table, thisPtr = this) {
476 while (pos != target) {
477 char c = thisPtr[pos];
478 int x = 0;
479 while (x < table.Length) {
480 if (c == tablePtr[x])
481 break;
482 x++;
484 if (x == table.Length)
485 return pos;
486 pos += change;
489 return pos;
492 public static int Compare (String strA, String strB)
494 return CultureInfo.CurrentCulture.CompareInfo.Compare (strA, strB, CompareOptions.None);
497 public static int Compare (String strA, String strB, bool ignoreCase)
499 return CultureInfo.CurrentCulture.CompareInfo.Compare (strA, strB, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
502 public static int Compare (String strA, String strB, bool ignoreCase, CultureInfo culture)
504 if (culture == null)
505 throw new ArgumentNullException ("culture");
507 return culture.CompareInfo.Compare (strA, strB, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
510 public static int Compare (String strA, int indexA, String strB, int indexB, int length)
512 return Compare (strA, indexA, strB, indexB, length, false, CultureInfo.CurrentCulture);
515 public static int Compare (String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
517 return Compare (strA, indexA, strB, indexB, length, ignoreCase, CultureInfo.CurrentCulture);
520 public static int Compare (String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
522 if (culture == null)
523 throw new ArgumentNullException ("culture");
525 if ((indexA > strA.Length) || (indexB > strB.Length) || (indexA < 0) || (indexB < 0) || (length < 0))
526 throw new ArgumentOutOfRangeException ();
528 if (length == 0)
529 return 0;
531 if (strA == null) {
532 if (strB == null) {
533 return 0;
534 } else {
535 return -1;
538 else if (strB == null) {
539 return 1;
542 CompareOptions compopts;
544 if (ignoreCase)
545 compopts = CompareOptions.IgnoreCase;
546 else
547 compopts = CompareOptions.None;
549 // Need to cap the requested length to the
550 // length of the string, because
551 // CompareInfo.Compare will insist that length
552 // <= (string.Length - offset)
554 int len1 = length;
555 int len2 = length;
557 if (length > (strA.Length - indexA)) {
558 len1 = strA.Length - indexA;
561 if (length > (strB.Length - indexB)) {
562 len2 = strB.Length - indexB;
565 // ENHANCE: Might call internal_compare_switch directly instead of doing all checks twice
566 return culture.CompareInfo.Compare (strA, indexA, len1, strB, indexB, len2, compopts);
569 public static int Compare (string strA, string strB, StringComparison comparisonType)
571 switch (comparisonType) {
572 case StringComparison.CurrentCulture:
573 return Compare (strA, strB, false, CultureInfo.CurrentCulture);
574 case StringComparison.CurrentCultureIgnoreCase:
575 return Compare (strA, strB, true, CultureInfo.CurrentCulture);
576 case StringComparison.InvariantCulture:
577 return Compare (strA, strB, false, CultureInfo.InvariantCulture);
578 case StringComparison.InvariantCultureIgnoreCase:
579 return Compare (strA, strB, true, CultureInfo.InvariantCulture);
580 case StringComparison.Ordinal:
581 return CompareOrdinalUnchecked (strA, 0, Int32.MaxValue, strB, 0, Int32.MaxValue);
582 case StringComparison.OrdinalIgnoreCase:
583 return CompareOrdinalCaseInsensitiveUnchecked (strA, 0, Int32.MaxValue, strB, 0, Int32.MaxValue);
584 default:
585 string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType);
586 throw new ArgumentException (msg, "comparisonType");
590 public static int Compare (string strA, int indexA, string strB, int indexB, int length, StringComparison comparisonType)
592 switch (comparisonType) {
593 case StringComparison.CurrentCulture:
594 return Compare (strA, indexA, strB, indexB, length, false, CultureInfo.CurrentCulture);
595 case StringComparison.CurrentCultureIgnoreCase:
596 return Compare (strA, indexA, strB, indexB, length, true, CultureInfo.CurrentCulture);
597 case StringComparison.InvariantCulture:
598 return Compare (strA, indexA, strB, indexB, length, false, CultureInfo.InvariantCulture);
599 case StringComparison.InvariantCultureIgnoreCase:
600 return Compare (strA, indexA, strB, indexB, length, true, CultureInfo.InvariantCulture);
601 case StringComparison.Ordinal:
602 return CompareOrdinal (strA, indexA, strB, indexB, length);
603 case StringComparison.OrdinalIgnoreCase:
604 return CompareOrdinalCaseInsensitive (strA, indexA, strB, indexB, length);
605 default:
606 string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType);
607 throw new ArgumentException (msg, "comparisonType");
611 public static bool Equals (string a, string b, StringComparison comparisonType)
613 return String.Compare (a, b, comparisonType) == 0;
616 public bool Equals (string value, StringComparison comparisonType)
618 return String.Compare (value, this, comparisonType) == 0;
621 public static int Compare (string strA, string strB, CultureInfo culture, CompareOptions options)
623 if (culture == null)
624 throw new ArgumentNullException ("culture");
626 return culture.CompareInfo.Compare (strA, strB, options);
629 public static int Compare (string strA, int indexA, string strB, int indexB, int length, CultureInfo culture, CompareOptions options)
631 if (culture == null)
632 throw new ArgumentNullException ("culture");
634 int len1 = length;
635 int len2 = length;
637 if (length > (strA.Length - indexA))
638 len1 = strA.Length - indexA;
640 if (length > (strB.Length - indexB))
641 len2 = strB.Length - indexB;
643 return culture.CompareInfo.Compare (strA, indexA, len1, strB, indexB, len2, options);
646 public int CompareTo (Object value)
648 if (value == null)
649 return 1;
651 if (!(value is String))
652 throw new ArgumentException ();
654 return String.Compare (this, (String) value);
657 public int CompareTo (String strB)
659 if (strB == null)
660 return 1;
662 return Compare (this, strB);
665 public static int CompareOrdinal (String strA, String strB)
667 return CompareOrdinalUnchecked (strA, 0, Int32.MaxValue, strB, 0, Int32.MaxValue);
670 public static int CompareOrdinal (String strA, int indexA, String strB, int indexB, int length)
672 if ((indexA > strA.Length) || (indexB > strB.Length) || (indexA < 0) || (indexB < 0) || (length < 0))
673 throw new ArgumentOutOfRangeException ();
675 return CompareOrdinalUnchecked (strA, indexA, length, strB, indexB, length);
678 internal static int CompareOrdinalCaseInsensitive (String strA, int indexA, String strB, int indexB, int length)
680 if ((indexA > strA.Length) || (indexB > strB.Length) || (indexA < 0) || (indexB < 0) || (length < 0))
681 throw new ArgumentOutOfRangeException ();
683 return CompareOrdinalCaseInsensitiveUnchecked (strA, indexA, length, strB, indexB, length);
686 internal static unsafe int CompareOrdinalUnchecked (String strA, int indexA, int lenA, String strB, int indexB, int lenB)
688 if (strA == null) {
689 if (strB == null)
690 return 0;
691 else
692 return -1;
693 } else if (strB == null) {
694 return 1;
696 int lengthA = Math.Min (lenA, strA.Length - indexA);
697 int lengthB = Math.Min (lenB, strB.Length - indexB);
699 if (lengthA == lengthB && Object.ReferenceEquals (strA, strB))
700 return 0;
702 fixed (char* aptr = strA, bptr = strB) {
703 char* ap = aptr + indexA;
704 char* end = ap + Math.Min (lengthA, lengthB);
705 char* bp = bptr + indexB;
706 while (ap < end) {
707 if (*ap != *bp)
708 return *ap - *bp;
709 ap++;
710 bp++;
712 return lengthA - lengthB;
716 internal static unsafe int CompareOrdinalCaseInsensitiveUnchecked (String strA, int indexA, int lenA, String strB, int indexB, int lenB)
718 // Same as above, but checks versus uppercase characters
719 if (strA == null) {
720 if (strB == null)
721 return 0;
722 else
723 return -1;
724 } else if (strB == null) {
725 return 1;
727 int lengthA = Math.Min (lenA, strA.Length - indexA);
728 int lengthB = Math.Min (lenB, strB.Length - indexB);
730 if (lengthA == lengthB && Object.ReferenceEquals (strA, strB))
731 return 0;
733 fixed (char* aptr = strA, bptr = strB) {
734 char* ap = aptr + indexA;
735 char* end = ap + Math.Min (lengthA, lengthB);
736 char* bp = bptr + indexB;
737 while (ap < end) {
738 if (*ap != *bp) {
739 char c1 = Char.ToUpperInvariant (*ap);
740 char c2 = Char.ToUpperInvariant (*bp);
741 if (c1 != c2)
742 return c1 - c2;
744 ap++;
745 bp++;
747 return lengthA - lengthB;
751 public bool EndsWith (String value)
753 if (value == null)
754 throw new ArgumentNullException ("value");
756 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix (this, value, CompareOptions.None);
759 public bool EndsWith (String value, bool ignoreCase, CultureInfo culture)
761 if (value == null)
762 throw new ArgumentNullException ("value");
763 if (culture == null)
764 culture = CultureInfo.CurrentCulture;
766 return culture.CompareInfo.IsSuffix (this, value,
767 ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
770 // Following methods are culture-insensitive
771 public int IndexOfAny (char [] anyOf)
773 if (anyOf == null)
774 throw new ArgumentNullException ();
775 if (this.length == 0)
776 return -1;
778 return IndexOfAnyUnchecked (anyOf, 0, this.length);
781 public int IndexOfAny (char [] anyOf, int startIndex)
783 if (anyOf == null)
784 throw new ArgumentNullException ();
785 if (startIndex < 0 || startIndex > this.length)
786 throw new ArgumentOutOfRangeException ();
788 return IndexOfAnyUnchecked (anyOf, startIndex, this.length - startIndex);
791 public int IndexOfAny (char [] anyOf, int startIndex, int count)
793 if (anyOf == null)
794 throw new ArgumentNullException ();
795 if (startIndex < 0 || startIndex > this.length)
796 throw new ArgumentOutOfRangeException ();
797 if (count < 0 || startIndex > this.length - count)
798 throw new ArgumentOutOfRangeException ("count", "Count cannot be negative, and startIndex + count must be less than length of the string.");
800 return IndexOfAnyUnchecked (anyOf, startIndex, count);
803 private unsafe int IndexOfAnyUnchecked (char[] anyOf, int startIndex, int count)
805 if (anyOf.Length == 0)
806 return -1;
808 if (anyOf.Length == 1)
809 return IndexOfUnchecked (anyOf[0], startIndex, count);
811 fixed (char* any = anyOf) {
812 int highest = *any;
813 int lowest = *any;
815 char* end_any_ptr = any + anyOf.Length;
816 char* any_ptr = any;
817 while (++any_ptr != end_any_ptr) {
818 if (*any_ptr > highest) {
819 highest = *any_ptr;
820 continue;
823 if (*any_ptr < lowest)
824 lowest = *any_ptr;
827 fixed (char* start = &start_char) {
828 char* ptr = start + startIndex;
829 char* end_ptr = ptr + count;
831 while (ptr != end_ptr) {
832 if (*ptr > highest || *ptr < lowest) {
833 ptr++;
834 continue;
837 if (*ptr == *any)
838 return (int)(ptr - start);
840 any_ptr = any;
841 while (++any_ptr != end_any_ptr) {
842 if (*ptr == *any_ptr)
843 return (int)(ptr - start);
846 ptr++;
850 return -1;
854 public int IndexOf (string value, StringComparison comparisonType)
856 return IndexOf (value, 0, this.Length, comparisonType);
859 public int IndexOf (string value, int startIndex, StringComparison comparisonType)
861 return IndexOf (value, startIndex, this.Length - startIndex, comparisonType);
864 public int IndexOf (string value, int startIndex, int count, StringComparison comparisonType)
866 switch (comparisonType) {
867 case StringComparison.CurrentCulture:
868 return CultureInfo.CurrentCulture.CompareInfo.IndexOf (this, value, startIndex, count, CompareOptions.None);
869 case StringComparison.CurrentCultureIgnoreCase:
870 return CultureInfo.CurrentCulture.CompareInfo.IndexOf (this, value, startIndex, count, CompareOptions.IgnoreCase);
871 case StringComparison.InvariantCulture:
872 return CultureInfo.InvariantCulture.CompareInfo.IndexOf (this, value, startIndex, count, CompareOptions.None);
873 case StringComparison.InvariantCultureIgnoreCase:
874 return CultureInfo.InvariantCulture.CompareInfo.IndexOf (this, value, startIndex, count, CompareOptions.IgnoreCase);
875 case StringComparison.Ordinal:
876 return IndexOfOrdinal (value, startIndex, count, CompareOptions.Ordinal);
877 case StringComparison.OrdinalIgnoreCase:
878 return IndexOfOrdinal (value, startIndex, count, CompareOptions.OrdinalIgnoreCase);
879 default:
880 string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType);
881 throw new ArgumentException (msg, "comparisonType");
885 internal int IndexOfOrdinal (string value, int startIndex, int count, CompareOptions options)
887 if (value == null)
888 throw new ArgumentNullException ("value");
889 if (startIndex < 0)
890 throw new ArgumentOutOfRangeException ("startIndex");
891 if (count < 0 || (this.length - startIndex) < count)
892 throw new ArgumentOutOfRangeException ("count");
894 if (options == CompareOptions.Ordinal)
895 return IndexOfOrdinalUnchecked (value, startIndex, count);
896 return IndexOfOrdinalIgnoreCaseUnchecked (value, startIndex, count);
899 internal unsafe int IndexOfOrdinalUnchecked (string value, int startIndex, int count)
901 int valueLen = value.Length;
902 if (count < valueLen)
903 return -1;
905 if (valueLen <= 1) {
906 if (valueLen == 1)
907 return IndexOfUnchecked (value[0], startIndex, count);
908 return startIndex;
911 fixed (char* thisptr = this, valueptr = value) {
912 char* ap = thisptr + startIndex;
913 char* thisEnd = ap + count - valueLen + 1;
914 while (ap != thisEnd) {
915 if (*ap == *valueptr) {
916 for (int i = 1; i < valueLen; i++) {
917 if (ap[i] != valueptr[i])
918 goto NextVal;
920 return (int)(ap - thisptr);
922 NextVal:
923 ap++;
926 return -1;
929 internal unsafe int IndexOfOrdinalIgnoreCaseUnchecked (string value, int startIndex, int count)
931 int valueLen = value.Length;
932 if (count < valueLen)
933 return -1;
935 if (valueLen == 0)
936 return startIndex;
938 fixed (char* thisptr = this, valueptr = value) {
939 char* ap = thisptr + startIndex;
940 char* thisEnd = ap + count - valueLen + 1;
941 while (ap != thisEnd) {
942 for (int i = 0; i < valueLen; i++) {
943 if (Char.ToUpperInvariant (ap[i]) != Char.ToUpperInvariant (valueptr[i]))
944 goto NextVal;
946 return (int)(ap - thisptr);
947 NextVal:
948 ap++;
951 return -1;
954 public int LastIndexOf (string value, StringComparison comparisonType)
956 if (this.Length == 0)
957 return value == String.Empty ? 0 : -1;
958 else
959 return LastIndexOf (value, this.Length - 1, this.Length, comparisonType);
962 public int LastIndexOf (string value, int startIndex, StringComparison comparisonType)
964 return LastIndexOf (value, startIndex, startIndex + 1, comparisonType);
967 public int LastIndexOf (string value, int startIndex, int count, StringComparison comparisonType)
969 switch (comparisonType) {
970 case StringComparison.CurrentCulture:
971 return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf (this, value, startIndex, count, CompareOptions.None);
972 case StringComparison.CurrentCultureIgnoreCase:
973 return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf (this, value, startIndex, count, CompareOptions.IgnoreCase);
974 case StringComparison.InvariantCulture:
975 return CultureInfo.InvariantCulture.CompareInfo.LastIndexOf (this, value, startIndex, count, CompareOptions.None);
976 case StringComparison.InvariantCultureIgnoreCase:
977 return CultureInfo.InvariantCulture.CompareInfo.LastIndexOf (this, value, startIndex, count, CompareOptions.IgnoreCase);
978 case StringComparison.Ordinal:
979 return LastIndexOfOrdinal (value, startIndex, count, CompareOptions.Ordinal);
980 case StringComparison.OrdinalIgnoreCase:
981 return LastIndexOfOrdinal (value, startIndex, count, CompareOptions.OrdinalIgnoreCase);
982 default:
983 string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType);
984 throw new ArgumentException (msg, "comparisonType");
988 internal int LastIndexOfOrdinal (string value, int startIndex, int count, CompareOptions options)
990 if (value == null)
991 throw new ArgumentNullException ("value");
992 if (startIndex < 0 || startIndex > length)
993 throw new ArgumentOutOfRangeException ("startIndex");
994 if (count < 0 || (startIndex < count - 1))
995 throw new ArgumentOutOfRangeException ("count");
997 if (options == CompareOptions.Ordinal)
998 return LastIndexOfOrdinalUnchecked (value, startIndex, count);
999 return LastIndexOfOrdinalIgnoreCaseUnchecked (value, startIndex, count);
1002 internal unsafe int LastIndexOfOrdinalUnchecked (string value, int startIndex, int count)
1004 int valueLen = value.Length;
1005 if (count < valueLen)
1006 return -1;
1008 if (valueLen <= 1) {
1009 if (valueLen == 1)
1010 return LastIndexOfUnchecked (value[0], startIndex, count);
1011 return startIndex;
1014 fixed (char* thisptr = this, valueptr = value) {
1015 char* ap = thisptr + startIndex - valueLen + 1;
1016 char* thisEnd = ap - count + valueLen - 1;
1017 while (ap != thisEnd) {
1018 if (*ap == *valueptr) {
1019 for (int i = 1; i < valueLen; i++) {
1020 if (ap[i] != valueptr[i])
1021 goto NextVal;
1023 return (int)(ap - thisptr);
1025 NextVal:
1026 ap--;
1029 return -1;
1032 internal unsafe int LastIndexOfOrdinalIgnoreCaseUnchecked (string value, int startIndex, int count)
1034 int valueLen = value.Length;
1035 if (count < valueLen)
1036 return -1;
1038 if (valueLen == 0)
1039 return startIndex;
1041 fixed (char* thisptr = this, valueptr = value) {
1042 char* ap = thisptr + startIndex - valueLen + 1;
1043 char* thisEnd = ap - count + valueLen - 1;
1044 while (ap != thisEnd) {
1045 for (int i = 0; i < valueLen; i++) {
1046 if (Char.ToUpperInvariant (ap[i]) != Char.ToUpperInvariant (valueptr[i]))
1047 goto NextVal;
1049 return (int)(ap - thisptr);
1050 NextVal:
1051 ap--;
1054 return -1;
1057 // Following methods are culture-insensitive
1058 public int IndexOf (char value)
1060 if (this.length == 0)
1061 return -1;
1063 return IndexOfUnchecked (value, 0, this.length);
1066 public int IndexOf (char value, int startIndex)
1068 if (startIndex < 0)
1069 throw new ArgumentOutOfRangeException ("startIndex", "< 0");
1070 if (startIndex > this.length)
1071 throw new ArgumentOutOfRangeException ("startIndex", "startIndex > this.length");
1073 if ((startIndex == 0 && this.length == 0) || (startIndex == this.length))
1074 return -1;
1076 return IndexOfUnchecked (value, startIndex, this.length - startIndex);
1079 public int IndexOf (char value, int startIndex, int count)
1081 if (startIndex < 0 || startIndex > this.length)
1082 throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative and must be< 0");
1083 if (count < 0)
1084 throw new ArgumentOutOfRangeException ("count", "< 0");
1085 if (startIndex > this.length - count)
1086 throw new ArgumentOutOfRangeException ("count", "startIndex + count > this.length");
1088 if ((startIndex == 0 && this.length == 0) || (startIndex == this.length) || (count == 0))
1089 return -1;
1091 return IndexOfUnchecked (value, startIndex, count);
1094 internal unsafe int IndexOfUnchecked (char value, int startIndex, int count)
1096 // It helps JIT compiler to optimize comparison
1097 int value_32 = (int)value;
1099 fixed (char* start = &start_char) {
1100 char* ptr = start + startIndex;
1101 char* end_ptr = ptr + (count >> 3 << 3);
1103 while (ptr != end_ptr) {
1104 if (*ptr == value_32)
1105 return (int)(ptr - start);
1106 if (ptr[1] == value_32)
1107 return (int)(ptr - start + 1);
1108 if (ptr[2] == value_32)
1109 return (int)(ptr - start + 2);
1110 if (ptr[3] == value_32)
1111 return (int)(ptr - start + 3);
1112 if (ptr[4] == value_32)
1113 return (int)(ptr - start + 4);
1114 if (ptr[5] == value_32)
1115 return (int)(ptr - start + 5);
1116 if (ptr[6] == value_32)
1117 return (int)(ptr - start + 6);
1118 if (ptr[7] == value_32)
1119 return (int)(ptr - start + 7);
1121 ptr += 8;
1124 end_ptr += count & 0x07;
1125 while (ptr != end_ptr) {
1126 if (*ptr == value_32)
1127 return (int)(ptr - start);
1129 ptr++;
1131 return -1;
1135 internal unsafe int IndexOfOrdinalIgnoreCase (char value, int startIndex, int count)
1137 if (length == 0)
1138 return -1;
1139 int end = startIndex + count;
1140 char c = Char.ToUpperInvariant (value);
1141 fixed (char* s = &start_char) {
1142 for (int i = startIndex; i < end; i++)
1143 if (Char.ToUpperInvariant (s [i]) == c)
1144 return i;
1146 return -1;
1149 // Following methods are culture-sensitive
1150 public int IndexOf (String value)
1152 if (value == null)
1153 throw new ArgumentNullException ("value");
1154 if (value.length == 0)
1155 return 0;
1156 if (this.length == 0)
1157 return -1;
1158 return CultureInfo.CurrentCulture.CompareInfo.IndexOf (this, value, 0, length, CompareOptions.Ordinal);
1161 public int IndexOf (String value, int startIndex)
1163 return IndexOf (value, startIndex, this.length - startIndex);
1166 public int IndexOf (String value, int startIndex, int count)
1168 if (value == null)
1169 throw new ArgumentNullException ("value");
1170 if (startIndex < 0 || startIndex > this.length)
1171 throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative, and should not exceed length of string.");
1172 if (count < 0 || startIndex > this.length - count)
1173 throw new ArgumentOutOfRangeException ("count", "Cannot be negative, and should point to location in string.");
1175 if (value.length == 0)
1176 return startIndex;
1178 if (startIndex == 0 && this.length == 0)
1179 return -1;
1181 if (count == 0)
1182 return -1;
1184 return CultureInfo.CurrentCulture.CompareInfo.IndexOf (this, value, startIndex, count);
1187 // Following methods are culture-insensitive
1188 public int LastIndexOfAny (char [] anyOf)
1190 if (anyOf == null)
1191 throw new ArgumentNullException ();
1193 return LastIndexOfAnyUnchecked (anyOf, this.length - 1, this.length);
1196 public int LastIndexOfAny (char [] anyOf, int startIndex)
1198 if (anyOf == null)
1199 throw new ArgumentNullException ();
1201 if (startIndex < 0 || startIndex >= this.length)
1202 throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative, and should be less than length of string.");
1204 if (this.length == 0)
1205 return -1;
1207 return LastIndexOfAnyUnchecked (anyOf, startIndex, startIndex + 1);
1210 public int LastIndexOfAny (char [] anyOf, int startIndex, int count)
1212 if (anyOf == null)
1213 throw new ArgumentNullException ();
1215 if ((startIndex < 0) || (startIndex >= this.Length))
1216 throw new ArgumentOutOfRangeException ("startIndex", "< 0 || > this.Length");
1217 if ((count < 0) || (count > this.Length))
1218 throw new ArgumentOutOfRangeException ("count", "< 0 || > this.Length");
1219 if (startIndex - count + 1 < 0)
1220 throw new ArgumentOutOfRangeException ("startIndex - count + 1 < 0");
1222 if (this.length == 0)
1223 return -1;
1225 return LastIndexOfAnyUnchecked (anyOf, startIndex, count);
1228 private unsafe int LastIndexOfAnyUnchecked (char [] anyOf, int startIndex, int count)
1230 if (anyOf.Length == 1)
1231 return LastIndexOfUnchecked (anyOf[0], startIndex, count);
1233 fixed (char* start = this, testStart = anyOf) {
1234 char* ptr = start + startIndex;
1235 char* ptrEnd = ptr - count;
1236 char* test;
1237 char* testEnd = testStart + anyOf.Length;
1239 while (ptr != ptrEnd) {
1240 test = testStart;
1241 while (test != testEnd) {
1242 if (*test == *ptr)
1243 return (int)(ptr - start);
1244 test++;
1246 ptr--;
1248 return -1;
1252 // Following methods are culture-insensitive
1253 public int LastIndexOf (char value)
1255 if (this.length == 0)
1256 return -1;
1258 return LastIndexOfUnchecked (value, this.length - 1, this.length);
1261 public int LastIndexOf (char value, int startIndex)
1263 return LastIndexOf (value, startIndex, startIndex + 1);
1266 public int LastIndexOf (char value, int startIndex, int count)
1268 if (startIndex == 0 && this.length == 0)
1269 return -1;
1271 // >= for char (> for string)
1272 if ((startIndex < 0) || (startIndex >= this.Length))
1273 throw new ArgumentOutOfRangeException ("startIndex", "< 0 || >= this.Length");
1274 if ((count < 0) || (count > this.Length))
1275 throw new ArgumentOutOfRangeException ("count", "< 0 || > this.Length");
1276 if (startIndex - count + 1 < 0)
1277 throw new ArgumentOutOfRangeException ("startIndex - count + 1 < 0");
1279 return LastIndexOfUnchecked (value, startIndex, count);
1282 internal unsafe int LastIndexOfUnchecked (char value, int startIndex, int count)
1284 // It helps JIT compiler to optimize comparison
1285 int value_32 = (int)value;
1287 fixed (char* start = &start_char) {
1288 char* ptr = start + startIndex;
1289 char* end_ptr = ptr - (count >> 3 << 3);
1291 while (ptr != end_ptr) {
1292 if (*ptr == value_32)
1293 return (int)(ptr - start);
1294 if (ptr[-1] == value_32)
1295 return (int)(ptr - start) - 1;
1296 if (ptr[-2] == value_32)
1297 return (int)(ptr - start) - 2;
1298 if (ptr[-3] == value_32)
1299 return (int)(ptr - start) - 3;
1300 if (ptr[-4] == value_32)
1301 return (int)(ptr - start) - 4;
1302 if (ptr[-5] == value_32)
1303 return (int)(ptr - start) - 5;
1304 if (ptr[-6] == value_32)
1305 return (int)(ptr - start) - 6;
1306 if (ptr[-7] == value_32)
1307 return (int)(ptr - start) - 7;
1309 ptr -= 8;
1312 end_ptr -= count & 0x07;
1313 while (ptr != end_ptr) {
1314 if (*ptr == value_32)
1315 return (int)(ptr - start);
1317 ptr--;
1319 return -1;
1323 internal unsafe int LastIndexOfOrdinalIgnoreCase (char value, int startIndex, int count)
1325 if (length == 0)
1326 return -1;
1327 int end = startIndex - count;
1328 char c = Char.ToUpperInvariant (value);
1329 fixed (char* s = &start_char) {
1330 for (int i = startIndex; i > end; i--)
1331 if (Char.ToUpperInvariant (s [i]) == c)
1332 return i;
1334 return -1;
1337 // Following methods are culture-sensitive
1338 public int LastIndexOf (String value)
1340 if (this.length == 0)
1341 // This overload does additional checking
1342 return LastIndexOf (value, 0, 0);
1343 else
1344 return LastIndexOf (value, this.length - 1, this.length);
1347 public int LastIndexOf (String value, int startIndex)
1349 int max = startIndex;
1350 if (max < this.Length)
1351 max++;
1352 return LastIndexOf (value, startIndex, max);
1355 public int LastIndexOf (String value, int startIndex, int count)
1357 if (value == null)
1358 throw new ArgumentNullException ("value");
1360 // -1 > startIndex > for string (0 > startIndex >= for char)
1361 if ((startIndex < -1) || (startIndex > this.Length))
1362 throw new ArgumentOutOfRangeException ("startIndex", "< 0 || > this.Length");
1363 if ((count < 0) || (count > this.Length))
1364 throw new ArgumentOutOfRangeException ("count", "< 0 || > this.Length");
1365 if (startIndex - count + 1 < 0)
1366 throw new ArgumentOutOfRangeException ("startIndex - count + 1 < 0");
1368 if (value.Length == 0)
1369 return startIndex;
1371 if (startIndex == 0 && this.length == 0)
1372 return -1;
1374 // This check is needed to match undocumented MS behaviour
1375 if (this.length == 0 && value.length > 0)
1376 return -1;
1378 if (count == 0)
1379 return -1;
1381 if (startIndex == this.Length)
1382 startIndex--;
1383 return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf (this, value, startIndex, count);
1386 public bool Contains (String value)
1388 return IndexOf (value) != -1;
1391 public static bool IsNullOrEmpty (String value)
1393 return (value == null) || (value.Length == 0);
1396 #if !MOONLIGHT
1397 public string Normalize ()
1399 return Normalization.Normalize (this, 0);
1402 public string Normalize (NormalizationForm normalizationForm)
1404 switch (normalizationForm) {
1405 default:
1406 return Normalization.Normalize (this, 0);
1407 case NormalizationForm.FormD:
1408 return Normalization.Normalize (this, 1);
1409 case NormalizationForm.FormKC:
1410 return Normalization.Normalize (this, 2);
1411 case NormalizationForm.FormKD:
1412 return Normalization.Normalize (this, 3);
1416 public bool IsNormalized ()
1418 return Normalization.IsNormalized (this, 0);
1421 public bool IsNormalized (NormalizationForm normalizationForm)
1423 switch (normalizationForm) {
1424 default:
1425 return Normalization.IsNormalized (this, 0);
1426 case NormalizationForm.FormD:
1427 return Normalization.IsNormalized (this, 1);
1428 case NormalizationForm.FormKC:
1429 return Normalization.IsNormalized (this, 2);
1430 case NormalizationForm.FormKD:
1431 return Normalization.IsNormalized (this, 3);
1434 #endif
1436 public string Remove (int startIndex)
1438 if (startIndex < 0)
1439 throw new ArgumentOutOfRangeException ("startIndex", "StartIndex can not be less than zero");
1440 if (startIndex >= this.length)
1441 throw new ArgumentOutOfRangeException ("startIndex", "StartIndex must be less than the length of the string");
1443 return Remove (startIndex, this.length - startIndex);
1446 public String PadLeft (int totalWidth)
1448 return PadLeft (totalWidth, ' ');
1451 public unsafe String PadLeft (int totalWidth, char paddingChar)
1453 //LAMESPEC: MSDN Doc says this is reversed for RtL languages, but this seems to be untrue
1455 if (totalWidth < 0)
1456 throw new ArgumentOutOfRangeException ("totalWidth", "< 0");
1458 if (totalWidth < this.length)
1459 return this;
1461 String tmp = InternalAllocateStr (totalWidth);
1463 fixed (char* dest = tmp, src = this) {
1464 char* padPos = dest;
1465 char* padTo = dest + (totalWidth - length);
1466 while (padPos != padTo)
1467 *padPos++ = paddingChar;
1469 CharCopy (padTo, src, length);
1471 return tmp;
1474 public String PadRight (int totalWidth)
1476 return PadRight (totalWidth, ' ');
1479 public unsafe String PadRight (int totalWidth, char paddingChar)
1481 //LAMESPEC: MSDN Doc says this is reversed for RtL languages, but this seems to be untrue
1483 if (totalWidth < 0)
1484 throw new ArgumentOutOfRangeException ("totalWidth", "< 0");
1486 if (totalWidth < this.length)
1487 return this;
1488 if (totalWidth == 0)
1489 return String.Empty;
1491 String tmp = InternalAllocateStr (totalWidth);
1493 fixed (char* dest = tmp, src = this) {
1494 CharCopy (dest, src, length);
1496 char* padPos = dest + length;
1497 char* padTo = dest + totalWidth;
1498 while (padPos != padTo)
1499 *padPos++ = paddingChar;
1501 return tmp;
1504 public bool StartsWith (String value)
1506 if (value == null)
1507 throw new ArgumentNullException ("value");
1509 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix (this, value, CompareOptions.None);
1512 [ComVisible (false)]
1513 public bool StartsWith (string value, StringComparison comparisonType)
1515 if (value == null)
1516 throw new ArgumentNullException ("value");
1518 switch (comparisonType) {
1519 case StringComparison.CurrentCulture:
1520 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix (this, value, CompareOptions.None);
1521 case StringComparison.CurrentCultureIgnoreCase:
1522 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix (this, value, CompareOptions.IgnoreCase);
1523 case StringComparison.InvariantCulture:
1524 return CultureInfo.InvariantCulture.CompareInfo.IsPrefix (this, value, CompareOptions.None);
1525 case StringComparison.InvariantCultureIgnoreCase:
1526 return CultureInfo.InvariantCulture.CompareInfo.IsPrefix (this, value, CompareOptions.IgnoreCase);
1527 case StringComparison.Ordinal:
1528 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix (this, value, CompareOptions.Ordinal);
1529 case StringComparison.OrdinalIgnoreCase:
1530 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix (this, value, CompareOptions.OrdinalIgnoreCase);
1531 default:
1532 string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType);
1533 throw new ArgumentException (msg, "comparisonType");
1537 [ComVisible (false)]
1538 public bool EndsWith (string value, StringComparison comparisonType)
1540 if (value == null)
1541 throw new ArgumentNullException ("value");
1543 switch (comparisonType) {
1544 case StringComparison.CurrentCulture:
1545 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix (this, value, CompareOptions.None);
1546 case StringComparison.CurrentCultureIgnoreCase:
1547 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix (this, value, CompareOptions.IgnoreCase);
1548 case StringComparison.InvariantCulture:
1549 return CultureInfo.InvariantCulture.CompareInfo.IsSuffix (this, value, CompareOptions.None);
1550 case StringComparison.InvariantCultureIgnoreCase:
1551 return CultureInfo.InvariantCulture.CompareInfo.IsSuffix (this, value, CompareOptions.IgnoreCase);
1552 case StringComparison.Ordinal:
1553 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix (this, value, CompareOptions.Ordinal);
1554 case StringComparison.OrdinalIgnoreCase:
1555 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix (this, value, CompareOptions.OrdinalIgnoreCase);
1556 default:
1557 string msg = Locale.GetText ("Invalid value '{0}' for StringComparison", comparisonType);
1558 throw new ArgumentException (msg, "comparisonType");
1562 public bool StartsWith (String value, bool ignoreCase, CultureInfo culture)
1564 if (culture == null)
1565 culture = CultureInfo.CurrentCulture;
1567 return culture.CompareInfo.IsPrefix (this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
1570 // Following method is culture-insensitive
1571 public unsafe String Replace (char oldChar, char newChar)
1573 if (this.length == 0 || oldChar == newChar)
1574 return this;
1576 int start_pos = IndexOfUnchecked (oldChar, 0, this.length);
1577 if (start_pos == -1)
1578 return this;
1580 if (start_pos < 4)
1581 start_pos = 0;
1583 string tmp = InternalAllocateStr (length);
1584 fixed (char* dest = tmp, src = &start_char) {
1585 if (start_pos != 0)
1586 CharCopy (dest, src, start_pos);
1588 char* end_ptr = dest + length;
1589 char* dest_ptr = dest + start_pos;
1590 char* src_ptr = src + start_pos;
1592 while (dest_ptr != end_ptr) {
1593 if (*src_ptr == oldChar)
1594 *dest_ptr = newChar;
1595 else
1596 *dest_ptr = *src_ptr;
1598 ++src_ptr;
1599 ++dest_ptr;
1602 return tmp;
1605 // culture-insensitive using ordinal search (See testcase StringTest.ReplaceStringCultureTests)
1606 public String Replace (String oldValue, String newValue)
1608 // LAMESPEC: According to MSDN the following method is culture-sensitive but this seems to be incorrect
1609 // LAMESPEC: Result is undefined if result length is longer than maximum string length
1611 if (oldValue == null)
1612 throw new ArgumentNullException ("oldValue");
1614 if (oldValue.Length == 0)
1615 throw new ArgumentException ("oldValue is the empty string.");
1617 if (this.Length == 0)
1618 return this;
1620 if (newValue == null)
1621 newValue = String.Empty;
1623 return ReplaceUnchecked (oldValue, newValue);
1626 private unsafe String ReplaceUnchecked (String oldValue, String newValue)
1628 if (oldValue.length > length)
1629 return this;
1630 if (oldValue.length == 1 && newValue.length == 1) {
1631 return Replace (oldValue[0], newValue[0]);
1632 // ENHANCE: It would be possible to special case oldValue.length == newValue.length
1633 // because the length of the result would be this.length and length calculation unneccesary
1636 const int maxValue = 200; // Allocate 800 byte maximum
1637 int* dat = stackalloc int[maxValue];
1638 fixed (char* source = this, replace = newValue) {
1639 int i = 0, count = 0;
1640 while (i < length) {
1641 int found = IndexOfOrdinalUnchecked (oldValue, i, length - i);
1642 if (found < 0)
1643 break;
1644 else {
1645 if (count < maxValue)
1646 dat[count++] = found;
1647 else
1648 return ReplaceFallback (oldValue, newValue, maxValue);
1650 i = found + oldValue.length;
1652 if (count == 0)
1653 return this;
1654 int nlen = this.length + ((newValue.length - oldValue.length) * count);
1655 String tmp = InternalAllocateStr (nlen);
1657 int curPos = 0, lastReadPos = 0;
1658 fixed (char* dest = tmp) {
1659 for (int j = 0; j < count; j++) {
1660 int precopy = dat[j] - lastReadPos;
1661 CharCopy (dest + curPos, source + lastReadPos, precopy);
1662 curPos += precopy;
1663 lastReadPos = dat[j] + oldValue.length;
1664 CharCopy (dest + curPos, replace, newValue.length);
1665 curPos += newValue.length;
1667 CharCopy (dest + curPos, source + lastReadPos, length - lastReadPos);
1669 return tmp;
1673 private String ReplaceFallback (String oldValue, String newValue, int testedCount)
1675 int lengthEstimate = this.length + ((newValue.length - oldValue.length) * testedCount);
1676 StringBuilder sb = new StringBuilder (lengthEstimate);
1677 for (int i = 0; i < length;) {
1678 int found = IndexOfOrdinalUnchecked (oldValue, i, length - i);
1679 if (found < 0) {
1680 sb.Append (SubstringUnchecked (i, length - i));
1681 break;
1683 sb.Append (SubstringUnchecked (i, found - i));
1684 sb.Append (newValue);
1685 i = found + oldValue.Length;
1687 return sb.ToString ();
1691 public unsafe String Remove (int startIndex, int count)
1693 if (startIndex < 0)
1694 throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative.");
1695 if (count < 0)
1696 throw new ArgumentOutOfRangeException ("count", "Cannot be negative.");
1697 if (startIndex > this.length - count)
1698 throw new ArgumentOutOfRangeException ("count", "startIndex + count > this.length");
1700 String tmp = InternalAllocateStr (this.length - count);
1702 fixed (char *dest = tmp, src = this) {
1703 char *dst = dest;
1704 CharCopy (dst, src, startIndex);
1705 int skip = startIndex + count;
1706 dst += startIndex;
1707 CharCopy (dst, src + skip, length - skip);
1709 return tmp;
1712 public String ToLower ()
1714 return ToLower (CultureInfo.CurrentCulture);
1717 public String ToLower (CultureInfo culture)
1719 if (culture == null)
1720 throw new ArgumentNullException ("culture");
1722 if (culture.LCID == 0x007F) // Invariant
1723 return ToLowerInvariant ();
1725 return culture.TextInfo.ToLower (this);
1728 public unsafe String ToLowerInvariant ()
1730 if (length == 0)
1731 return String.Empty;
1733 string tmp = InternalAllocateStr (length);
1734 fixed (char* source = &start_char, dest = tmp) {
1736 char* destPtr = (char*)dest;
1737 char* sourcePtr = (char*)source;
1739 for (int n = 0; n < length; n++) {
1740 *destPtr = Char.ToLowerInvariant (*sourcePtr);
1741 sourcePtr++;
1742 destPtr++;
1745 return tmp;
1748 public String ToUpper ()
1750 return ToUpper (CultureInfo.CurrentCulture);
1753 public String ToUpper (CultureInfo culture)
1755 if (culture == null)
1756 throw new ArgumentNullException ("culture");
1758 if (culture.LCID == 0x007F) // Invariant
1759 return ToUpperInvariant ();
1761 return culture.TextInfo.ToUpper (this);
1764 public unsafe String ToUpperInvariant ()
1766 if (length == 0)
1767 return String.Empty;
1769 string tmp = InternalAllocateStr (length);
1770 fixed (char* source = &start_char, dest = tmp) {
1772 char* destPtr = (char*)dest;
1773 char* sourcePtr = (char*)source;
1775 for (int n = 0; n < length; n++) {
1776 *destPtr = Char.ToUpperInvariant (*sourcePtr);
1777 sourcePtr++;
1778 destPtr++;
1781 return tmp;
1784 public override String ToString ()
1786 return this;
1789 public String ToString (IFormatProvider provider)
1791 return this;
1794 public static String Format (String format, Object arg0)
1796 return Format (null, format, new Object[] {arg0});
1799 public static String Format (String format, Object arg0, Object arg1)
1801 return Format (null, format, new Object[] {arg0, arg1});
1804 public static String Format (String format, Object arg0, Object arg1, Object arg2)
1806 return Format (null, format, new Object[] {arg0, arg1, arg2});
1809 public static string Format (string format, params object[] args)
1811 return Format (null, format, args);
1814 public static string Format (IFormatProvider provider, string format, params object[] args)
1816 StringBuilder b = FormatHelper (null, provider, format, args);
1817 return b.ToString ();
1820 internal static StringBuilder FormatHelper (StringBuilder result, IFormatProvider provider, string format, params object[] args)
1822 if (format == null)
1823 throw new ArgumentNullException ("format");
1824 if (args == null)
1825 throw new ArgumentNullException ("args");
1827 if (result == null) {
1828 /* Try to approximate the size of result to avoid reallocations */
1829 int i, len;
1831 len = 0;
1832 for (i = 0; i < args.Length; ++i) {
1833 string s = args [i] as string;
1834 if (s != null)
1835 len += s.length;
1836 else
1837 break;
1839 if (i == args.Length)
1840 result = new StringBuilder (len + format.length);
1841 else
1842 result = new StringBuilder ();
1845 int ptr = 0;
1846 int start = ptr;
1847 while (ptr < format.length) {
1848 char c = format[ptr ++];
1850 if (c == '{') {
1851 result.Append (format, start, ptr - start - 1);
1853 // check for escaped open bracket
1855 if (format[ptr] == '{') {
1856 start = ptr ++;
1857 continue;
1860 // parse specifier
1862 int n, width;
1863 bool left_align;
1864 string arg_format;
1866 ParseFormatSpecifier (format, ref ptr, out n, out width, out left_align, out arg_format);
1867 if (n >= args.Length)
1868 throw new FormatException ("Index (zero based) must be greater than or equal to zero and less than the size of the argument list.");
1870 // format argument
1872 object arg = args[n];
1874 string str;
1875 ICustomFormatter formatter = null;
1876 if (provider != null)
1877 formatter = provider.GetFormat (typeof (ICustomFormatter))
1878 as ICustomFormatter;
1879 if (arg == null)
1880 str = String.Empty;
1881 else if (formatter != null)
1882 str = formatter.Format (arg_format, arg, provider);
1883 else if (arg is IFormattable)
1884 str = ((IFormattable)arg).ToString (arg_format, provider);
1885 else
1886 str = arg.ToString ();
1888 // pad formatted string and append to result
1890 if (width > str.length) {
1891 const char padchar = ' ';
1892 int padlen = width - str.length;
1894 if (left_align) {
1895 result.Append (str);
1896 result.Append (padchar, padlen);
1898 else {
1899 result.Append (padchar, padlen);
1900 result.Append (str);
1903 else
1904 result.Append (str);
1906 start = ptr;
1908 else if (c == '}' && ptr < format.length && format[ptr] == '}') {
1909 result.Append (format, start, ptr - start - 1);
1910 start = ptr ++;
1912 else if (c == '}') {
1913 throw new FormatException ("Input string was not in a correct format.");
1917 if (start < format.length)
1918 result.Append (format, start, format.Length - start);
1920 return result;
1923 public unsafe static String Copy (String str)
1925 if (str == null)
1926 throw new ArgumentNullException ("str");
1928 int length = str.length;
1930 String tmp = InternalAllocateStr (length);
1931 if (length != 0) {
1932 fixed (char *dest = tmp, src = str) {
1933 CharCopy (dest, src, length);
1936 return tmp;
1939 public static String Concat (Object arg0)
1941 if (arg0 == null)
1942 return String.Empty;
1944 return arg0.ToString ();
1947 public static String Concat (Object arg0, Object arg1)
1949 return Concat ((arg0 != null) ? arg0.ToString () : null, (arg1 != null) ? arg1.ToString () : null);
1952 public static String Concat (Object arg0, Object arg1, Object arg2)
1954 string s1, s2, s3;
1955 if (arg0 == null)
1956 s1 = String.Empty;
1957 else
1958 s1 = arg0.ToString ();
1960 if (arg1 == null)
1961 s2 = String.Empty;
1962 else
1963 s2 = arg1.ToString ();
1965 if (arg2 == null)
1966 s3 = String.Empty;
1967 else
1968 s3 = arg2.ToString ();
1970 return Concat (s1, s2, s3);
1973 [CLSCompliant(false)]
1974 public static String Concat (Object arg0, Object arg1, Object arg2,
1975 Object arg3, __arglist)
1977 string s1, s2, s3, s4;
1979 if (arg0 == null)
1980 s1 = String.Empty;
1981 else
1982 s1 = arg0.ToString ();
1984 if (arg1 == null)
1985 s2 = String.Empty;
1986 else
1987 s2 = arg1.ToString ();
1989 if (arg2 == null)
1990 s3 = String.Empty;
1991 else
1992 s3 = arg2.ToString ();
1994 ArgIterator iter = new ArgIterator (__arglist);
1995 int argCount = iter.GetRemainingCount();
1997 StringBuilder sb = new StringBuilder ();
1998 if (arg3 != null)
1999 sb.Append (arg3.ToString ());
2001 for (int i = 0; i < argCount; i++) {
2002 TypedReference typedRef = iter.GetNextArg ();
2003 sb.Append (TypedReference.ToObject (typedRef));
2006 s4 = sb.ToString ();
2008 return Concat (s1, s2, s3, s4);
2011 public unsafe static String Concat (String str0, String str1)
2013 if (str0 == null || str0.Length == 0) {
2014 if (str1 == null || str1.Length == 0)
2015 return String.Empty;
2016 return str1;
2019 if (str1 == null || str1.Length == 0)
2020 return str0;
2022 String tmp = InternalAllocateStr (str0.length + str1.length);
2024 fixed (char *dest = tmp, src = str0)
2025 CharCopy (dest, src, str0.length);
2026 fixed (char *dest = tmp, src = str1)
2027 CharCopy (dest + str0.Length, src, str1.length);
2029 return tmp;
2032 public unsafe static String Concat (String str0, String str1, String str2)
2034 if (str0 == null || str0.Length == 0){
2035 if (str1 == null || str1.Length == 0){
2036 if (str2 == null || str2.Length == 0)
2037 return String.Empty;
2038 return str2;
2039 } else {
2040 if (str2 == null || str2.Length == 0)
2041 return str1;
2043 str0 = String.Empty;
2044 } else {
2045 if (str1 == null || str1.Length == 0){
2046 if (str2 == null || str2.Length == 0)
2047 return str0;
2048 else
2049 str1 = String.Empty;
2050 } else {
2051 if (str2 == null || str2.Length == 0)
2052 str2 = String.Empty;
2056 String tmp = InternalAllocateStr (str0.length + str1.length + str2.length);
2058 if (str0.Length != 0) {
2059 fixed (char *dest = tmp, src = str0) {
2060 CharCopy (dest, src, str0.length);
2063 if (str1.Length != 0) {
2064 fixed (char *dest = tmp, src = str1) {
2065 CharCopy (dest + str0.Length, src, str1.length);
2068 if (str2.Length != 0) {
2069 fixed (char *dest = tmp, src = str2) {
2070 CharCopy (dest + str0.Length + str1.Length, src, str2.length);
2074 return tmp;
2077 public unsafe static String Concat (String str0, String str1, String str2, String str3)
2079 if (str0 == null && str1 == null && str2 == null && str3 == null)
2080 return String.Empty;
2082 if (str0 == null)
2083 str0 = String.Empty;
2084 if (str1 == null)
2085 str1 = String.Empty;
2086 if (str2 == null)
2087 str2 = String.Empty;
2088 if (str3 == null)
2089 str3 = String.Empty;
2091 String tmp = InternalAllocateStr (str0.length + str1.length + str2.length + str3.length);
2093 if (str0.Length != 0) {
2094 fixed (char *dest = tmp, src = str0) {
2095 CharCopy (dest, src, str0.length);
2098 if (str1.Length != 0) {
2099 fixed (char *dest = tmp, src = str1) {
2100 CharCopy (dest + str0.Length, src, str1.length);
2103 if (str2.Length != 0) {
2104 fixed (char *dest = tmp, src = str2) {
2105 CharCopy (dest + str0.Length + str1.Length, src, str2.length);
2108 if (str3.Length != 0) {
2109 fixed (char *dest = tmp, src = str3) {
2110 CharCopy (dest + str0.Length + str1.Length + str2.Length, src, str3.length);
2114 return tmp;
2117 public static String Concat (params Object[] args)
2119 if (args == null)
2120 throw new ArgumentNullException ("args");
2122 int argLen = args.Length;
2123 if (argLen == 0)
2124 return String.Empty;
2126 string [] strings = new string [argLen];
2127 int len = 0;
2128 for (int i = 0; i < argLen; i++) {
2129 if (args[i] != null) {
2130 strings[i] = args[i].ToString ();
2131 len += strings[i].length;
2135 return ConcatInternal (strings, len);
2138 public static String Concat (params String[] values)
2140 if (values == null)
2141 throw new ArgumentNullException ("values");
2143 int len = 0;
2144 for (int i = 0; i < values.Length; i++) {
2145 String s = values[i];
2146 if (s != null)
2147 len += s.length;
2150 return ConcatInternal (values, len);
2153 private static unsafe String ConcatInternal (String[] values, int length)
2155 if (length == 0)
2156 return String.Empty;
2158 String tmp = InternalAllocateStr (length);
2160 fixed (char* dest = tmp) {
2161 int pos = 0;
2162 for (int i = 0; i < values.Length; i++) {
2163 String source = values[i];
2164 if (source != null) {
2165 fixed (char* src = source) {
2166 CharCopy (dest + pos, src, source.length);
2168 pos += source.Length;
2172 return tmp;
2175 public unsafe String Insert (int startIndex, String value)
2177 if (value == null)
2178 throw new ArgumentNullException ("value");
2180 if (startIndex < 0 || startIndex > this.length)
2181 throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative and must be less than or equal to length of string.");
2183 if (value.Length == 0)
2184 return this;
2185 if (this.Length == 0)
2186 return value;
2187 String tmp = InternalAllocateStr (this.length + value.length);
2189 fixed (char *dest = tmp, src = this, val = value) {
2190 char *dst = dest;
2191 CharCopy (dst, src, startIndex);
2192 dst += startIndex;
2193 CharCopy (dst, val, value.length);
2194 dst += value.length;
2195 CharCopy (dst, src + startIndex, length - startIndex);
2197 return tmp;
2200 public static string Intern (string str)
2202 if (str == null)
2203 throw new ArgumentNullException ("str");
2205 return InternalIntern (str);
2208 public static string IsInterned (string str)
2210 if (str == null)
2211 throw new ArgumentNullException ("str");
2213 return InternalIsInterned (str);
2216 public static string Join (string separator, string [] value)
2218 if (value == null)
2219 throw new ArgumentNullException ("value");
2220 if (separator == null)
2221 separator = String.Empty;
2223 return JoinUnchecked (separator, value, 0, value.Length);
2226 public static string Join (string separator, string[] value, int startIndex, int count)
2228 if (value == null)
2229 throw new ArgumentNullException ("value");
2230 if (startIndex < 0)
2231 throw new ArgumentOutOfRangeException ("startIndex", "< 0");
2232 if (count < 0)
2233 throw new ArgumentOutOfRangeException ("count", "< 0");
2234 if (startIndex > value.Length - count)
2235 throw new ArgumentOutOfRangeException ("startIndex", "startIndex + count > value.length");
2237 if (startIndex == value.Length)
2238 return String.Empty;
2239 if (separator == null)
2240 separator = String.Empty;
2242 return JoinUnchecked (separator, value, startIndex, count);
2245 private static unsafe string JoinUnchecked (string separator, string[] value, int startIndex, int count)
2247 // Unchecked parameters
2248 // startIndex, count must be >= 0; startIndex + count must be <= value.length
2249 // separator and value must not be null
2251 int length = 0;
2252 int maxIndex = startIndex + count;
2253 // Precount the number of characters that the resulting string will have
2254 for (int i = startIndex; i < maxIndex; i++) {
2255 String s = value[i];
2256 if (s != null)
2257 length += s.length;
2259 length += separator.length * (count - 1);
2260 if (length <= 0)
2261 return String.Empty;
2263 String tmp = InternalAllocateStr (length);
2265 maxIndex--;
2266 fixed (char* dest = tmp, sepsrc = separator) {
2267 // Copy each string from value except the last one and add a separator for each
2268 int pos = 0;
2269 for (int i = startIndex; i < maxIndex; i++) {
2270 String source = value[i];
2271 if (source != null) {
2272 if (source.Length > 0) {
2273 fixed (char* src = source)
2274 CharCopy (dest + pos, src, source.Length);
2275 pos += source.Length;
2278 if (separator.Length > 0) {
2279 CharCopy (dest + pos, sepsrc, separator.Length);
2280 pos += separator.Length;
2283 // Append last string that does not get an additional separator
2284 String sourceLast = value[maxIndex];
2285 if (sourceLast != null) {
2286 if (sourceLast.Length > 0) {
2287 fixed (char* src = sourceLast)
2288 CharCopy (dest + pos, src, sourceLast.Length);
2292 return tmp;
2295 bool IConvertible.ToBoolean (IFormatProvider provider)
2297 return Convert.ToBoolean (this, provider);
2300 byte IConvertible.ToByte (IFormatProvider provider)
2302 return Convert.ToByte (this, provider);
2305 char IConvertible.ToChar (IFormatProvider provider)
2307 return Convert.ToChar (this, provider);
2310 DateTime IConvertible.ToDateTime (IFormatProvider provider)
2312 return Convert.ToDateTime (this, provider);
2315 decimal IConvertible.ToDecimal (IFormatProvider provider)
2317 return Convert.ToDecimal (this, provider);
2320 double IConvertible.ToDouble (IFormatProvider provider)
2322 return Convert.ToDouble (this, provider);
2325 short IConvertible.ToInt16 (IFormatProvider provider)
2327 return Convert.ToInt16 (this, provider);
2330 int IConvertible.ToInt32 (IFormatProvider provider)
2332 return Convert.ToInt32 (this, provider);
2335 long IConvertible.ToInt64 (IFormatProvider provider)
2337 return Convert.ToInt64 (this, provider);
2340 sbyte IConvertible.ToSByte (IFormatProvider provider)
2342 return Convert.ToSByte (this, provider);
2345 float IConvertible.ToSingle (IFormatProvider provider)
2347 return Convert.ToSingle (this, provider);
2350 object IConvertible.ToType (Type targetType, IFormatProvider provider)
2352 if (targetType == null)
2353 throw new ArgumentNullException ("type");
2354 return Convert.ToType (this, targetType, provider, false);
2357 ushort IConvertible.ToUInt16 (IFormatProvider provider)
2359 return Convert.ToUInt16 (this, provider);
2362 uint IConvertible.ToUInt32 (IFormatProvider provider)
2364 return Convert.ToUInt32 (this, provider);
2367 ulong IConvertible.ToUInt64 (IFormatProvider provider)
2369 return Convert.ToUInt64 (this, provider);
2372 public int Length {
2373 get {
2374 return length;
2378 public CharEnumerator GetEnumerator ()
2380 return new CharEnumerator (this);
2383 IEnumerator<char> IEnumerable<char>.GetEnumerator ()
2385 return new CharEnumerator (this);
2388 IEnumerator IEnumerable.GetEnumerator ()
2390 return new CharEnumerator (this);
2393 private static void ParseFormatSpecifier (string str, ref int ptr, out int n, out int width,
2394 out bool left_align, out string format)
2396 // parses format specifier of form:
2397 // N,[\ +[-]M][:F]}
2399 // where:
2401 try {
2402 // N = argument number (non-negative integer)
2404 n = ParseDecimal (str, ref ptr);
2405 if (n < 0)
2406 throw new FormatException ("Input string was not in a correct format.");
2408 // M = width (non-negative integer)
2410 if (str[ptr] == ',') {
2411 // White space between ',' and number or sign.
2412 ++ptr;
2413 while (Char.IsWhiteSpace (str [ptr]))
2414 ++ptr;
2415 int start = ptr;
2417 format = str.Substring (start, ptr - start);
2419 left_align = (str [ptr] == '-');
2420 if (left_align)
2421 ++ ptr;
2423 width = ParseDecimal (str, ref ptr);
2424 if (width < 0)
2425 throw new FormatException ("Input string was not in a correct format.");
2427 else {
2428 width = 0;
2429 left_align = false;
2430 format = String.Empty;
2433 // F = argument format (string)
2435 if (str[ptr] == ':') {
2436 int start = ++ ptr;
2437 while (str[ptr] != '}')
2438 ++ ptr;
2440 format += str.Substring (start, ptr - start);
2442 else
2443 format = null;
2445 if (str[ptr ++] != '}')
2446 throw new FormatException ("Input string was not in a correct format.");
2448 catch (IndexOutOfRangeException) {
2449 throw new FormatException ("Input string was not in a correct format.");
2453 private static int ParseDecimal (string str, ref int ptr)
2455 int p = ptr;
2456 int n = 0;
2457 while (true) {
2458 char c = str[p];
2459 if (c < '0' || '9' < c)
2460 break;
2462 n = n * 10 + c - '0';
2463 ++ p;
2466 if (p == ptr)
2467 return -1;
2469 ptr = p;
2470 return n;
2473 internal unsafe void InternalSetChar (int idx, char val)
2475 if ((uint) idx >= (uint) Length)
2476 throw new ArgumentOutOfRangeException ("idx");
2478 fixed (char * pStr = &start_char)
2480 pStr [idx] = val;
2484 internal unsafe void InternalSetLength (int newLength)
2486 if (newLength > length)
2487 throw new ArgumentOutOfRangeException ("newLength", "newLength as to be <= length");
2489 // zero terminate, we can pass string objects directly via pinvoke
2490 // we also zero the rest of the string, since the new GC needs to be
2491 // able to handle the changing size (it will skip the 0 bytes).
2492 fixed (char * pStr = &start_char) {
2493 char *p = pStr + newLength;
2494 char *end = pStr + length;
2495 while (p < end) {
2496 p [0] = '\0';
2497 p++;
2500 length = newLength;
2503 [ReliabilityContractAttribute (Consistency.WillNotCorruptState, Cer.MayFail)]
2504 // When modifying it, GetCaseInsensitiveHashCode() should be modified as well.
2505 public unsafe override int GetHashCode ()
2507 fixed (char * c = this) {
2508 char * cc = c;
2509 char * end = cc + length - 1;
2510 int h = 0;
2511 for (;cc < end; cc += 2) {
2512 h = (h << 5) - h + *cc;
2513 h = (h << 5) - h + cc [1];
2515 ++end;
2516 if (cc < end)
2517 h = (h << 5) - h + *cc;
2518 return h;
2522 #if MOONLIGHT || NET_4_0
2523 public static bool IsNullOrWhiteSpace (string value)
2525 if (value == null)
2526 return true;
2527 foreach (char c in value)
2528 if (!Char.IsWhiteSpace (c))
2529 return false;
2530 return true;
2533 [ComVisible(false)]
2534 public static string Concat (IEnumerable<string> values)
2536 if (values == null)
2537 throw new ArgumentNullException ("values");
2539 var stringList = new List<string> ();
2540 int len = 0;
2541 foreach (var v in values){
2542 if (v == null)
2543 continue;
2544 len += v.Length;
2545 stringList.Add (v);
2547 return ConcatInternal (stringList.ToArray (), len);
2550 [ComVisibleAttribute(false)]
2551 public static string Concat<T> (IEnumerable<T> values)
2553 if (values == null)
2554 throw new ArgumentNullException ("values");
2556 var stringList = new List<string> ();
2557 int len = 0;
2558 foreach (var v in values){
2559 string sr = v.ToString ();
2560 len += sr.Length;
2561 stringList.Add (sr);
2563 return ConcatInternal (stringList.ToArray (), len);
2566 [ComVisibleAttribute(false)]
2567 public static string Join (string separator, IEnumerable<string> values)
2569 if (separator == null)
2570 return Concat (values);
2572 if (values == null)
2573 throw new ArgumentNullException ("values");
2575 var stringList = new List<string> ();
2576 foreach (var v in values)
2577 stringList.Add (v);
2579 return JoinUnchecked (separator, stringList.ToArray (), 0, stringList.Count);
2582 [ComVisibleAttribute(false)]
2583 public static string Join (string separator, params object [] values)
2585 if (separator == null)
2586 return Concat (values);
2588 if (values == null)
2589 throw new ArgumentNullException ("values");
2591 var strCopy = new string [values.Length];
2592 int i = 0;
2593 foreach (var v in values)
2594 strCopy [i++] = v.ToString ();
2596 return JoinUnchecked (separator, strCopy, 0, strCopy.Length);
2599 [ComVisible (false)]
2600 public static string Join<T> (string separator, IEnumerable<T> values)
2602 if (separator == null)
2603 return Concat<T> (values);
2605 if (values == null)
2606 throw new ArgumentNullException ("values");
2608 var stringList = new List<string> ();
2609 foreach (var v in values)
2610 stringList.Add (v.ToString ());
2612 return JoinUnchecked (separator, stringList.ToArray (), 0, stringList.Count);
2614 #endif
2615 internal unsafe int GetCaseInsensitiveHashCode ()
2617 fixed (char * c = this) {
2618 char * cc = c;
2619 char * end = cc + length - 1;
2620 int h = 0;
2621 for (;cc < end; cc += 2) {
2622 h = (h << 5) - h + Char.ToUpperInvariant (*cc);
2623 h = (h << 5) - h + Char.ToUpperInvariant (cc [1]);
2625 ++end;
2626 if (cc < end)
2627 h = (h << 5) - h + Char.ToUpperInvariant (*cc);
2628 return h;
2632 // Certain constructors are redirected to CreateString methods with
2633 // matching argument list. The this pointer should not be used.
2634 #pragma warning disable 169
2635 private unsafe String CreateString (sbyte* value)
2637 if (value == null)
2638 return String.Empty;
2640 byte* bytes = (byte*) value;
2641 int length = 0;
2643 try {
2644 while (bytes++ [0] != 0)
2645 length++;
2646 } catch (NullReferenceException) {
2647 throw new ArgumentOutOfRangeException ("ptr", "Value does not refer to a valid string.");
2648 } catch (AccessViolationException) {
2649 throw new ArgumentOutOfRangeException ("ptr", "Value does not refer to a valid string.");
2652 return CreateString (value, 0, length, null);
2655 private unsafe String CreateString (sbyte* value, int startIndex, int length)
2657 return CreateString (value, startIndex, length, null);
2660 private unsafe String CreateString (sbyte* value, int startIndex, int length, Encoding enc)
2662 if (length < 0)
2663 throw new ArgumentOutOfRangeException ("length", "Non-negative number required.");
2664 if (startIndex < 0)
2665 throw new ArgumentOutOfRangeException ("startIndex", "Non-negative number required.");
2666 if (value + startIndex < value)
2667 throw new ArgumentOutOfRangeException ("startIndex", "Value, startIndex and length do not refer to a valid string.");
2669 bool isDefaultEncoding;
2671 if (isDefaultEncoding = (enc == null)) {
2672 if (value == null)
2673 throw new ArgumentNullException ("value");
2674 if (length == 0)
2675 return String.Empty;
2677 enc = Encoding.Default;
2680 byte [] bytes = new byte [length];
2682 if (length != 0)
2683 fixed (byte* bytePtr = bytes)
2684 try {
2685 memcpy (bytePtr, (byte*) (value + startIndex), length);
2686 } catch (NullReferenceException) {
2687 throw new ArgumentOutOfRangeException ("ptr", "Value, startIndex and length do not refer to a valid string.");
2688 } catch (AccessViolationException) {
2689 if (!isDefaultEncoding)
2690 throw;
2692 throw new ArgumentOutOfRangeException ("value", "Value, startIndex and length do not refer to a valid string.");
2695 // GetString () is called even when length == 0
2696 return enc.GetString (bytes);
2699 unsafe string CreateString (char *value)
2701 if (value == null)
2702 return string.Empty;
2703 char *p = value;
2704 int i = 0;
2705 while (*p != 0) {
2706 ++i;
2707 ++p;
2709 string result = InternalAllocateStr (i);
2711 if (i != 0) {
2712 fixed (char *dest = result) {
2713 CharCopy (dest, value, i);
2716 return result;
2719 unsafe string CreateString (char *value, int startIndex, int length)
2721 if (length == 0)
2722 return string.Empty;
2723 if (value == null)
2724 throw new ArgumentNullException ("value");
2725 if (startIndex < 0)
2726 throw new ArgumentOutOfRangeException ("startIndex");
2727 if (length < 0)
2728 throw new ArgumentOutOfRangeException ("length");
2730 string result = InternalAllocateStr (length);
2732 fixed (char *dest = result) {
2733 CharCopy (dest, value + startIndex, length);
2735 return result;
2738 unsafe string CreateString (char [] val, int startIndex, int length)
2740 if (val == null)
2741 throw new ArgumentNullException ("value");
2742 if (startIndex < 0)
2743 throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative.");
2744 if (length < 0)
2745 throw new ArgumentOutOfRangeException ("length", "Cannot be negative.");
2746 if (startIndex > val.Length - length)
2747 throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative, and should be less than length of string.");
2748 if (length == 0)
2749 return string.Empty;
2751 string result = InternalAllocateStr (length);
2753 fixed (char *dest = result, src = val) {
2754 CharCopy (dest, src + startIndex, length);
2756 return result;
2759 unsafe string CreateString (char [] val)
2761 if (val == null)
2762 return string.Empty;
2763 if (val.Length == 0)
2764 return string.Empty;
2765 string result = InternalAllocateStr (val.Length);
2767 fixed (char *dest = result, src = val) {
2768 CharCopy (dest, src, val.Length);
2770 return result;
2773 unsafe string CreateString (char c, int count)
2775 if (count < 0)
2776 throw new ArgumentOutOfRangeException ("count");
2777 if (count == 0)
2778 return string.Empty;
2779 string result = InternalAllocateStr (count);
2780 fixed (char *dest = result) {
2781 char *p = dest;
2782 char *end = p + count;
2783 while (p < end) {
2784 *p = c;
2785 p++;
2788 return result;
2790 #pragma warning restore 169
2792 /* helpers used by the runtime as well as above or eslewhere in corlib */
2793 internal static unsafe void memset (byte *dest, int val, int len)
2795 if (len < 8) {
2796 while (len != 0) {
2797 *dest = (byte)val;
2798 ++dest;
2799 --len;
2801 return;
2803 if (val != 0) {
2804 val = val | (val << 8);
2805 val = val | (val << 16);
2807 // align to 4
2808 int rest = (int)dest & 3;
2809 if (rest != 0) {
2810 rest = 4 - rest;
2811 len -= rest;
2812 do {
2813 *dest = (byte)val;
2814 ++dest;
2815 --rest;
2816 } while (rest != 0);
2818 while (len >= 16) {
2819 ((int*)dest) [0] = val;
2820 ((int*)dest) [1] = val;
2821 ((int*)dest) [2] = val;
2822 ((int*)dest) [3] = val;
2823 dest += 16;
2824 len -= 16;
2826 while (len >= 4) {
2827 ((int*)dest) [0] = val;
2828 dest += 4;
2829 len -= 4;
2831 // tail bytes
2832 while (len > 0) {
2833 *dest = (byte)val;
2834 dest++;
2835 len--;
2839 static unsafe void memcpy4 (byte *dest, byte *src, int size) {
2840 /*while (size >= 32) {
2841 // using long is better than int and slower than double
2842 // FIXME: enable this only on correct alignment or on platforms
2843 // that can tolerate unaligned reads/writes of doubles
2844 ((double*)dest) [0] = ((double*)src) [0];
2845 ((double*)dest) [1] = ((double*)src) [1];
2846 ((double*)dest) [2] = ((double*)src) [2];
2847 ((double*)dest) [3] = ((double*)src) [3];
2848 dest += 32;
2849 src += 32;
2850 size -= 32;
2852 while (size >= 16) {
2853 ((int*)dest) [0] = ((int*)src) [0];
2854 ((int*)dest) [1] = ((int*)src) [1];
2855 ((int*)dest) [2] = ((int*)src) [2];
2856 ((int*)dest) [3] = ((int*)src) [3];
2857 dest += 16;
2858 src += 16;
2859 size -= 16;
2861 while (size >= 4) {
2862 ((int*)dest) [0] = ((int*)src) [0];
2863 dest += 4;
2864 src += 4;
2865 size -= 4;
2867 while (size > 0) {
2868 ((byte*)dest) [0] = ((byte*)src) [0];
2869 dest += 1;
2870 src += 1;
2871 --size;
2874 static unsafe void memcpy2 (byte *dest, byte *src, int size) {
2875 while (size >= 8) {
2876 ((short*)dest) [0] = ((short*)src) [0];
2877 ((short*)dest) [1] = ((short*)src) [1];
2878 ((short*)dest) [2] = ((short*)src) [2];
2879 ((short*)dest) [3] = ((short*)src) [3];
2880 dest += 8;
2881 src += 8;
2882 size -= 8;
2884 while (size >= 2) {
2885 ((short*)dest) [0] = ((short*)src) [0];
2886 dest += 2;
2887 src += 2;
2888 size -= 2;
2890 if (size > 0)
2891 ((byte*)dest) [0] = ((byte*)src) [0];
2893 static unsafe void memcpy1 (byte *dest, byte *src, int size) {
2894 while (size >= 8) {
2895 ((byte*)dest) [0] = ((byte*)src) [0];
2896 ((byte*)dest) [1] = ((byte*)src) [1];
2897 ((byte*)dest) [2] = ((byte*)src) [2];
2898 ((byte*)dest) [3] = ((byte*)src) [3];
2899 ((byte*)dest) [4] = ((byte*)src) [4];
2900 ((byte*)dest) [5] = ((byte*)src) [5];
2901 ((byte*)dest) [6] = ((byte*)src) [6];
2902 ((byte*)dest) [7] = ((byte*)src) [7];
2903 dest += 8;
2904 src += 8;
2905 size -= 8;
2907 while (size >= 2) {
2908 ((byte*)dest) [0] = ((byte*)src) [0];
2909 ((byte*)dest) [1] = ((byte*)src) [1];
2910 dest += 2;
2911 src += 2;
2912 size -= 2;
2914 if (size > 0)
2915 ((byte*)dest) [0] = ((byte*)src) [0];
2918 internal static unsafe void memcpy (byte *dest, byte *src, int size) {
2919 // FIXME: if pointers are not aligned, try to align them
2920 // so a faster routine can be used. Handle the case where
2921 // the pointers can't be reduced to have the same alignment
2922 // (just ignore the issue on x86?)
2923 if ((((int)dest | (int)src) & 3) != 0) {
2924 if (((int)dest & 1) != 0 && ((int)src & 1) != 0 && size >= 1) {
2925 dest [0] = src [0];
2926 ++dest;
2927 ++src;
2928 --size;
2930 if (((int)dest & 2) != 0 && ((int)src & 2) != 0 && size >= 2) {
2931 ((short*)dest) [0] = ((short*)src) [0];
2932 dest += 2;
2933 src += 2;
2934 size -= 2;
2936 if ((((int)dest | (int)src) & 1) != 0) {
2937 memcpy1 (dest, src, size);
2938 return;
2940 if ((((int)dest | (int)src) & 2) != 0) {
2941 memcpy2 (dest, src, size);
2942 return;
2945 memcpy4 (dest, src, size);
2948 internal static unsafe void CharCopy (char *dest, char *src, int count) {
2949 // Same rules as for memcpy, but with the premise that
2950 // chars can only be aligned to even addresses if their
2951 // enclosing types are correctly aligned
2952 if ((((int)(byte*)dest | (int)(byte*)src) & 3) != 0) {
2953 if (((int)(byte*)dest & 2) != 0 && ((int)(byte*)src & 2) != 0 && count > 0) {
2954 ((short*)dest) [0] = ((short*)src) [0];
2955 dest++;
2956 src++;
2957 count--;
2959 if ((((int)(byte*)dest | (int)(byte*)src) & 2) != 0) {
2960 memcpy2 ((byte*)dest, (byte*)src, count * 2);
2961 return;
2964 memcpy4 ((byte*)dest, (byte*)src, count * 2);
2967 internal static unsafe void CharCopyReverse (char *dest, char *src, int count)
2969 dest += count;
2970 src += count;
2971 for (int i = count; i > 0; i--) {
2972 dest--;
2973 src--;
2974 *dest = *src;
2978 internal static unsafe void CharCopy (String target, int targetIndex, String source, int sourceIndex, int count)
2980 fixed (char* dest = target, src = source)
2981 CharCopy (dest + targetIndex, src + sourceIndex, count);
2984 internal static unsafe void CharCopy (String target, int targetIndex, Char[] source, int sourceIndex, int count)
2986 fixed (char* dest = target, src = source)
2987 CharCopy (dest + targetIndex, src + sourceIndex, count);
2990 // Use this method if you cannot block copy from left to right (e.g. because you are coping within the same string)
2991 internal static unsafe void CharCopyReverse (String target, int targetIndex, String source, int sourceIndex, int count)
2993 fixed (char* dest = target, src = source)
2994 CharCopyReverse (dest + targetIndex, src + sourceIndex, count);
2997 [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)]
2998 unsafe public extern String (char *value);
3000 [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)]
3001 unsafe public extern String (char *value, int startIndex, int length);
3003 [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)]
3004 unsafe public extern String (sbyte *value);
3006 [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)]
3007 unsafe public extern String (sbyte *value, int startIndex, int length);
3009 [CLSCompliant (false), MethodImplAttribute (MethodImplOptions.InternalCall)]
3010 unsafe public extern String (sbyte *value, int startIndex, int length, Encoding enc);
3012 [MethodImplAttribute (MethodImplOptions.InternalCall)]
3013 public extern String (char [] value, int startIndex, int length);
3015 [MethodImplAttribute (MethodImplOptions.InternalCall)]
3016 public extern String (char [] value);
3018 [MethodImplAttribute (MethodImplOptions.InternalCall)]
3019 public extern String (char c, int count);
3021 [MethodImplAttribute (MethodImplOptions.InternalCall)]
3022 private extern String[] InternalSplit (char[] separator, int count, int options);
3024 [MethodImplAttribute (MethodImplOptions.InternalCall)]
3025 internal extern static String InternalAllocateStr (int length);
3027 [MethodImplAttribute (MethodImplOptions.InternalCall)]
3028 private extern static string InternalIntern (string str);
3030 [MethodImplAttribute (MethodImplOptions.InternalCall)]
3031 private extern static string InternalIsInterned (string str);
3033 [MethodImplAttribute (MethodImplOptions.InternalCall)]
3034 private extern static int GetLOSLimit ();