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;
12 using Internal.Runtime.CompilerServices;
15 using nuint = System.UInt64;
17 using nuint = System.UInt32;
22 public partial class String
25 //Native Static Methods
28 private static unsafe int CompareOrdinalIgnoreCaseHelper(String strA, String strB)
30 Debug.Assert(strA != null);
31 Debug.Assert(strB != null);
32 int length = Math.Min(strA.Length, strB.Length);
34 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
38 int charA = 0, charB = 0;
45 Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
47 // uppercase both chars - notice that we need just one compare per char
48 if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
49 if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
51 //Return the (case-insensitive) difference between them.
60 return strA.Length - strB.Length;
64 // native call to COMString::CompareOrdinalEx
65 [MethodImplAttribute(MethodImplOptions.InternalCall)]
66 internal static extern int CompareOrdinalHelper(String strA, int indexA, int countA, String strB, int indexB, int countB);
70 // NATIVE INSTANCE METHODS
75 // Search/Query methods
78 [MethodImpl(MethodImplOptions.AggressiveInlining)]
79 private static bool EqualsHelper(String strA, String strB)
81 Debug.Assert(strA != null);
82 Debug.Assert(strB != null);
83 Debug.Assert(strA.Length == strB.Length);
85 return SpanHelpers.SequenceEqual(
86 ref Unsafe.As<char, byte>(ref strA.GetRawStringData()),
87 ref Unsafe.As<char, byte>(ref strB.GetRawStringData()),
88 ((nuint)strA.Length) * 2);
91 private static unsafe bool EqualsIgnoreCaseAsciiHelper(String strA, String strB)
93 Debug.Assert(strA != null);
94 Debug.Assert(strB != null);
95 Debug.Assert(strA.Length == strB.Length);
96 int length = strA.Length;
98 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
108 Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
110 // Ordinal equals or lowercase equals if the result ends up in the a-z range
111 if (charA == charB ||
112 ((charA | 0x20) == (charB | 0x20) &&
113 (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a')))
129 private static unsafe int CompareOrdinalHelper(String strA, String strB)
131 Debug.Assert(strA != null);
132 Debug.Assert(strB != null);
134 // NOTE: This may be subject to change if eliminating the check
135 // in the callers makes them small enough to be inlined
136 Debug.Assert(strA._firstChar == strB._firstChar,
137 "For performance reasons, callers of this method should " +
138 "check/short-circuit beforehand if the first char is the same.");
140 int length = Math.Min(strA.Length, strB.Length);
142 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
147 // Check if the second chars are different here
148 // The reason we check if _firstChar is different is because
149 // it's the most common case and allows us to avoid a method call
151 // The reason we check if the second char is different is because
152 // if the first two chars the same we can increment by 4 bytes,
153 // leaving us word-aligned on both 32-bit (12 bytes into the string)
154 // and 64-bit (16 bytes) platforms.
156 // For empty strings, the second char will be null due to padding.
157 // The start of the string (not including sync block pointer)
158 // is the method table pointer + string length, which takes up
159 // 8 bytes on 32-bit, 12 on x64. For empty strings the null
160 // terminator immediately follows, leaving us with an object
161 // 10/14 bytes in size. Since everything needs to be a multiple
162 // of 4/8, this will get padded and zeroed out.
164 // For one-char strings the second char will be the null terminator.
166 // NOTE: If in the future there is a way to read the second char
167 // without pinning the string (e.g. System.Runtime.CompilerServices.Unsafe
168 // is exposed to mscorlib, or a future version of C# allows inline IL),
169 // then do that and short-circuit before the fixed.
171 if (*(a + 1) != *(b + 1)) goto DiffOffset1;
173 // Since we know that the first two chars are the same,
174 // we can increment by 2 here and skip 4 bytes.
175 // This leaves us 8-byte aligned, which results
176 // on better perf for 64-bit platforms.
177 length -= 2; a += 2; b += 2;
183 if (*(long*)a != *(long*)b) goto DiffOffset0;
184 if (*(long*)(a + 4) != *(long*)(b + 4)) goto DiffOffset4;
185 if (*(long*)(a + 8) != *(long*)(b + 8)) goto DiffOffset8;
186 length -= 12; a += 12; b += 12;
191 if (*(int*)a != *(int*)b) goto DiffOffset0;
192 if (*(int*)(a + 2) != *(int*)(b + 2)) goto DiffOffset2;
193 if (*(int*)(a + 4) != *(int*)(b + 4)) goto DiffOffset4;
194 if (*(int*)(a + 6) != *(int*)(b + 6)) goto DiffOffset6;
195 if (*(int*)(a + 8) != *(int*)(b + 8)) goto DiffOffset8;
196 length -= 10; a += 10; b += 10;
201 // go back to slower code path and do comparison on 4 bytes at a time.
202 // This depends on the fact that the String objects are
203 // always zero terminated and that the terminating zero is not included
204 // in the length. For odd string sizes, the last compare will include
205 // the zero terminator.
208 if (*(int*)a != *(int*)b) goto DiffNextInt;
214 // At this point, we have compared all the characters in at least one string.
215 // The longer string will be larger.
216 return strA.Length - strB.Length;
219 DiffOffset8: a += 4; b += 4;
220 DiffOffset4: a += 4; b += 4;
222 // Use jumps instead of falling through, since
223 // otherwise going to DiffOffset8 will involve
224 // 8 add instructions before getting to DiffNextInt
225 DiffOffset8: a += 8; b += 8; goto DiffOffset0;
226 DiffOffset6: a += 6; b += 6; goto DiffOffset0;
227 DiffOffset4: a += 2; b += 2;
228 DiffOffset2: a += 2; b += 2;
232 // If we reached here, we already see a difference in the unrolled loop above
234 if (*(int*)a == *(int*)b)
241 if (*a != *b) return *a - *b;
244 Debug.Assert(*(a + 1) != *(b + 1), "This char must be different if we reach here!");
245 return *(a + 1) - *(b + 1);
249 // Provides a culture-correct string comparison. StrA is compared to StrB
250 // to determine whether it is lexicographically less, equal, or greater, and then returns
251 // either a negative integer, 0, or a positive integer; respectively.
253 public static int Compare(String strA, String strB)
255 return Compare(strA, strB, StringComparison.CurrentCulture);
259 // Provides a culture-correct string comparison. strA is compared to strB
260 // to determine whether it is lexicographically less, equal, or greater, and then a
261 // negative integer, 0, or a positive integer is returned; respectively.
262 // The case-sensitive option is set by ignoreCase
264 public static int Compare(String strA, String strB, bool ignoreCase)
266 var comparisonType = ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
267 return Compare(strA, strB, comparisonType);
271 // Provides a more flexible function for string comparision. See StringComparison
272 // for meaning of different comparisonType.
273 public static int Compare(String strA, String strB, StringComparison comparisonType)
275 if (object.ReferenceEquals(strA, strB))
277 CheckStringComparison(comparisonType);
281 // They can't both be null at this point.
284 CheckStringComparison(comparisonType);
289 CheckStringComparison(comparisonType);
293 switch (comparisonType)
295 case StringComparison.CurrentCulture:
296 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.None);
298 case StringComparison.CurrentCultureIgnoreCase:
299 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreCase);
301 case StringComparison.InvariantCulture:
302 return CompareInfo.Invariant.Compare(strA, strB, CompareOptions.None);
304 case StringComparison.InvariantCultureIgnoreCase:
305 return CompareInfo.Invariant.Compare(strA, strB, CompareOptions.IgnoreCase);
307 case StringComparison.Ordinal:
308 // Most common case: first character is different.
309 // Returns false for empty strings.
310 if (strA._firstChar != strB._firstChar)
312 return strA._firstChar - strB._firstChar;
315 return CompareOrdinalHelper(strA, strB);
317 case StringComparison.OrdinalIgnoreCase:
318 // If both strings are ASCII strings, we can take the fast path.
319 if (strA.IsAscii() && strB.IsAscii())
321 return (CompareOrdinalIgnoreCaseHelper(strA, strB));
324 return CompareInfo.CompareOrdinalIgnoreCase(strA, 0, strA.Length, strB, 0, strB.Length);
327 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
332 // Provides a culture-correct string comparison. strA is compared to strB
333 // to determine whether it is lexicographically less, equal, or greater, and then a
334 // negative integer, 0, or a positive integer is returned; respectively.
336 public static int Compare(String strA, String strB, CultureInfo culture, CompareOptions options)
340 throw new ArgumentNullException(nameof(culture));
343 return culture.CompareInfo.Compare(strA, strB, options);
348 // Provides a culture-correct string comparison. strA is compared to strB
349 // to determine whether it is lexicographically less, equal, or greater, and then a
350 // negative integer, 0, or a positive integer is returned; respectively.
351 // The case-sensitive option is set by ignoreCase, and the culture is set
354 public static int Compare(String strA, String strB, bool ignoreCase, CultureInfo culture)
356 var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
357 return Compare(strA, strB, culture, options);
360 // Determines whether two string regions match. The substring of strA beginning
361 // at indexA of length count is compared with the substring of strB
362 // beginning at indexB of the same length.
364 public static int Compare(String strA, int indexA, String strB, int indexB, int length)
366 // NOTE: It's important we call the boolean overload, and not the StringComparison
367 // one. The two have some subtly different behavior (see notes in the former).
368 return Compare(strA, indexA, strB, indexB, length, ignoreCase: false);
371 // Determines whether two string regions match. The substring of strA beginning
372 // at indexA of length count is compared with the substring of strB
373 // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean.
375 public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
377 // Ideally we would just forward to the string.Compare overload that takes
378 // a StringComparison parameter, and just pass in CurrentCulture/CurrentCultureIgnoreCase.
379 // That function will return early if an optimization can be applied, e.g. if
380 // (object)strA == strB && indexA == indexB then it will return 0 straightaway.
381 // There are a couple of subtle behavior differences that prevent us from doing so
383 // - string.Compare(null, -1, null, -1, -1, StringComparison.CurrentCulture) works
384 // since that method also returns early for nulls before validation. It shouldn't
385 // for this overload.
386 // - Since we originally forwarded to CompareInfo.Compare for all of the argument
387 // validation logic, the ArgumentOutOfRangeExceptions thrown will contain different
389 // Therefore, we have to duplicate some of the logic here.
391 int lengthA = length;
392 int lengthB = length;
396 lengthA = Math.Min(lengthA, strA.Length - indexA);
401 lengthB = Math.Min(lengthB, strB.Length - indexB);
404 var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
405 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
408 // Determines whether two string regions match. The substring of strA beginning
409 // at indexA of length length is compared with the substring of strB
410 // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean,
411 // and the culture is set by culture.
413 public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
415 var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
416 return Compare(strA, indexA, strB, indexB, length, culture, options);
420 // Determines whether two string regions match. The substring of strA beginning
421 // at indexA of length length is compared with the substring of strB
422 // beginning at indexB of the same length.
424 public static int Compare(String strA, int indexA, String strB, int indexB, int length, CultureInfo culture, CompareOptions options)
428 throw new ArgumentNullException(nameof(culture));
431 int lengthA = length;
432 int lengthB = length;
436 lengthA = Math.Min(lengthA, strA.Length - indexA);
441 lengthB = Math.Min(lengthB, strB.Length - indexB);
444 return culture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
447 public static int Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType)
449 CheckStringComparison(comparisonType);
451 if (strA == null || strB == null)
454 if (object.ReferenceEquals(strA, strB))
460 return strA == null ? -1 : 1;
465 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
468 if (indexA < 0 || indexB < 0)
470 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
471 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
474 if (strA.Length - indexA < 0 || strB.Length - indexB < 0)
476 string paramName = strA.Length - indexA < 0 ? nameof(indexA) : nameof(indexB);
477 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
480 if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
485 int lengthA = Math.Min(length, strA.Length - indexA);
486 int lengthB = Math.Min(length, strB.Length - indexB);
488 switch (comparisonType)
490 case StringComparison.CurrentCulture:
491 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
493 case StringComparison.CurrentCultureIgnoreCase:
494 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
496 case StringComparison.InvariantCulture:
497 return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
499 case StringComparison.InvariantCultureIgnoreCase:
500 return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
502 case StringComparison.Ordinal:
503 return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
505 case StringComparison.OrdinalIgnoreCase:
506 return (CompareInfo.CompareOrdinalIgnoreCase(strA, indexA, lengthA, strB, indexB, lengthB));
509 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
513 // Compares strA and strB using an ordinal (code-point) comparison.
515 public static int CompareOrdinal(String strA, String strB)
517 if (object.ReferenceEquals(strA, strB))
522 // They can't both be null at this point.
532 // Most common case, first character is different.
533 // This will return false for empty strings.
534 if (strA._firstChar != strB._firstChar)
536 return strA._firstChar - strB._firstChar;
539 return CompareOrdinalHelper(strA, strB);
542 internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
544 // TODO: Add a vectorized code path, similar to SequenceEqual
545 // https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/SpanHelpers.byte.cs#L900
547 int minLength = Math.Min(strA.Length, strB.Length);
548 ref char first = ref MemoryMarshal.GetReference(strA);
549 ref char second = ref MemoryMarshal.GetReference(strB);
552 if (minLength >= sizeof(nuint) / sizeof(char))
554 while (i < minLength - sizeof(nuint) / sizeof(char))
556 if (Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
557 Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
561 i += sizeof(nuint) / sizeof(char);
564 while (i < minLength)
566 char a = Unsafe.Add(ref first, i);
567 char b = Unsafe.Add(ref second, i);
574 return strA.Length - strB.Length;
577 // Compares strA and strB using an ordinal (code-point) comparison.
579 public static int CompareOrdinal(String strA, int indexA, String strB, int indexB, int length)
581 if (strA == null || strB == null)
583 if (object.ReferenceEquals(strA, strB))
589 return strA == null ? -1 : 1;
592 // COMPAT: Checking for nulls should become before the arguments are validated,
593 // but other optimizations which allow us to return early should come after.
597 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeCount);
600 if (indexA < 0 || indexB < 0)
602 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
603 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
606 int lengthA = Math.Min(length, strA.Length - indexA);
607 int lengthB = Math.Min(length, strB.Length - indexB);
609 if (lengthA < 0 || lengthB < 0)
611 string paramName = lengthA < 0 ? nameof(indexA) : nameof(indexB);
612 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
615 if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
620 return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
623 // Compares this String to another String (cast as object), returning an integer that
624 // indicates the relationship. This method returns a value less than 0 if this is less than value, 0
625 // if this is equal to value, or a value greater than 0 if this is greater than value.
627 public int CompareTo(Object value)
634 string other = value as string;
638 throw new ArgumentException(SR.Arg_MustBeString);
641 return CompareTo(other); // will call the string-based overload
644 // Determines the sorting relation of StrB to the current instance.
646 public int CompareTo(String strB)
648 return string.Compare(this, strB, StringComparison.CurrentCulture);
651 // Determines whether a specified string is a suffix of the current instance.
653 // The case-sensitive and culture-sensitive option is set by options,
654 // and the default culture is used.
656 public Boolean EndsWith(String value)
658 return EndsWith(value, StringComparison.CurrentCulture);
661 public Boolean EndsWith(String value, StringComparison comparisonType)
663 if ((Object)value == null)
665 throw new ArgumentNullException(nameof(value));
668 if ((Object)this == (Object)value)
670 CheckStringComparison(comparisonType);
674 if (value.Length == 0)
676 CheckStringComparison(comparisonType);
680 switch (comparisonType)
682 case StringComparison.CurrentCulture:
683 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None);
685 case StringComparison.CurrentCultureIgnoreCase:
686 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase);
688 case StringComparison.InvariantCulture:
689 return CompareInfo.Invariant.IsSuffix(this, value, CompareOptions.None);
691 case StringComparison.InvariantCultureIgnoreCase:
692 return CompareInfo.Invariant.IsSuffix(this, value, CompareOptions.IgnoreCase);
694 case StringComparison.Ordinal:
695 return this.Length < value.Length ? false : (CompareOrdinalHelper(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
697 case StringComparison.OrdinalIgnoreCase:
698 return this.Length < value.Length ? false : (CompareInfo.CompareOrdinalIgnoreCase(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
701 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
705 public Boolean EndsWith(String value, Boolean ignoreCase, CultureInfo culture)
709 throw new ArgumentNullException(nameof(value));
712 if ((object)this == (object)value)
717 CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
718 return referenceCulture.CompareInfo.IsSuffix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
721 public bool EndsWith(char value)
723 int thisLen = Length;
724 return thisLen != 0 && this[thisLen - 1] == value;
727 // Determines whether two strings match.
728 public override bool Equals(Object obj)
730 if (object.ReferenceEquals(this, obj))
733 string str = obj as string;
737 if (this.Length != str.Length)
740 return EqualsHelper(this, str);
743 // Determines whether two strings match.
744 public bool Equals(String value)
746 if (object.ReferenceEquals(this, value))
749 // NOTE: No need to worry about casting to object here.
750 // If either side of an == comparison between strings
751 // is null, Roslyn generates a simple ceq instruction
752 // instead of calling string.op_Equality.
756 if (this.Length != value.Length)
759 return EqualsHelper(this, value);
762 public bool Equals(String value, StringComparison comparisonType)
764 if ((Object)this == (Object)value)
766 CheckStringComparison(comparisonType);
770 if ((Object)value == null)
772 CheckStringComparison(comparisonType);
776 switch (comparisonType)
778 case StringComparison.CurrentCulture:
779 return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0);
781 case StringComparison.CurrentCultureIgnoreCase:
782 return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0);
784 case StringComparison.InvariantCulture:
785 return (CompareInfo.Invariant.Compare(this, value, CompareOptions.None) == 0);
787 case StringComparison.InvariantCultureIgnoreCase:
788 return (CompareInfo.Invariant.Compare(this, value, CompareOptions.IgnoreCase) == 0);
790 case StringComparison.Ordinal:
791 if (this.Length != value.Length)
793 return EqualsHelper(this, value);
795 case StringComparison.OrdinalIgnoreCase:
796 if (this.Length != value.Length)
799 // If both strings are ASCII strings, we can take the fast path.
800 if (this.IsAscii() && value.IsAscii())
802 return EqualsIgnoreCaseAsciiHelper(this, value);
805 return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, this.Length, value, 0, value.Length) == 0);
808 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
813 // Determines whether two Strings match.
814 public static bool Equals(String a, String b)
816 if ((Object)a == (Object)b)
821 if ((Object)a == null || (Object)b == null || a.Length != b.Length)
826 return EqualsHelper(a, b);
829 public static bool Equals(String a, String b, StringComparison comparisonType)
831 if ((Object)a == (Object)b)
833 CheckStringComparison(comparisonType);
837 if ((Object)a == null || (Object)b == null)
839 CheckStringComparison(comparisonType);
843 switch (comparisonType)
845 case StringComparison.CurrentCulture:
846 return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0);
848 case StringComparison.CurrentCultureIgnoreCase:
849 return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0);
851 case StringComparison.InvariantCulture:
852 return (CompareInfo.Invariant.Compare(a, b, CompareOptions.None) == 0);
854 case StringComparison.InvariantCultureIgnoreCase:
855 return (CompareInfo.Invariant.Compare(a, b, CompareOptions.IgnoreCase) == 0);
857 case StringComparison.Ordinal:
858 if (a.Length != b.Length)
861 return EqualsHelper(a, b);
863 case StringComparison.OrdinalIgnoreCase:
864 if (a.Length != b.Length)
868 // If both strings are ASCII strings, we can take the fast path.
869 if (a.IsAscii() && b.IsAscii())
871 return EqualsIgnoreCaseAsciiHelper(a, b);
873 // Take the slow path.
875 return (CompareInfo.CompareOrdinalIgnoreCase(a, 0, a.Length, b, 0, b.Length) == 0);
879 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
883 public static bool operator ==(String a, String b)
885 return String.Equals(a, b);
888 public static bool operator !=(String a, String b)
890 return !String.Equals(a, b);
893 // Gets a hash code for this string. If strings A and B are such that A.Equals(B), then
894 // they will return the same hash code.
895 public override int GetHashCode()
897 return Marvin.ComputeHash32(ref Unsafe.As<char, byte>(ref _firstChar), _stringLength * 2, Marvin.DefaultSeed);
900 // Gets a hash code for this string and this comparison. If strings A and B and comparison C are such
901 // that String.Equals(A, B, C), then they will return the same hash code with this comparison C.
902 public int GetHashCode(StringComparison comparisonType) => StringComparer.FromComparison(comparisonType).GetHashCode(this);
904 // Use this if and only if you need the hashcode to not change across app domains (e.g. you have an app domain agile
906 internal int GetLegacyNonRandomizedHashCode()
910 fixed (char* src = &_firstChar)
912 Debug.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'");
913 Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary");
917 int hash1 = (5381<<16) + 5381;
924 while ((c = s[0]) != 0)
926 hash1 = ((hash1 << 5) + hash1) ^ c;
930 hash2 = ((hash2 << 5) + hash2) ^ c;
935 int* pint = (int *)src;
936 int len = this.Length;
939 hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
940 hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
947 hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
951 // We want to ensure we can change our hash function daily.
952 // This is perfectly fine as long as you don't persist the
953 // value from GetHashCode to disk or count on String A
954 // hashing before string B. Those are bugs in your code.
955 hash1 ^= ThisAssembly.DailyBuildNumber;
957 return hash1 + (hash2 * 1566083941);
962 // Determines whether a specified string is a prefix of the current instance
964 public Boolean StartsWith(String value)
966 if ((Object)value == null)
968 throw new ArgumentNullException(nameof(value));
970 return StartsWith(value, StringComparison.CurrentCulture);
973 public Boolean StartsWith(String value, StringComparison comparisonType)
975 if ((Object)value == null)
977 throw new ArgumentNullException(nameof(value));
980 if ((Object)this == (Object)value)
982 CheckStringComparison(comparisonType);
986 if (value.Length == 0)
988 CheckStringComparison(comparisonType);
992 switch (comparisonType)
994 case StringComparison.CurrentCulture:
995 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
997 case StringComparison.CurrentCultureIgnoreCase:
998 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
1000 case StringComparison.InvariantCulture:
1001 return CompareInfo.Invariant.IsPrefix(this, value, CompareOptions.None);
1003 case StringComparison.InvariantCultureIgnoreCase:
1004 return CompareInfo.Invariant.IsPrefix(this, value, CompareOptions.IgnoreCase);
1006 case StringComparison.Ordinal:
1007 if (this.Length < value.Length || _firstChar != value._firstChar)
1011 return (value.Length == 1) ?
1012 true : // First char is the same and thats all there is to compare
1013 SpanHelpers.SequenceEqual(
1014 ref Unsafe.As<char, byte>(ref this.GetRawStringData()),
1015 ref Unsafe.As<char, byte>(ref value.GetRawStringData()),
1016 ((nuint)value.Length) * 2);
1018 case StringComparison.OrdinalIgnoreCase:
1019 if (this.Length < value.Length)
1023 return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, value.Length, value, 0, value.Length) == 0);
1026 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
1030 public Boolean StartsWith(String value, Boolean ignoreCase, CultureInfo culture)
1034 throw new ArgumentNullException(nameof(value));
1037 if ((object)this == (object)value)
1042 CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
1043 return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
1046 public bool StartsWith(char value) => Length != 0 && _firstChar == value;
1048 internal static void CheckStringComparison(StringComparison comparisonType)
1050 // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase]
1051 if ((uint)(comparisonType - StringComparison.CurrentCulture) > (StringComparison.OrdinalIgnoreCase - StringComparison.CurrentCulture))
1053 ThrowHelper.ThrowArgumentException(ExceptionResource.NotSupported_StringComparison, ExceptionArgument.comparisonType);