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);
68 //This will not work in case-insensitive mode for any character greater than 0x7F.
69 //We'll throw an ArgumentException.
70 [MethodImplAttribute(MethodImplOptions.InternalCall)]
71 internal static extern unsafe int nativeCompareOrdinalIgnoreCaseWC(String strA, sbyte* strBBytes);
75 // NATIVE INSTANCE METHODS
80 // Search/Query methods
83 [MethodImpl(MethodImplOptions.AggressiveInlining)]
84 private static bool EqualsHelper(String strA, String strB)
86 Debug.Assert(strA != null);
87 Debug.Assert(strB != null);
88 Debug.Assert(strA.Length == strB.Length);
90 return SpanHelpers.SequenceEqual(
91 ref Unsafe.As<char, byte>(ref strA.GetRawStringData()),
92 ref Unsafe.As<char, byte>(ref strB.GetRawStringData()),
93 ((nuint)strA.Length) * 2);
96 private static unsafe bool EqualsIgnoreCaseAsciiHelper(String strA, String strB)
98 Debug.Assert(strA != null);
99 Debug.Assert(strB != null);
100 Debug.Assert(strA.Length == strB.Length);
101 int length = strA.Length;
103 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
113 Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
115 // Ordinal equals or lowercase equals if the result ends up in the a-z range
116 if (charA == charB ||
117 ((charA | 0x20) == (charB | 0x20) &&
118 (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a')))
134 private static unsafe int CompareOrdinalHelper(String strA, String strB)
136 Debug.Assert(strA != null);
137 Debug.Assert(strB != null);
139 // NOTE: This may be subject to change if eliminating the check
140 // in the callers makes them small enough to be inlined
141 Debug.Assert(strA._firstChar == strB._firstChar,
142 "For performance reasons, callers of this method should " +
143 "check/short-circuit beforehand if the first char is the same.");
145 int length = Math.Min(strA.Length, strB.Length);
147 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
152 // Check if the second chars are different here
153 // The reason we check if _firstChar is different is because
154 // it's the most common case and allows us to avoid a method call
156 // The reason we check if the second char is different is because
157 // if the first two chars the same we can increment by 4 bytes,
158 // leaving us word-aligned on both 32-bit (12 bytes into the string)
159 // and 64-bit (16 bytes) platforms.
161 // For empty strings, the second char will be null due to padding.
162 // The start of the string (not including sync block pointer)
163 // is the method table pointer + string length, which takes up
164 // 8 bytes on 32-bit, 12 on x64. For empty strings the null
165 // terminator immediately follows, leaving us with an object
166 // 10/14 bytes in size. Since everything needs to be a multiple
167 // of 4/8, this will get padded and zeroed out.
169 // For one-char strings the second char will be the null terminator.
171 // NOTE: If in the future there is a way to read the second char
172 // without pinning the string (e.g. System.Runtime.CompilerServices.Unsafe
173 // is exposed to mscorlib, or a future version of C# allows inline IL),
174 // then do that and short-circuit before the fixed.
176 if (*(a + 1) != *(b + 1)) goto DiffOffset1;
178 // Since we know that the first two chars are the same,
179 // we can increment by 2 here and skip 4 bytes.
180 // This leaves us 8-byte aligned, which results
181 // on better perf for 64-bit platforms.
182 length -= 2; a += 2; b += 2;
188 if (*(long*)a != *(long*)b) goto DiffOffset0;
189 if (*(long*)(a + 4) != *(long*)(b + 4)) goto DiffOffset4;
190 if (*(long*)(a + 8) != *(long*)(b + 8)) goto DiffOffset8;
191 length -= 12; a += 12; b += 12;
196 if (*(int*)a != *(int*)b) goto DiffOffset0;
197 if (*(int*)(a + 2) != *(int*)(b + 2)) goto DiffOffset2;
198 if (*(int*)(a + 4) != *(int*)(b + 4)) goto DiffOffset4;
199 if (*(int*)(a + 6) != *(int*)(b + 6)) goto DiffOffset6;
200 if (*(int*)(a + 8) != *(int*)(b + 8)) goto DiffOffset8;
201 length -= 10; a += 10; b += 10;
206 // go back to slower code path and do comparison on 4 bytes at a time.
207 // This depends on the fact that the String objects are
208 // always zero terminated and that the terminating zero is not included
209 // in the length. For odd string sizes, the last compare will include
210 // the zero terminator.
213 if (*(int*)a != *(int*)b) goto DiffNextInt;
219 // At this point, we have compared all the characters in at least one string.
220 // The longer string will be larger.
221 return strA.Length - strB.Length;
224 DiffOffset8: a += 4; b += 4;
225 DiffOffset4: a += 4; b += 4;
227 // Use jumps instead of falling through, since
228 // otherwise going to DiffOffset8 will involve
229 // 8 add instructions before getting to DiffNextInt
230 DiffOffset8: a += 8; b += 8; goto DiffOffset0;
231 DiffOffset6: a += 6; b += 6; goto DiffOffset0;
232 DiffOffset4: a += 2; b += 2;
233 DiffOffset2: a += 2; b += 2;
237 // If we reached here, we already see a difference in the unrolled loop above
239 if (*(int*)a == *(int*)b)
246 if (*a != *b) return *a - *b;
249 Debug.Assert(*(a + 1) != *(b + 1), "This char must be different if we reach here!");
250 return *(a + 1) - *(b + 1);
254 // Provides a culture-correct string comparison. StrA is compared to StrB
255 // to determine whether it is lexicographically less, equal, or greater, and then returns
256 // either a negative integer, 0, or a positive integer; respectively.
258 public static int Compare(String strA, String strB)
260 return Compare(strA, strB, StringComparison.CurrentCulture);
264 // Provides a culture-correct string comparison. strA is compared to strB
265 // to determine whether it is lexicographically less, equal, or greater, and then a
266 // negative integer, 0, or a positive integer is returned; respectively.
267 // The case-sensitive option is set by ignoreCase
269 public static int Compare(String strA, String strB, bool ignoreCase)
271 var comparisonType = ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
272 return Compare(strA, strB, comparisonType);
276 // Provides a more flexible function for string comparision. See StringComparison
277 // for meaning of different comparisonType.
278 public static int Compare(String strA, String strB, StringComparison comparisonType)
280 if (object.ReferenceEquals(strA, strB))
282 CheckStringComparison(comparisonType);
286 // They can't both be null at this point.
289 CheckStringComparison(comparisonType);
294 CheckStringComparison(comparisonType);
298 switch (comparisonType)
300 case StringComparison.CurrentCulture:
301 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.None);
303 case StringComparison.CurrentCultureIgnoreCase:
304 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreCase);
306 case StringComparison.InvariantCulture:
307 return CompareInfo.Invariant.Compare(strA, strB, CompareOptions.None);
309 case StringComparison.InvariantCultureIgnoreCase:
310 return CompareInfo.Invariant.Compare(strA, strB, CompareOptions.IgnoreCase);
312 case StringComparison.Ordinal:
313 // Most common case: first character is different.
314 // Returns false for empty strings.
315 if (strA._firstChar != strB._firstChar)
317 return strA._firstChar - strB._firstChar;
320 return CompareOrdinalHelper(strA, strB);
322 case StringComparison.OrdinalIgnoreCase:
323 // If both strings are ASCII strings, we can take the fast path.
324 if (strA.IsAscii() && strB.IsAscii())
326 return (CompareOrdinalIgnoreCaseHelper(strA, strB));
329 return CompareInfo.CompareOrdinalIgnoreCase(strA, 0, strA.Length, strB, 0, strB.Length);
332 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
337 // Provides a culture-correct string comparison. strA is compared to strB
338 // to determine whether it is lexicographically less, equal, or greater, and then a
339 // negative integer, 0, or a positive integer is returned; respectively.
341 public static int Compare(String strA, String strB, CultureInfo culture, CompareOptions options)
345 throw new ArgumentNullException(nameof(culture));
348 return culture.CompareInfo.Compare(strA, strB, options);
353 // Provides a culture-correct string comparison. strA is compared to strB
354 // to determine whether it is lexicographically less, equal, or greater, and then a
355 // negative integer, 0, or a positive integer is returned; respectively.
356 // The case-sensitive option is set by ignoreCase, and the culture is set
359 public static int Compare(String strA, String strB, bool ignoreCase, CultureInfo culture)
361 var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
362 return Compare(strA, strB, culture, options);
365 // Determines whether two string regions match. The substring of strA beginning
366 // at indexA of length count is compared with the substring of strB
367 // beginning at indexB of the same length.
369 public static int Compare(String strA, int indexA, String strB, int indexB, int length)
371 // NOTE: It's important we call the boolean overload, and not the StringComparison
372 // one. The two have some subtly different behavior (see notes in the former).
373 return Compare(strA, indexA, strB, indexB, length, ignoreCase: false);
376 // Determines whether two string regions match. The substring of strA beginning
377 // at indexA of length count is compared with the substring of strB
378 // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean.
380 public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
382 // Ideally we would just forward to the string.Compare overload that takes
383 // a StringComparison parameter, and just pass in CurrentCulture/CurrentCultureIgnoreCase.
384 // That function will return early if an optimization can be applied, e.g. if
385 // (object)strA == strB && indexA == indexB then it will return 0 straightaway.
386 // There are a couple of subtle behavior differences that prevent us from doing so
388 // - string.Compare(null, -1, null, -1, -1, StringComparison.CurrentCulture) works
389 // since that method also returns early for nulls before validation. It shouldn't
390 // for this overload.
391 // - Since we originally forwarded to CompareInfo.Compare for all of the argument
392 // validation logic, the ArgumentOutOfRangeExceptions thrown will contain different
394 // Therefore, we have to duplicate some of the logic here.
396 int lengthA = length;
397 int lengthB = length;
401 lengthA = Math.Min(lengthA, strA.Length - indexA);
406 lengthB = Math.Min(lengthB, strB.Length - indexB);
409 var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
410 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
413 // Determines whether two string regions match. The substring of strA beginning
414 // at indexA of length length is compared with the substring of strB
415 // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean,
416 // and the culture is set by culture.
418 public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
420 var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
421 return Compare(strA, indexA, strB, indexB, length, culture, options);
425 // Determines whether two string regions match. The substring of strA beginning
426 // at indexA of length length is compared with the substring of strB
427 // beginning at indexB of the same length.
429 public static int Compare(String strA, int indexA, String strB, int indexB, int length, CultureInfo culture, CompareOptions options)
433 throw new ArgumentNullException(nameof(culture));
436 int lengthA = length;
437 int lengthB = length;
441 lengthA = Math.Min(lengthA, strA.Length - indexA);
446 lengthB = Math.Min(lengthB, strB.Length - indexB);
449 return culture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
452 public static int Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType)
454 CheckStringComparison(comparisonType);
456 if (strA == null || strB == null)
459 if (object.ReferenceEquals(strA, strB))
465 return strA == null ? -1 : 1;
470 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
473 if (indexA < 0 || indexB < 0)
475 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
476 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
479 if (strA.Length - indexA < 0 || strB.Length - indexB < 0)
481 string paramName = strA.Length - indexA < 0 ? nameof(indexA) : nameof(indexB);
482 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
485 if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
490 int lengthA = Math.Min(length, strA.Length - indexA);
491 int lengthB = Math.Min(length, strB.Length - indexB);
493 switch (comparisonType)
495 case StringComparison.CurrentCulture:
496 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
498 case StringComparison.CurrentCultureIgnoreCase:
499 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
501 case StringComparison.InvariantCulture:
502 return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
504 case StringComparison.InvariantCultureIgnoreCase:
505 return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
507 case StringComparison.Ordinal:
508 return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
510 case StringComparison.OrdinalIgnoreCase:
511 return (CompareInfo.CompareOrdinalIgnoreCase(strA, indexA, lengthA, strB, indexB, lengthB));
514 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
518 // Compares strA and strB using an ordinal (code-point) comparison.
520 public static int CompareOrdinal(String strA, String strB)
522 if (object.ReferenceEquals(strA, strB))
527 // They can't both be null at this point.
537 // Most common case, first character is different.
538 // This will return false for empty strings.
539 if (strA._firstChar != strB._firstChar)
541 return strA._firstChar - strB._firstChar;
544 return CompareOrdinalHelper(strA, strB);
547 internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
549 // TODO: Add a vectorized code path, similar to SequenceEqual
550 // https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/SpanHelpers.byte.cs#L900
552 int minLength = Math.Min(strA.Length, strB.Length);
553 ref char first = ref MemoryMarshal.GetReference(strA);
554 ref char second = ref MemoryMarshal.GetReference(strB);
557 if (minLength >= sizeof(nuint) / sizeof(char))
559 while (i < minLength - sizeof(nuint) / sizeof(char))
561 if (Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
562 Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
566 i += sizeof(nuint) / sizeof(char);
569 while (i < minLength)
571 char a = Unsafe.Add(ref first, i);
572 char b = Unsafe.Add(ref second, i);
579 return strA.Length - strB.Length;
582 // Compares strA and strB using an ordinal (code-point) comparison.
584 public static int CompareOrdinal(String strA, int indexA, String strB, int indexB, int length)
586 if (strA == null || strB == null)
588 if (object.ReferenceEquals(strA, strB))
594 return strA == null ? -1 : 1;
597 // COMPAT: Checking for nulls should become before the arguments are validated,
598 // but other optimizations which allow us to return early should come after.
602 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeCount);
605 if (indexA < 0 || indexB < 0)
607 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
608 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
611 int lengthA = Math.Min(length, strA.Length - indexA);
612 int lengthB = Math.Min(length, strB.Length - indexB);
614 if (lengthA < 0 || lengthB < 0)
616 string paramName = lengthA < 0 ? nameof(indexA) : nameof(indexB);
617 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
620 if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
625 return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
628 // Compares this String to another String (cast as object), returning an integer that
629 // indicates the relationship. This method returns a value less than 0 if this is less than value, 0
630 // if this is equal to value, or a value greater than 0 if this is greater than value.
632 public int CompareTo(Object value)
639 string other = value as string;
643 throw new ArgumentException(SR.Arg_MustBeString);
646 return CompareTo(other); // will call the string-based overload
649 // Determines the sorting relation of StrB to the current instance.
651 public int CompareTo(String strB)
653 return string.Compare(this, strB, StringComparison.CurrentCulture);
656 // Determines whether a specified string is a suffix of the current instance.
658 // The case-sensitive and culture-sensitive option is set by options,
659 // and the default culture is used.
661 public Boolean EndsWith(String value)
663 return EndsWith(value, StringComparison.CurrentCulture);
666 public Boolean EndsWith(String value, StringComparison comparisonType)
668 if ((Object)value == null)
670 throw new ArgumentNullException(nameof(value));
673 if ((Object)this == (Object)value)
675 CheckStringComparison(comparisonType);
679 if (value.Length == 0)
681 CheckStringComparison(comparisonType);
685 switch (comparisonType)
687 case StringComparison.CurrentCulture:
688 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None);
690 case StringComparison.CurrentCultureIgnoreCase:
691 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase);
693 case StringComparison.InvariantCulture:
694 return CompareInfo.Invariant.IsSuffix(this, value, CompareOptions.None);
696 case StringComparison.InvariantCultureIgnoreCase:
697 return CompareInfo.Invariant.IsSuffix(this, value, CompareOptions.IgnoreCase);
699 case StringComparison.Ordinal:
700 return this.Length < value.Length ? false : (CompareOrdinalHelper(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
702 case StringComparison.OrdinalIgnoreCase:
703 return this.Length < value.Length ? false : (CompareInfo.CompareOrdinalIgnoreCase(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
706 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
710 public Boolean EndsWith(String value, Boolean ignoreCase, CultureInfo culture)
714 throw new ArgumentNullException(nameof(value));
717 if ((object)this == (object)value)
722 CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
723 return referenceCulture.CompareInfo.IsSuffix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
726 public bool EndsWith(char value)
728 int thisLen = Length;
729 return thisLen != 0 && this[thisLen - 1] == value;
732 // Determines whether two strings match.
733 public override bool Equals(Object obj)
735 if (object.ReferenceEquals(this, obj))
738 string str = obj as string;
742 if (this.Length != str.Length)
745 return EqualsHelper(this, str);
748 // Determines whether two strings match.
749 public bool Equals(String value)
751 if (object.ReferenceEquals(this, value))
754 // NOTE: No need to worry about casting to object here.
755 // If either side of an == comparison between strings
756 // is null, Roslyn generates a simple ceq instruction
757 // instead of calling string.op_Equality.
761 if (this.Length != value.Length)
764 return EqualsHelper(this, value);
767 public bool Equals(String value, StringComparison comparisonType)
769 if ((Object)this == (Object)value)
771 CheckStringComparison(comparisonType);
775 if ((Object)value == null)
777 CheckStringComparison(comparisonType);
781 switch (comparisonType)
783 case StringComparison.CurrentCulture:
784 return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0);
786 case StringComparison.CurrentCultureIgnoreCase:
787 return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0);
789 case StringComparison.InvariantCulture:
790 return (CompareInfo.Invariant.Compare(this, value, CompareOptions.None) == 0);
792 case StringComparison.InvariantCultureIgnoreCase:
793 return (CompareInfo.Invariant.Compare(this, value, CompareOptions.IgnoreCase) == 0);
795 case StringComparison.Ordinal:
796 if (this.Length != value.Length)
798 return EqualsHelper(this, value);
800 case StringComparison.OrdinalIgnoreCase:
801 if (this.Length != value.Length)
804 // If both strings are ASCII strings, we can take the fast path.
805 if (this.IsAscii() && value.IsAscii())
807 return EqualsIgnoreCaseAsciiHelper(this, value);
810 return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, this.Length, value, 0, value.Length) == 0);
813 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
818 // Determines whether two Strings match.
819 public static bool Equals(String a, String b)
821 if ((Object)a == (Object)b)
826 if ((Object)a == null || (Object)b == null || a.Length != b.Length)
831 return EqualsHelper(a, b);
834 public static bool Equals(String a, String b, StringComparison comparisonType)
836 if ((Object)a == (Object)b)
838 CheckStringComparison(comparisonType);
842 if ((Object)a == null || (Object)b == null)
844 CheckStringComparison(comparisonType);
848 switch (comparisonType)
850 case StringComparison.CurrentCulture:
851 return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0);
853 case StringComparison.CurrentCultureIgnoreCase:
854 return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0);
856 case StringComparison.InvariantCulture:
857 return (CompareInfo.Invariant.Compare(a, b, CompareOptions.None) == 0);
859 case StringComparison.InvariantCultureIgnoreCase:
860 return (CompareInfo.Invariant.Compare(a, b, CompareOptions.IgnoreCase) == 0);
862 case StringComparison.Ordinal:
863 if (a.Length != b.Length)
866 return EqualsHelper(a, b);
868 case StringComparison.OrdinalIgnoreCase:
869 if (a.Length != b.Length)
873 // If both strings are ASCII strings, we can take the fast path.
874 if (a.IsAscii() && b.IsAscii())
876 return EqualsIgnoreCaseAsciiHelper(a, b);
878 // Take the slow path.
880 return (CompareInfo.CompareOrdinalIgnoreCase(a, 0, a.Length, b, 0, b.Length) == 0);
884 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
888 public static bool operator ==(String a, String b)
890 return String.Equals(a, b);
893 public static bool operator !=(String a, String b)
895 return !String.Equals(a, b);
898 // Gets a hash code for this string. If strings A and B are such that A.Equals(B), then
899 // they will return the same hash code.
900 public override int GetHashCode()
902 return Marvin.ComputeHash32(ref Unsafe.As<char, byte>(ref _firstChar), _stringLength * 2, Marvin.DefaultSeed);
905 // Gets a hash code for this string and this comparison. If strings A and B and comparison C are such
906 // that String.Equals(A, B, C), then they will return the same hash code with this comparison C.
907 public int GetHashCode(StringComparison comparisonType) => StringComparer.FromComparison(comparisonType).GetHashCode(this);
909 // Use this if and only if you need the hashcode to not change across app domains (e.g. you have an app domain agile
911 internal int GetLegacyNonRandomizedHashCode()
915 fixed (char* src = &_firstChar)
917 Debug.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'");
918 Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary");
922 int hash1 = (5381<<16) + 5381;
929 while ((c = s[0]) != 0)
931 hash1 = ((hash1 << 5) + hash1) ^ c;
935 hash2 = ((hash2 << 5) + hash2) ^ c;
940 int* pint = (int *)src;
941 int len = this.Length;
944 hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
945 hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
952 hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
956 // We want to ensure we can change our hash function daily.
957 // This is perfectly fine as long as you don't persist the
958 // value from GetHashCode to disk or count on String A
959 // hashing before string B. Those are bugs in your code.
960 hash1 ^= ThisAssembly.DailyBuildNumber;
962 return hash1 + (hash2 * 1566083941);
967 // Determines whether a specified string is a prefix of the current instance
969 public Boolean StartsWith(String value)
971 if ((Object)value == null)
973 throw new ArgumentNullException(nameof(value));
975 return StartsWith(value, StringComparison.CurrentCulture);
978 public Boolean StartsWith(String value, StringComparison comparisonType)
980 if ((Object)value == null)
982 throw new ArgumentNullException(nameof(value));
985 if ((Object)this == (Object)value)
987 CheckStringComparison(comparisonType);
991 if (value.Length == 0)
993 CheckStringComparison(comparisonType);
997 switch (comparisonType)
999 case StringComparison.CurrentCulture:
1000 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
1002 case StringComparison.CurrentCultureIgnoreCase:
1003 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
1005 case StringComparison.InvariantCulture:
1006 return CompareInfo.Invariant.IsPrefix(this, value, CompareOptions.None);
1008 case StringComparison.InvariantCultureIgnoreCase:
1009 return CompareInfo.Invariant.IsPrefix(this, value, CompareOptions.IgnoreCase);
1011 case StringComparison.Ordinal:
1012 if (this.Length < value.Length || _firstChar != value._firstChar)
1016 return (value.Length == 1) ?
1017 true : // First char is the same and thats all there is to compare
1018 SpanHelpers.SequenceEqual(
1019 ref Unsafe.As<char, byte>(ref this.GetRawStringData()),
1020 ref Unsafe.As<char, byte>(ref value.GetRawStringData()),
1021 ((nuint)value.Length) * 2);
1023 case StringComparison.OrdinalIgnoreCase:
1024 if (this.Length < value.Length)
1028 return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, value.Length, value, 0, value.Length) == 0);
1031 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
1035 public Boolean StartsWith(String value, Boolean ignoreCase, CultureInfo culture)
1039 throw new ArgumentNullException(nameof(value));
1042 if ((object)this == (object)value)
1047 CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
1048 return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
1051 public bool StartsWith(char value) => Length != 0 && _firstChar == value;
1053 internal static void CheckStringComparison(StringComparison comparisonType)
1055 // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase]
1056 if ((uint)(comparisonType - StringComparison.CurrentCulture) > (StringComparison.OrdinalIgnoreCase - StringComparison.CurrentCulture))
1058 ThrowHelper.ThrowArgumentException(ExceptionResource.NotSupported_StringComparison, ExceptionArgument.comparisonType);