Move EncodingTable and CodePageDataItem to System.Text namespace (#17061)
[platform/upstream/coreclr.git] / src / mscorlib / src / System / String.Comparison.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Collections;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.Runtime.CompilerServices;
9 using System.Runtime.ConstrainedExecution;
10 using System.Runtime.InteropServices;
11
12 using Internal.Runtime.CompilerServices;
13
14 #if BIT64
15 using nuint = System.UInt64;
16 #else
17 using nuint = System.UInt32;
18 #endif
19
20 namespace System
21 {
22     public partial class String
23     {
24         //
25         //Native Static Methods
26         //
27
28         private static unsafe int CompareOrdinalIgnoreCaseHelper(String strA, String strB)
29         {
30             Debug.Assert(strA != null);
31             Debug.Assert(strB != null);
32             int length = Math.Min(strA.Length, strB.Length);
33
34             fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
35             {
36                 char* a = ap;
37                 char* b = bp;
38                 int charA = 0, charB = 0;
39
40                 while (length != 0)
41                 {
42                     charA = *a;
43                     charB = *b;
44
45                     Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
46
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;
50
51                     //Return the (case-insensitive) difference between them.
52                     if (charA != charB)
53                         return charA - charB;
54
55                     // Next char
56                     a++; b++;
57                     length--;
58                 }
59
60                 return strA.Length - strB.Length;
61             }
62         }
63
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);
67
68         //
69         //
70         // NATIVE INSTANCE METHODS
71         //
72         //
73
74         //
75         // Search/Query methods
76         //
77
78         [MethodImpl(MethodImplOptions.AggressiveInlining)]
79         private static bool EqualsHelper(String strA, String strB)
80         {
81             Debug.Assert(strA != null);
82             Debug.Assert(strB != null);
83             Debug.Assert(strA.Length == strB.Length);
84
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);
89         }
90
91         private static unsafe bool EqualsIgnoreCaseAsciiHelper(String strA, String strB)
92         {
93             Debug.Assert(strA != null);
94             Debug.Assert(strB != null);
95             Debug.Assert(strA.Length == strB.Length);
96             int length = strA.Length;
97
98             fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
99             {
100                 char* a = ap;
101                 char* b = bp;
102
103                 while (length != 0)
104                 {
105                     int charA = *a;
106                     int charB = *b;
107
108                     Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
109
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')))
114                     {
115                         a++;
116                         b++;
117                         length--;
118                     }
119                     else
120                     {
121                         return false;
122                     }
123                 }
124
125                 return true;
126             }
127         }
128
129         private static unsafe int CompareOrdinalHelper(String strA, String strB)
130         {
131             Debug.Assert(strA != null);
132             Debug.Assert(strB != null);
133
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.");
139
140             int length = Math.Min(strA.Length, strB.Length);
141
142             fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
143             {
144                 char* a = ap;
145                 char* b = bp;
146
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
150                 // to here.
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.
155
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.
163
164                 // For one-char strings the second char will be the null terminator.
165
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.
170
171                 if (*(a + 1) != *(b + 1)) goto DiffOffset1;
172
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;
178
179                 // unroll the loop
180 #if BIT64
181                 while (length >= 12)
182                 {
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;
187                 }
188 #else // BIT64
189                 while (length >= 10)
190                 {
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; 
197                 }
198 #endif // BIT64
199
200                 // Fallback loop:
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.
206                 while (length > 0)
207                 {
208                     if (*(int*)a != *(int*)b) goto DiffNextInt;
209                     length -= 2;
210                     a += 2;
211                     b += 2;
212                 }
213
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;
217
218 #if BIT64
219             DiffOffset8: a += 4; b += 4;
220             DiffOffset4: a += 4; b += 4;
221 #else // BIT64
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;
229 #endif // BIT64
230
231             DiffOffset0:
232                 // If we reached here, we already see a difference in the unrolled loop above
233 #if BIT64
234                 if (*(int*)a == *(int*)b)
235                 {
236                     a += 2; b += 2;
237                 }
238 #endif // BIT64
239
240             DiffNextInt:
241                 if (*a != *b) return *a - *b;
242
243                 DiffOffset1:
244                 Debug.Assert(*(a + 1) != *(b + 1), "This char must be different if we reach here!");
245                 return *(a + 1) - *(b + 1);
246             }
247         }
248
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.
252         //
253         public static int Compare(String strA, String strB)
254         {
255             return Compare(strA, strB, StringComparison.CurrentCulture);
256         }
257
258
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
263         //
264         public static int Compare(String strA, String strB, bool ignoreCase)
265         {
266             var comparisonType = ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
267             return Compare(strA, strB, comparisonType);
268         }
269
270
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)
274         {
275             if (object.ReferenceEquals(strA, strB))
276             {
277                 CheckStringComparison(comparisonType);
278                 return 0;
279             }
280
281             // They can't both be null at this point.
282             if (strA == null)
283             {
284                 CheckStringComparison(comparisonType);
285                 return -1;
286             }
287             if (strB == null)
288             {
289                 CheckStringComparison(comparisonType);
290                 return 1;
291             }
292
293             switch (comparisonType)
294             {
295                 case StringComparison.CurrentCulture:
296                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.None);
297
298                 case StringComparison.CurrentCultureIgnoreCase:
299                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreCase);
300
301                 case StringComparison.InvariantCulture:
302                     return CompareInfo.Invariant.Compare(strA, strB, CompareOptions.None);
303
304                 case StringComparison.InvariantCultureIgnoreCase:
305                     return CompareInfo.Invariant.Compare(strA, strB, CompareOptions.IgnoreCase);
306
307                 case StringComparison.Ordinal:
308                     // Most common case: first character is different.
309                     // Returns false for empty strings.
310                     if (strA._firstChar != strB._firstChar)
311                     {
312                         return strA._firstChar - strB._firstChar;
313                     }
314
315                     return CompareOrdinalHelper(strA, strB);
316
317                 case StringComparison.OrdinalIgnoreCase:
318                     // If both strings are ASCII strings, we can take the fast path.
319                     if (strA.IsAscii() && strB.IsAscii())
320                     {
321                         return (CompareOrdinalIgnoreCaseHelper(strA, strB));
322                     }
323
324                     return CompareInfo.CompareOrdinalIgnoreCase(strA, 0, strA.Length, strB, 0, strB.Length);
325
326                 default:
327                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
328             }
329         }
330
331
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.
335         //
336         public static int Compare(String strA, String strB, CultureInfo culture, CompareOptions options)
337         {
338             if (culture == null)
339             {
340                 throw new ArgumentNullException(nameof(culture));
341             }
342
343             return culture.CompareInfo.Compare(strA, strB, options);
344         }
345
346
347
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
352         // by culture
353         //
354         public static int Compare(String strA, String strB, bool ignoreCase, CultureInfo culture)
355         {
356             var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
357             return Compare(strA, strB, culture, options);
358         }
359
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.
363         //
364         public static int Compare(String strA, int indexA, String strB, int indexB, int length)
365         {
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);
369         }
370
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.
374         //
375         public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
376         {
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
382             // however:
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
388             //   parameter names.
389             // Therefore, we have to duplicate some of the logic here.
390
391             int lengthA = length;
392             int lengthB = length;
393
394             if (strA != null)
395             {
396                 lengthA = Math.Min(lengthA, strA.Length - indexA);
397             }
398
399             if (strB != null)
400             {
401                 lengthB = Math.Min(lengthB, strB.Length - indexB);
402             }
403
404             var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
405             return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
406         }
407
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.
412         //
413         public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
414         {
415             var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
416             return Compare(strA, indexA, strB, indexB, length, culture, options);
417         }
418
419
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.
423         //
424         public static int Compare(String strA, int indexA, String strB, int indexB, int length, CultureInfo culture, CompareOptions options)
425         {
426             if (culture == null)
427             {
428                 throw new ArgumentNullException(nameof(culture));
429             }
430
431             int lengthA = length;
432             int lengthB = length;
433
434             if (strA != null)
435             {
436                 lengthA = Math.Min(lengthA, strA.Length - indexA);
437             }
438
439             if (strB != null)
440             {
441                 lengthB = Math.Min(lengthB, strB.Length - indexB);
442             }
443
444             return culture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
445         }
446
447         public static int Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType)
448         {
449             CheckStringComparison(comparisonType);
450
451             if (strA == null || strB == null)
452             {
453
454                 if (object.ReferenceEquals(strA, strB))
455                 {
456                     // They're both null
457                     return 0;
458                 }
459
460                 return strA == null ? -1 : 1;
461             }
462
463             if (length < 0)
464             {
465                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
466             }
467
468             if (indexA < 0 || indexB < 0)
469             {
470                 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
471                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
472             }
473
474             if (strA.Length - indexA < 0 || strB.Length - indexB < 0)
475             {
476                 string paramName = strA.Length - indexA < 0 ? nameof(indexA) : nameof(indexB);
477                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
478             }
479
480             if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
481             {
482                 return 0;
483             }
484
485             int lengthA = Math.Min(length, strA.Length - indexA);
486             int lengthB = Math.Min(length, strB.Length - indexB);
487
488             switch (comparisonType)
489             {
490                 case StringComparison.CurrentCulture:
491                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
492
493                 case StringComparison.CurrentCultureIgnoreCase:
494                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
495
496                 case StringComparison.InvariantCulture:
497                     return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
498
499                 case StringComparison.InvariantCultureIgnoreCase:
500                     return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
501
502                 case StringComparison.Ordinal:
503                     return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
504
505                 case StringComparison.OrdinalIgnoreCase:
506                     return (CompareInfo.CompareOrdinalIgnoreCase(strA, indexA, lengthA, strB, indexB, lengthB));
507
508                 default:
509                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
510             }
511         }
512
513         // Compares strA and strB using an ordinal (code-point) comparison.
514         //
515         public static int CompareOrdinal(String strA, String strB)
516         {
517             if (object.ReferenceEquals(strA, strB))
518             {
519                 return 0;
520             }
521
522             // They can't both be null at this point.
523             if (strA == null)
524             {
525                 return -1;
526             }
527             if (strB == null)
528             {
529                 return 1;
530             }
531
532             // Most common case, first character is different.
533             // This will return false for empty strings.
534             if (strA._firstChar != strB._firstChar)
535             {
536                 return strA._firstChar - strB._firstChar;
537             }
538
539             return CompareOrdinalHelper(strA, strB);
540         }
541
542         internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
543         {
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
546
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);
550
551             int i = 0;
552             if (minLength >= sizeof(nuint) / sizeof(char))
553             {
554                 while (i < minLength - sizeof(nuint) / sizeof(char))
555                 {
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))))
558                     {
559                         break;
560                     }
561                     i += sizeof(nuint) / sizeof(char);
562                 }
563             }
564             while (i < minLength)
565             {
566                 char a = Unsafe.Add(ref first, i);
567                 char b = Unsafe.Add(ref second, i);
568                 if (a != b)
569                 {
570                     return a - b;
571                 }
572                 i++;
573             }
574             return strA.Length - strB.Length;
575         }
576
577         // Compares strA and strB using an ordinal (code-point) comparison.
578         //
579         public static int CompareOrdinal(String strA, int indexA, String strB, int indexB, int length)
580         {
581             if (strA == null || strB == null)
582             {
583                 if (object.ReferenceEquals(strA, strB))
584                 {
585                     // They're both null
586                     return 0;
587                 }
588
589                 return strA == null ? -1 : 1;
590             }
591
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.
594
595             if (length < 0)
596             {
597                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeCount);
598             }
599
600             if (indexA < 0 || indexB < 0)
601             {
602                 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
603                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
604             }
605
606             int lengthA = Math.Min(length, strA.Length - indexA);
607             int lengthB = Math.Min(length, strB.Length - indexB);
608
609             if (lengthA < 0 || lengthB < 0)
610             {
611                 string paramName = lengthA < 0 ? nameof(indexA) : nameof(indexB);
612                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
613             }
614
615             if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
616             {
617                 return 0;
618             }
619
620             return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
621         }
622
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.
626         //
627         public int CompareTo(Object value)
628         {
629             if (value == null)
630             {
631                 return 1;
632             }
633
634             string other = value as string;
635
636             if (other == null)
637             {
638                 throw new ArgumentException(SR.Arg_MustBeString);
639             }
640
641             return CompareTo(other); // will call the string-based overload
642         }
643
644         // Determines the sorting relation of StrB to the current instance.
645         //
646         public int CompareTo(String strB)
647         {
648             return string.Compare(this, strB, StringComparison.CurrentCulture);
649         }
650
651         // Determines whether a specified string is a suffix of the current instance.
652         //
653         // The case-sensitive and culture-sensitive option is set by options,
654         // and the default culture is used.
655         //        
656         public Boolean EndsWith(String value)
657         {
658             return EndsWith(value, StringComparison.CurrentCulture);
659         }
660
661         public Boolean EndsWith(String value, StringComparison comparisonType)
662         {
663             if ((Object)value == null)
664             {
665                 throw new ArgumentNullException(nameof(value));
666             }
667             
668             if ((Object)this == (Object)value)
669             {
670                 CheckStringComparison(comparisonType);
671                 return true;
672             }
673
674             if (value.Length == 0)
675             {
676                 CheckStringComparison(comparisonType);
677                 return true;
678             }
679
680             switch (comparisonType)
681             {
682                 case StringComparison.CurrentCulture:
683                     return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None);
684
685                 case StringComparison.CurrentCultureIgnoreCase:
686                     return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase);
687
688                 case StringComparison.InvariantCulture:
689                     return CompareInfo.Invariant.IsSuffix(this, value, CompareOptions.None);
690
691                 case StringComparison.InvariantCultureIgnoreCase:
692                     return CompareInfo.Invariant.IsSuffix(this, value, CompareOptions.IgnoreCase);
693
694                 case StringComparison.Ordinal:
695                     return this.Length < value.Length ? false : (CompareOrdinalHelper(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
696
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);
699
700                 default:
701                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
702             }
703         }
704
705         public Boolean EndsWith(String value, Boolean ignoreCase, CultureInfo culture)
706         {
707             if (null == value)
708             {
709                 throw new ArgumentNullException(nameof(value));
710             }
711
712             if ((object)this == (object)value)
713             {
714                 return true;
715             }
716
717             CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
718             return referenceCulture.CompareInfo.IsSuffix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
719         }
720
721         public bool EndsWith(char value)
722         {
723             int thisLen = Length;
724             return thisLen != 0 && this[thisLen - 1] == value;
725         }
726
727         // Determines whether two strings match.
728         public override bool Equals(Object obj)
729         {
730             if (object.ReferenceEquals(this, obj))
731                 return true;
732
733             string str = obj as string;
734             if (str == null)
735                 return false;
736
737             if (this.Length != str.Length)
738                 return false;
739
740             return EqualsHelper(this, str);
741         }
742
743         // Determines whether two strings match.
744         public bool Equals(String value)
745         {
746             if (object.ReferenceEquals(this, value))
747                 return true;
748
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.
753             if (value == null)
754                 return false;
755
756             if (this.Length != value.Length)
757                 return false;
758
759             return EqualsHelper(this, value);
760         }
761
762         public bool Equals(String value, StringComparison comparisonType)
763         {
764             if ((Object)this == (Object)value)
765             {
766                 CheckStringComparison(comparisonType);
767                 return true;
768             }
769
770             if ((Object)value == null)
771             {
772                 CheckStringComparison(comparisonType);
773                 return false;
774             }
775
776             switch (comparisonType)
777             {
778                 case StringComparison.CurrentCulture:
779                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0);
780
781                 case StringComparison.CurrentCultureIgnoreCase:
782                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0);
783
784                 case StringComparison.InvariantCulture:
785                     return (CompareInfo.Invariant.Compare(this, value, CompareOptions.None) == 0);
786
787                 case StringComparison.InvariantCultureIgnoreCase:
788                     return (CompareInfo.Invariant.Compare(this, value, CompareOptions.IgnoreCase) == 0);
789
790                 case StringComparison.Ordinal:
791                     if (this.Length != value.Length)
792                         return false;
793                     return EqualsHelper(this, value);
794
795                 case StringComparison.OrdinalIgnoreCase:
796                     if (this.Length != value.Length)
797                         return false;
798
799                     // If both strings are ASCII strings, we can take the fast path.
800                     if (this.IsAscii() && value.IsAscii())
801                     {
802                         return EqualsIgnoreCaseAsciiHelper(this, value);
803                     }
804
805                     return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, this.Length, value, 0, value.Length) == 0);
806
807                 default:
808                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
809             }
810         }
811
812
813         // Determines whether two Strings match.
814         public static bool Equals(String a, String b)
815         {
816             if ((Object)a == (Object)b)
817             {
818                 return true;
819             }
820
821             if ((Object)a == null || (Object)b == null || a.Length != b.Length)
822             {
823                 return false;
824             }
825
826             return EqualsHelper(a, b);
827         }
828
829         public static bool Equals(String a, String b, StringComparison comparisonType)
830         {
831             if ((Object)a == (Object)b)
832             {
833                 CheckStringComparison(comparisonType);
834                 return true;
835             }
836
837             if ((Object)a == null || (Object)b == null)
838             {
839                 CheckStringComparison(comparisonType);
840                 return false;
841             }
842
843             switch (comparisonType)
844             {
845                 case StringComparison.CurrentCulture:
846                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0);
847
848                 case StringComparison.CurrentCultureIgnoreCase:
849                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0);
850
851                 case StringComparison.InvariantCulture:
852                     return (CompareInfo.Invariant.Compare(a, b, CompareOptions.None) == 0);
853
854                 case StringComparison.InvariantCultureIgnoreCase:
855                     return (CompareInfo.Invariant.Compare(a, b, CompareOptions.IgnoreCase) == 0);
856
857                 case StringComparison.Ordinal:
858                     if (a.Length != b.Length)
859                         return false;
860
861                     return EqualsHelper(a, b);
862
863                 case StringComparison.OrdinalIgnoreCase:
864                     if (a.Length != b.Length)
865                         return false;
866                     else
867                     {
868                         // If both strings are ASCII strings, we can take the fast path.
869                         if (a.IsAscii() && b.IsAscii())
870                         {
871                             return EqualsIgnoreCaseAsciiHelper(a, b);
872                         }
873                         // Take the slow path.
874
875                         return (CompareInfo.CompareOrdinalIgnoreCase(a, 0, a.Length, b, 0, b.Length) == 0);
876                     }
877
878                 default:
879                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
880             }
881         }
882
883         public static bool operator ==(String a, String b)
884         {
885             return String.Equals(a, b);
886         }
887
888         public static bool operator !=(String a, String b)
889         {
890             return !String.Equals(a, b);
891         }
892
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()
896         {
897             return Marvin.ComputeHash32(ref Unsafe.As<char, byte>(ref _firstChar), _stringLength * 2, Marvin.DefaultSeed);
898         }
899
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);
903
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
905         // hash table).
906         internal int GetLegacyNonRandomizedHashCode()
907         {
908             unsafe
909             {
910                 fixed (char* src = &_firstChar)
911                 {
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");
914 #if BIT64
915                     int hash1 = 5381;
916 #else // !BIT64 (32)
917                     int hash1 = (5381<<16) + 5381;
918 #endif
919                     int hash2 = hash1;
920
921 #if BIT64
922                     int c;
923                     char* s = src;
924                     while ((c = s[0]) != 0)
925                     {
926                         hash1 = ((hash1 << 5) + hash1) ^ c;
927                         c = s[1];
928                         if (c == 0)
929                             break;
930                         hash2 = ((hash2 << 5) + hash2) ^ c;
931                         s += 2;
932                     }
933 #else // !BIT64 (32)
934                     // 32 bit machines.
935                     int* pint = (int *)src;
936                     int len = this.Length;
937                     while (len > 2)
938                     {
939                         hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
940                         hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
941                         pint += 2;
942                         len  -= 4;
943                     }
944
945                     if (len > 0)
946                     {
947                         hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
948                     }
949 #endif
950 #if DEBUG
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;
956 #endif
957                     return hash1 + (hash2 * 1566083941);
958                 }
959             }
960         }
961
962         // Determines whether a specified string is a prefix of the current instance
963         //
964         public Boolean StartsWith(String value)
965         {
966             if ((Object)value == null)
967             {
968                 throw new ArgumentNullException(nameof(value));
969             }
970             return StartsWith(value, StringComparison.CurrentCulture);
971         }
972
973         public Boolean StartsWith(String value, StringComparison comparisonType)
974         {
975             if ((Object)value == null)
976             {
977                 throw new ArgumentNullException(nameof(value));
978             }
979
980             if ((Object)this == (Object)value)
981             {
982                 CheckStringComparison(comparisonType);
983                 return true;
984             }
985
986             if (value.Length == 0)
987             {
988                 CheckStringComparison(comparisonType);
989                 return true;
990             }
991
992             switch (comparisonType)
993             {
994                 case StringComparison.CurrentCulture:
995                     return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
996
997                 case StringComparison.CurrentCultureIgnoreCase:
998                     return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
999
1000                 case StringComparison.InvariantCulture:
1001                     return CompareInfo.Invariant.IsPrefix(this, value, CompareOptions.None);
1002
1003                 case StringComparison.InvariantCultureIgnoreCase:
1004                     return CompareInfo.Invariant.IsPrefix(this, value, CompareOptions.IgnoreCase);
1005
1006                 case StringComparison.Ordinal:
1007                     if (this.Length < value.Length || _firstChar != value._firstChar)
1008                     {
1009                         return false;
1010                     }
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);
1017
1018                 case StringComparison.OrdinalIgnoreCase:
1019                     if (this.Length < value.Length)
1020                     {
1021                         return false;
1022                     }
1023                     return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, value.Length, value, 0, value.Length) == 0);
1024
1025                 default:
1026                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
1027             }
1028         }
1029
1030         public Boolean StartsWith(String value, Boolean ignoreCase, CultureInfo culture)
1031         {
1032             if (null == value)
1033             {
1034                 throw new ArgumentNullException(nameof(value));
1035             }
1036
1037             if ((object)this == (object)value)
1038             {
1039                 return true;
1040             }
1041
1042             CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
1043             return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
1044         }
1045
1046         public bool StartsWith(char value) => Length != 0 && _firstChar == value;
1047
1048         internal static void CheckStringComparison(StringComparison comparisonType)
1049         {
1050             // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase]
1051             if ((uint)(comparisonType - StringComparison.CurrentCulture) > (StringComparison.OrdinalIgnoreCase - StringComparison.CurrentCulture))
1052             {
1053                 ThrowHelper.ThrowArgumentException(ExceptionResource.NotSupported_StringComparison, ExceptionArgument.comparisonType);
1054             }
1055         }
1056     }
1057 }