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.
5 using System.Collections;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.Runtime.CompilerServices;
9 using System.Runtime.ConstrainedExecution;
10 using System.Runtime.InteropServices;
14 public partial class String
17 //Native Static Methods
20 private unsafe static int CompareOrdinalIgnoreCaseHelper(String strA, String strB)
22 Debug.Assert(strA != null);
23 Debug.Assert(strB != null);
24 int length = Math.Min(strA.Length, strB.Length);
26 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
30 int charA = 0, charB = 0;
37 Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
39 // uppercase both chars - notice that we need just one compare per char
40 if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
41 if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
43 //Return the (case-insensitive) difference between them.
52 return strA.Length - strB.Length;
56 // native call to COMString::CompareOrdinalEx
57 [MethodImplAttribute(MethodImplOptions.InternalCall)]
58 internal static extern int CompareOrdinalHelper(String strA, int indexA, int countA, String strB, int indexB, int countB);
60 //This will not work in case-insensitive mode for any character greater than 0x80.
61 //We'll throw an ArgumentException.
62 [MethodImplAttribute(MethodImplOptions.InternalCall)]
63 unsafe internal static extern int nativeCompareOrdinalIgnoreCaseWC(String strA, sbyte* strBBytes);
67 // NATIVE INSTANCE METHODS
72 // Search/Query methods
75 private unsafe static bool EqualsHelper(String strA, String strB)
77 Debug.Assert(strA != null);
78 Debug.Assert(strB != null);
79 Debug.Assert(strA.Length == strB.Length);
81 int length = strA.Length;
83 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
89 // Single int read aligns pointers for the following long reads
90 // PERF: No length check needed as there is always an int32 worth of string allocated
91 // This read can also include the null terminator which both strings will have
92 if (*(int*)a != *(int*)b) return false;
93 length -= 2; a += 2; b += 2;
95 // for AMD64 bit platform we unroll by 12 and
96 // check 3 qword at a time. This is less code
97 // than the 32 bit case and is a shorter path length.
101 if (*(long*)a != *(long*)b) return false;
102 if (*(long*)(a + 4) != *(long*)(b + 4)) return false;
103 if (*(long*)(a + 8) != *(long*)(b + 8)) return false;
104 length -= 12; a += 12; b += 12;
109 if (*(int*)a != *(int*)b) return false;
110 if (*(int*)(a + 2) != *(int*)(b + 2)) return false;
111 if (*(int*)(a + 4) != *(int*)(b + 4)) return false;
112 if (*(int*)(a + 6) != *(int*)(b + 6)) return false;
113 if (*(int*)(a + 8) != *(int*)(b + 8)) return false;
114 length -= 10; a += 10; b += 10;
118 // This depends on the fact that the String objects are
119 // always zero terminated and that the terminating zero is not included
120 // in the length. For odd string sizes, the last compare will include
121 // the zero terminator.
124 if (*(int*)a != *(int*)b) return false;
125 length -= 2; a += 2; b += 2;
132 private unsafe static bool EqualsIgnoreCaseAsciiHelper(String strA, String strB)
134 Debug.Assert(strA != null);
135 Debug.Assert(strB != null);
136 Debug.Assert(strA.Length == strB.Length);
137 int length = strA.Length;
139 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
149 Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
151 // Ordinal equals or lowercase equals if the result ends up in the a-z range
152 if (charA == charB ||
153 ((charA | 0x20) == (charB | 0x20) &&
154 (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a')))
170 private unsafe static bool StartsWithOrdinalHelper(String str, String startsWith)
172 Debug.Assert(str != null);
173 Debug.Assert(startsWith != null);
174 Debug.Assert(str.Length >= startsWith.Length);
176 int length = startsWith.Length;
178 fixed (char* ap = &str._firstChar) fixed (char* bp = &startsWith._firstChar)
184 // Single int read aligns pointers for the following long reads
185 // No length check needed as this method is called when length >= 2
186 Debug.Assert(length >= 2);
187 if (*(int*)a != *(int*)b) return false;
188 length -= 2; a += 2; b += 2;
192 if (*(long*)a != *(long*)b) return false;
193 if (*(long*)(a + 4) != *(long*)(b + 4)) return false;
194 if (*(long*)(a + 8) != *(long*)(b + 8)) return false;
195 length -= 12; a += 12; b += 12;
200 if (*(int*)a != *(int*)b) return false;
201 if (*(int*)(a+2) != *(int*)(b+2)) return false;
202 if (*(int*)(a+4) != *(int*)(b+4)) return false;
203 if (*(int*)(a+6) != *(int*)(b+6)) return false;
204 if (*(int*)(a+8) != *(int*)(b+8)) return false;
205 length -= 10; a += 10; b += 10;
211 if (*(int*)a != *(int*)b) return false;
212 length -= 2; a += 2; b += 2;
215 // PERF: This depends on the fact that the String objects are always zero terminated
216 // and that the terminating zero is not included in the length. For even string sizes
217 // this compare can include the zero terminator. Bitwise OR avoids a branch.
218 return length == 0 | *a == *b;
222 private static unsafe int CompareOrdinalHelper(String strA, String strB)
224 Debug.Assert(strA != null);
225 Debug.Assert(strB != null);
227 // NOTE: This may be subject to change if eliminating the check
228 // in the callers makes them small enough to be inlined
229 Debug.Assert(strA._firstChar == strB._firstChar,
230 "For performance reasons, callers of this method should " +
231 "check/short-circuit beforehand if the first char is the same.");
233 int length = Math.Min(strA.Length, strB.Length);
235 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
240 // Check if the second chars are different here
241 // The reason we check if _firstChar is different is because
242 // it's the most common case and allows us to avoid a method call
244 // The reason we check if the second char is different is because
245 // if the first two chars the same we can increment by 4 bytes,
246 // leaving us word-aligned on both 32-bit (12 bytes into the string)
247 // and 64-bit (16 bytes) platforms.
249 // For empty strings, the second char will be null due to padding.
250 // The start of the string (not including sync block pointer)
251 // is the method table pointer + string length, which takes up
252 // 8 bytes on 32-bit, 12 on x64. For empty strings the null
253 // terminator immediately follows, leaving us with an object
254 // 10/14 bytes in size. Since everything needs to be a multiple
255 // of 4/8, this will get padded and zeroed out.
257 // For one-char strings the second char will be the null terminator.
259 // NOTE: If in the future there is a way to read the second char
260 // without pinning the string (e.g. System.Runtime.CompilerServices.Unsafe
261 // is exposed to mscorlib, or a future version of C# allows inline IL),
262 // then do that and short-circuit before the fixed.
264 if (*(a + 1) != *(b + 1)) goto DiffOffset1;
266 // Since we know that the first two chars are the same,
267 // we can increment by 2 here and skip 4 bytes.
268 // This leaves us 8-byte aligned, which results
269 // on better perf for 64-bit platforms.
270 length -= 2; a += 2; b += 2;
276 if (*(long*)a != *(long*)b) goto DiffOffset0;
277 if (*(long*)(a + 4) != *(long*)(b + 4)) goto DiffOffset4;
278 if (*(long*)(a + 8) != *(long*)(b + 8)) goto DiffOffset8;
279 length -= 12; a += 12; b += 12;
284 if (*(int*)a != *(int*)b) goto DiffOffset0;
285 if (*(int*)(a + 2) != *(int*)(b + 2)) goto DiffOffset2;
286 if (*(int*)(a + 4) != *(int*)(b + 4)) goto DiffOffset4;
287 if (*(int*)(a + 6) != *(int*)(b + 6)) goto DiffOffset6;
288 if (*(int*)(a + 8) != *(int*)(b + 8)) goto DiffOffset8;
289 length -= 10; a += 10; b += 10;
294 // go back to slower code path and do comparison on 4 bytes at a time.
295 // This depends on the fact that the String objects are
296 // always zero terminated and that the terminating zero is not included
297 // in the length. For odd string sizes, the last compare will include
298 // the zero terminator.
301 if (*(int*)a != *(int*)b) goto DiffNextInt;
307 // At this point, we have compared all the characters in at least one string.
308 // The longer string will be larger.
309 return strA.Length - strB.Length;
312 DiffOffset8: a += 4; b += 4;
313 DiffOffset4: a += 4; b += 4;
315 // Use jumps instead of falling through, since
316 // otherwise going to DiffOffset8 will involve
317 // 8 add instructions before getting to DiffNextInt
318 DiffOffset8: a += 8; b += 8; goto DiffOffset0;
319 DiffOffset6: a += 6; b += 6; goto DiffOffset0;
320 DiffOffset4: a += 2; b += 2;
321 DiffOffset2: a += 2; b += 2;
325 // If we reached here, we already see a difference in the unrolled loop above
327 if (*(int*)a == *(int*)b)
334 if (*a != *b) return *a - *b;
337 Debug.Assert(*(a + 1) != *(b + 1), "This char must be different if we reach here!");
338 return *(a + 1) - *(b + 1);
342 // Provides a culture-correct string comparison. StrA is compared to StrB
343 // to determine whether it is lexicographically less, equal, or greater, and then returns
344 // either a negative integer, 0, or a positive integer; respectively.
346 public static int Compare(String strA, String strB)
348 return Compare(strA, strB, StringComparison.CurrentCulture);
352 // Provides a culture-correct string comparison. strA is compared to strB
353 // to determine whether it is lexicographically less, equal, or greater, and then a
354 // negative integer, 0, or a positive integer is returned; respectively.
355 // The case-sensitive option is set by ignoreCase
357 public static int Compare(String strA, String strB, bool ignoreCase)
359 var comparisonType = ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
360 return Compare(strA, strB, comparisonType);
364 // Provides a more flexible function for string comparision. See StringComparison
365 // for meaning of different comparisonType.
366 public static int Compare(String strA, String strB, StringComparison comparisonType)
368 // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase]
369 if ((uint)(comparisonType - StringComparison.CurrentCulture) > (uint)(StringComparison.OrdinalIgnoreCase - StringComparison.CurrentCulture))
371 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
374 if (object.ReferenceEquals(strA, strB))
379 // They can't both be null at this point.
389 switch (comparisonType)
391 case StringComparison.CurrentCulture:
392 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.None);
394 case StringComparison.CurrentCultureIgnoreCase:
395 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreCase);
397 case StringComparison.InvariantCulture:
398 return CompareInfo.Invariant.Compare(strA, strB, CompareOptions.None);
400 case StringComparison.InvariantCultureIgnoreCase:
401 return CompareInfo.Invariant.Compare(strA, strB, CompareOptions.IgnoreCase);
403 case StringComparison.Ordinal:
404 // Most common case: first character is different.
405 // Returns false for empty strings.
406 if (strA._firstChar != strB._firstChar)
408 return strA._firstChar - strB._firstChar;
411 return CompareOrdinalHelper(strA, strB);
413 case StringComparison.OrdinalIgnoreCase:
414 // If both strings are ASCII strings, we can take the fast path.
415 if (strA.IsAscii() && strB.IsAscii())
417 return (CompareOrdinalIgnoreCaseHelper(strA, strB));
420 return CompareInfo.CompareOrdinalIgnoreCase(strA, 0, strA.Length, strB, 0, strB.Length);
423 throw new NotSupportedException(SR.NotSupported_StringComparison);
428 // Provides a culture-correct string comparison. strA is compared to strB
429 // to determine whether it is lexicographically less, equal, or greater, and then a
430 // negative integer, 0, or a positive integer is returned; respectively.
432 public static int Compare(String strA, String strB, CultureInfo culture, CompareOptions options)
436 throw new ArgumentNullException(nameof(culture));
439 return culture.CompareInfo.Compare(strA, strB, options);
444 // Provides a culture-correct string comparison. strA is compared to strB
445 // to determine whether it is lexicographically less, equal, or greater, and then a
446 // negative integer, 0, or a positive integer is returned; respectively.
447 // The case-sensitive option is set by ignoreCase, and the culture is set
450 public static int Compare(String strA, String strB, bool ignoreCase, CultureInfo culture)
452 var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
453 return Compare(strA, strB, culture, options);
456 // Determines whether two string regions match. The substring of strA beginning
457 // at indexA of length count is compared with the substring of strB
458 // beginning at indexB of the same length.
460 public static int Compare(String strA, int indexA, String strB, int indexB, int length)
462 // NOTE: It's important we call the boolean overload, and not the StringComparison
463 // one. The two have some subtly different behavior (see notes in the former).
464 return Compare(strA, indexA, strB, indexB, length, ignoreCase: false);
467 // Determines whether two string regions match. The substring of strA beginning
468 // at indexA of length count is compared with the substring of strB
469 // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean.
471 public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
473 // Ideally we would just forward to the string.Compare overload that takes
474 // a StringComparison parameter, and just pass in CurrentCulture/CurrentCultureIgnoreCase.
475 // That function will return early if an optimization can be applied, e.g. if
476 // (object)strA == strB && indexA == indexB then it will return 0 straightaway.
477 // There are a couple of subtle behavior differences that prevent us from doing so
479 // - string.Compare(null, -1, null, -1, -1, StringComparison.CurrentCulture) works
480 // since that method also returns early for nulls before validation. It shouldn't
481 // for this overload.
482 // - Since we originally forwarded to CompareInfo.Compare for all of the argument
483 // validation logic, the ArgumentOutOfRangeExceptions thrown will contain different
485 // Therefore, we have to duplicate some of the logic here.
487 int lengthA = length;
488 int lengthB = length;
492 lengthA = Math.Min(lengthA, strA.Length - indexA);
497 lengthB = Math.Min(lengthB, strB.Length - indexB);
500 var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
501 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
504 // Determines whether two string regions match. The substring of strA beginning
505 // at indexA of length length is compared with the substring of strB
506 // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean,
507 // and the culture is set by culture.
509 public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
511 var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
512 return Compare(strA, indexA, strB, indexB, length, culture, options);
516 // Determines whether two string regions match. The substring of strA beginning
517 // at indexA of length length is compared with the substring of strB
518 // beginning at indexB of the same length.
520 public static int Compare(String strA, int indexA, String strB, int indexB, int length, CultureInfo culture, CompareOptions options)
524 throw new ArgumentNullException(nameof(culture));
527 int lengthA = length;
528 int lengthB = length;
532 lengthA = Math.Min(lengthA, strA.Length - indexA);
537 lengthB = Math.Min(lengthB, strB.Length - indexB);
540 return culture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
543 public static int Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType)
545 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
547 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
550 if (strA == null || strB == null)
552 if (object.ReferenceEquals(strA, strB))
558 return strA == null ? -1 : 1;
563 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
566 if (indexA < 0 || indexB < 0)
568 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
569 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
572 if (strA.Length - indexA < 0 || strB.Length - indexB < 0)
574 string paramName = strA.Length - indexA < 0 ? nameof(indexA) : nameof(indexB);
575 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
578 if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
583 int lengthA = Math.Min(length, strA.Length - indexA);
584 int lengthB = Math.Min(length, strB.Length - indexB);
586 switch (comparisonType)
588 case StringComparison.CurrentCulture:
589 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
591 case StringComparison.CurrentCultureIgnoreCase:
592 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
594 case StringComparison.InvariantCulture:
595 return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
597 case StringComparison.InvariantCultureIgnoreCase:
598 return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
600 case StringComparison.Ordinal:
601 return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
603 case StringComparison.OrdinalIgnoreCase:
604 return (CompareInfo.CompareOrdinalIgnoreCase(strA, indexA, lengthA, strB, indexB, lengthB));
607 throw new ArgumentException(SR.NotSupported_StringComparison);
611 // Compares strA and strB using an ordinal (code-point) comparison.
613 public static int CompareOrdinal(String strA, String strB)
615 if (object.ReferenceEquals(strA, strB))
620 // They can't both be null at this point.
630 // Most common case, first character is different.
631 // This will return false for empty strings.
632 if (strA._firstChar != strB._firstChar)
634 return strA._firstChar - strB._firstChar;
637 return CompareOrdinalHelper(strA, strB);
640 // TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly?
641 internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
643 // TODO: This needs to be optimized / unrolled. It can't just use CompareOrdinalHelper(str, str)
644 // (changed to accept spans) because its implementation is based on a string layout,
645 // in a way that doesn't work when there isn't guaranteed to be a null terminator.
647 int minLength = Math.Min(strA.Length, strB.Length);
648 for (int i = 0; i < minLength; i++)
650 if (strA[i] != strB[i])
652 return strA[i] - strB[i];
656 return strA.Length - strB.Length;
659 // Compares strA and strB using an ordinal (code-point) comparison.
661 public static int CompareOrdinal(String strA, int indexA, String strB, int indexB, int length)
663 if (strA == null || strB == null)
665 if (object.ReferenceEquals(strA, strB))
671 return strA == null ? -1 : 1;
674 // COMPAT: Checking for nulls should become before the arguments are validated,
675 // but other optimizations which allow us to return early should come after.
679 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeCount);
682 if (indexA < 0 || indexB < 0)
684 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
685 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
688 int lengthA = Math.Min(length, strA.Length - indexA);
689 int lengthB = Math.Min(length, strB.Length - indexB);
691 if (lengthA < 0 || lengthB < 0)
693 string paramName = lengthA < 0 ? nameof(indexA) : nameof(indexB);
694 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
697 if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
702 return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
705 // Compares this String to another String (cast as object), returning an integer that
706 // indicates the relationship. This method returns a value less than 0 if this is less than value, 0
707 // if this is equal to value, or a value greater than 0 if this is greater than value.
709 public int CompareTo(Object value)
716 string other = value as string;
720 throw new ArgumentException(SR.Arg_MustBeString);
723 return CompareTo(other); // will call the string-based overload
726 // Determines the sorting relation of StrB to the current instance.
728 public int CompareTo(String strB)
730 return string.Compare(this, strB, StringComparison.CurrentCulture);
733 // Determines whether a specified string is a suffix of the current instance.
735 // The case-sensitive and culture-sensitive option is set by options,
736 // and the default culture is used.
738 public Boolean EndsWith(String value)
740 return EndsWith(value, StringComparison.CurrentCulture);
743 public Boolean EndsWith(String value, StringComparison comparisonType)
745 if ((Object)value == null)
747 throw new ArgumentNullException(nameof(value));
750 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
752 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
755 if ((Object)this == (Object)value)
760 if (value.Length == 0)
765 switch (comparisonType)
767 case StringComparison.CurrentCulture:
768 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None);
770 case StringComparison.CurrentCultureIgnoreCase:
771 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase);
773 case StringComparison.InvariantCulture:
774 return CompareInfo.Invariant.IsSuffix(this, value, CompareOptions.None);
776 case StringComparison.InvariantCultureIgnoreCase:
777 return CompareInfo.Invariant.IsSuffix(this, value, CompareOptions.IgnoreCase);
779 case StringComparison.Ordinal:
780 return this.Length < value.Length ? false : (CompareOrdinalHelper(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
782 case StringComparison.OrdinalIgnoreCase:
783 return this.Length < value.Length ? false : (CompareInfo.CompareOrdinalIgnoreCase(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
785 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
789 public Boolean EndsWith(String value, Boolean ignoreCase, CultureInfo culture)
793 throw new ArgumentNullException(nameof(value));
796 if ((object)this == (object)value)
801 CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
802 return referenceCulture.CompareInfo.IsSuffix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
805 public bool EndsWith(char value)
807 int thisLen = Length;
808 return thisLen != 0 && this[thisLen - 1] == value;
811 // Determines whether two strings match.
812 public override bool Equals(Object obj)
814 if (object.ReferenceEquals(this, obj))
817 string str = obj as string;
821 if (this.Length != str.Length)
824 return EqualsHelper(this, str);
827 // Determines whether two strings match.
828 public bool Equals(String value)
830 if (object.ReferenceEquals(this, value))
833 // NOTE: No need to worry about casting to object here.
834 // If either side of an == comparison between strings
835 // is null, Roslyn generates a simple ceq instruction
836 // instead of calling string.op_Equality.
840 if (this.Length != value.Length)
843 return EqualsHelper(this, value);
846 public bool Equals(String value, StringComparison comparisonType)
848 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
849 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
851 if ((Object)this == (Object)value)
856 if ((Object)value == null)
861 switch (comparisonType)
863 case StringComparison.CurrentCulture:
864 return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0);
866 case StringComparison.CurrentCultureIgnoreCase:
867 return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0);
869 case StringComparison.InvariantCulture:
870 return (CompareInfo.Invariant.Compare(this, value, CompareOptions.None) == 0);
872 case StringComparison.InvariantCultureIgnoreCase:
873 return (CompareInfo.Invariant.Compare(this, value, CompareOptions.IgnoreCase) == 0);
875 case StringComparison.Ordinal:
876 if (this.Length != value.Length)
878 return EqualsHelper(this, value);
880 case StringComparison.OrdinalIgnoreCase:
881 if (this.Length != value.Length)
884 // If both strings are ASCII strings, we can take the fast path.
885 if (this.IsAscii() && value.IsAscii())
887 return EqualsIgnoreCaseAsciiHelper(this, value);
890 return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, this.Length, value, 0, value.Length) == 0);
893 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
898 // Determines whether two Strings match.
899 public static bool Equals(String a, String b)
901 if ((Object)a == (Object)b)
906 if ((Object)a == null || (Object)b == null || a.Length != b.Length)
911 return EqualsHelper(a, b);
914 public static bool Equals(String a, String b, StringComparison comparisonType)
916 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
917 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
919 if ((Object)a == (Object)b)
924 if ((Object)a == null || (Object)b == null)
929 switch (comparisonType)
931 case StringComparison.CurrentCulture:
932 return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0);
934 case StringComparison.CurrentCultureIgnoreCase:
935 return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0);
937 case StringComparison.InvariantCulture:
938 return (CompareInfo.Invariant.Compare(a, b, CompareOptions.None) == 0);
940 case StringComparison.InvariantCultureIgnoreCase:
941 return (CompareInfo.Invariant.Compare(a, b, CompareOptions.IgnoreCase) == 0);
943 case StringComparison.Ordinal:
944 if (a.Length != b.Length)
947 return EqualsHelper(a, b);
949 case StringComparison.OrdinalIgnoreCase:
950 if (a.Length != b.Length)
954 // If both strings are ASCII strings, we can take the fast path.
955 if (a.IsAscii() && b.IsAscii())
957 return EqualsIgnoreCaseAsciiHelper(a, b);
959 // Take the slow path.
961 return (CompareInfo.CompareOrdinalIgnoreCase(a, 0, a.Length, b, 0, b.Length) == 0);
965 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
969 public static bool operator ==(String a, String b)
971 return String.Equals(a, b);
974 public static bool operator !=(String a, String b)
976 return !String.Equals(a, b);
979 [MethodImplAttribute(MethodImplOptions.InternalCall)]
980 private static extern int InternalMarvin32HashString(string s);
982 // Gets a hash code for this string. If strings A and B are such that A.Equals(B), then
983 // they will return the same hash code.
984 public override int GetHashCode()
986 return InternalMarvin32HashString(this);
989 // Gets a hash code for this string and this comparison. If strings A and B and comparition C are such
990 // that String.Equals(A, B, C), then they will return the same hash code with this comparison C.
991 public int GetHashCode(StringComparison comparisonType) => StringComparer.FromComparison(comparisonType).GetHashCode(this);
993 // Use this if and only if you need the hashcode to not change across app domains (e.g. you have an app domain agile
995 internal int GetLegacyNonRandomizedHashCode()
999 fixed (char* src = &_firstChar)
1001 Debug.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'");
1002 Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary");
1005 #else // !BIT64 (32)
1006 int hash1 = (5381<<16) + 5381;
1013 while ((c = s[0]) != 0)
1015 hash1 = ((hash1 << 5) + hash1) ^ c;
1019 hash2 = ((hash2 << 5) + hash2) ^ c;
1022 #else // !BIT64 (32)
1024 int* pint = (int *)src;
1025 int len = this.Length;
1028 hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
1029 hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
1036 hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
1040 // We want to ensure we can change our hash function daily.
1041 // This is perfectly fine as long as you don't persist the
1042 // value from GetHashCode to disk or count on String A
1043 // hashing before string B. Those are bugs in your code.
1044 hash1 ^= ThisAssembly.DailyBuildNumber;
1046 return hash1 + (hash2 * 1566083941);
1051 // Determines whether a specified string is a prefix of the current instance
1053 public Boolean StartsWith(String value)
1055 if ((Object)value == null)
1057 throw new ArgumentNullException(nameof(value));
1059 return StartsWith(value, StringComparison.CurrentCulture);
1062 public Boolean StartsWith(String value, StringComparison comparisonType)
1064 if ((Object)value == null)
1066 throw new ArgumentNullException(nameof(value));
1069 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
1071 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
1074 if ((Object)this == (Object)value)
1079 if (value.Length == 0)
1084 switch (comparisonType)
1086 case StringComparison.CurrentCulture:
1087 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
1089 case StringComparison.CurrentCultureIgnoreCase:
1090 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
1092 case StringComparison.InvariantCulture:
1093 return CompareInfo.Invariant.IsPrefix(this, value, CompareOptions.None);
1095 case StringComparison.InvariantCultureIgnoreCase:
1096 return CompareInfo.Invariant.IsPrefix(this, value, CompareOptions.IgnoreCase);
1098 case StringComparison.Ordinal:
1099 if (this.Length < value.Length || _firstChar != value._firstChar)
1103 return (value.Length == 1) ?
1104 true : // First char is the same and thats all there is to compare
1105 StartsWithOrdinalHelper(this, value);
1107 case StringComparison.OrdinalIgnoreCase:
1108 if (this.Length < value.Length)
1113 return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, value.Length, value, 0, value.Length) == 0);
1116 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
1120 public Boolean StartsWith(String value, Boolean ignoreCase, CultureInfo culture)
1124 throw new ArgumentNullException(nameof(value));
1127 if ((object)this == (object)value)
1132 CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
1133 return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
1136 public bool StartsWith(char value) => Length != 0 && _firstChar == value;