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.Diagnostics; 6 using System.Globalization; 7 using System.Runtime.CompilerServices; 8 9 using Internal.Runtime.CompilerServices; 10 11 namespace System 12 { 13 public partial class String 14 { 15 // 16 //Native Static Methods 17 // 18 FastCompareStringHelper(uint* strAChars, int countA, uint* strBChars, int countB)19 private static unsafe int FastCompareStringHelper(uint* strAChars, int countA, uint* strBChars, int countB) 20 { 21 int count = (countA < countB) ? countA : countB; 22 23 #if BIT64 24 long diff = (long)((byte*)strAChars - (byte*)strBChars); 25 #else 26 int diff = (int)((byte*)strAChars - (byte*)strBChars); 27 #endif 28 29 #if BIT64 30 int alignmentA = (int)((long)strAChars) & (sizeof(IntPtr) - 1); 31 int alignmentB = (int)((long)strBChars) & (sizeof(IntPtr) - 1); 32 33 if (alignmentA == alignmentB) 34 { 35 if ((alignmentA == 2 || alignmentA == 6) && (count >= 1)) 36 { 37 char* ptr2 = (char*)strBChars; 38 39 if ((*((char*)((byte*)ptr2 + diff)) - *ptr2) != 0) 40 return ((int)*((char*)((byte*)ptr2 + diff)) - (int)*ptr2); 41 42 strBChars = (uint*)(++ptr2); 43 count -= 1; 44 alignmentA = (alignmentA == 2 ? 4 : 0); 45 } 46 47 if ((alignmentA == 4) && (count >= 2)) 48 { 49 uint* ptr2 = (uint*)strBChars; 50 51 if ((*((uint*)((byte*)ptr2 + diff)) - *ptr2) != 0) 52 { 53 char* chkptr1 = (char*)((byte*)strBChars + diff); 54 char* chkptr2 = (char*)strBChars; 55 56 if (*chkptr1 != *chkptr2) 57 return ((int)*chkptr1 - (int)*chkptr2); 58 return ((int)*(chkptr1 + 1) - (int)*(chkptr2 + 1)); 59 } 60 strBChars = ++ptr2; 61 count -= 2; 62 alignmentA = 0; 63 } 64 65 if ((alignmentA == 0)) 66 { 67 while (count >= 4) 68 { 69 long* ptr2 = (long*)strBChars; 70 71 if ((*((long*)((byte*)ptr2 + diff)) - *ptr2) != 0) 72 { 73 if ((*((uint*)((byte*)ptr2 + diff)) - *(uint*)ptr2) != 0) 74 { 75 char* chkptr1 = (char*)((byte*)strBChars + diff); 76 char* chkptr2 = (char*)strBChars; 77 78 if (*chkptr1 != *chkptr2) 79 return ((int)*chkptr1 - (int)*chkptr2); 80 return ((int)*(chkptr1 + 1) - (int)*(chkptr2 + 1)); 81 } 82 else 83 { 84 char* chkptr1 = (char*)((uint*)((byte*)strBChars + diff) + 1); 85 char* chkptr2 = (char*)((uint*)strBChars + 1); 86 87 if (*chkptr1 != *chkptr2) 88 return ((int)*chkptr1 - (int)*chkptr2); 89 return ((int)*(chkptr1 + 1) - (int)*(chkptr2 + 1)); 90 } 91 } 92 strBChars = (uint*)(++ptr2); 93 count -= 4; 94 } 95 } 96 97 { 98 char* ptr2 = (char*)strBChars; 99 while ((count -= 1) >= 0) 100 { 101 if ((*((char*)((byte*)ptr2 + diff)) - *ptr2) != 0) 102 return ((int)*((char*)((byte*)ptr2 + diff)) - (int)*ptr2); 103 ++ptr2; 104 } 105 } 106 } 107 else 108 #endif // BIT64 109 { 110 #if BIT64 111 if (Math.Abs(alignmentA - alignmentB) == 4) 112 { 113 if ((alignmentA == 2) || (alignmentB == 2)) 114 { 115 char* ptr2 = (char*)strBChars; 116 117 if ((*((char*)((byte*)ptr2 + diff)) - *ptr2) != 0) 118 return ((int)*((char*)((byte*)ptr2 + diff)) - (int)*ptr2); 119 strBChars = (uint*)(++ptr2); 120 count -= 1; 121 } 122 } 123 #endif // BIT64 124 125 // Loop comparing a DWORD at a time. 126 // Reads are potentially unaligned 127 while ((count -= 2) >= 0) 128 { 129 if ((*((uint*)((byte*)strBChars + diff)) - *strBChars) != 0) 130 { 131 char* ptr1 = (char*)((byte*)strBChars + diff); 132 char* ptr2 = (char*)strBChars; 133 if (*ptr1 != *ptr2) 134 return ((int)*ptr1 - (int)*ptr2); 135 return ((int)*(ptr1 + 1) - (int)*(ptr2 + 1)); 136 } 137 ++strBChars; 138 } 139 140 int c; 141 if (count == -1) 142 if ((c = *((char*)((byte*)strBChars + diff)) - *((char*)strBChars)) != 0) 143 return c; 144 } 145 146 return countA - countB; 147 } 148 149 // 150 // 151 // NATIVE INSTANCE METHODS 152 // 153 // 154 155 // 156 // Search/Query methods 157 // 158 159 // 160 // Common worker for the various Equality methods. The caller must have already ensured that 161 // both strings are non-null and that their lengths are equal. Ther caller should also have 162 // done the Object.ReferenceEquals() fastpath check as we won't repeat it here. 163 // EqualsHelper(String strA, String strB)164 private static unsafe bool EqualsHelper(String strA, String strB) 165 { 166 Debug.Assert(strA != null); 167 Debug.Assert(strB != null); 168 Debug.Assert(strA.Length == strB.Length); 169 170 int length = strA.Length; 171 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar) 172 { 173 char* a = ap; 174 char* b = bp; 175 176 #if BIT64 177 // Single int read aligns pointers for the following long reads 178 // PERF: No length check needed as there is always an int32 worth of string allocated 179 // This read can also include the null terminator which both strings will have 180 if (*(int*)a != *(int*)b) return false; 181 length -= 2; a += 2; b += 2; 182 183 // for 64-bit platforms we unroll by 12 and 184 // check 3 qword at a time. This is less code 185 // than the 32 bit case and is a shorter path length 186 187 while (length >= 12) 188 { 189 if (*(long*)a != *(long*)b) goto ReturnFalse; 190 if (*(long*)(a + 4) != *(long*)(b + 4)) goto ReturnFalse; 191 if (*(long*)(a + 8) != *(long*)(b + 8)) goto ReturnFalse; 192 length -= 12; a += 12; b += 12; 193 } 194 #else 195 while (length >= 10) 196 { 197 if (*(int*)a != *(int*)b) goto ReturnFalse; 198 if (*(int*)(a + 2) != *(int*)(b + 2)) goto ReturnFalse; 199 if (*(int*)(a + 4) != *(int*)(b + 4)) goto ReturnFalse; 200 if (*(int*)(a + 6) != *(int*)(b + 6)) goto ReturnFalse; 201 if (*(int*)(a + 8) != *(int*)(b + 8)) goto ReturnFalse; 202 length -= 10; a += 10; b += 10; 203 } 204 #endif 205 206 // This depends on the fact that the String objects are 207 // always zero terminated and that the terminating zero is not included 208 // in the length. For odd string sizes, the last compare will include 209 // the zero terminator. 210 while (length > 0) 211 { 212 if (*(int*)a != *(int*)b) goto ReturnFalse; 213 length -= 2; a += 2; b += 2; 214 } 215 216 return true; 217 218 ReturnFalse: 219 return false; 220 } 221 } 222 StartsWithOrdinalHelper(String str, String startsWith)223 private static unsafe bool StartsWithOrdinalHelper(String str, String startsWith) 224 { 225 Debug.Assert(str != null); 226 Debug.Assert(startsWith != null); 227 Debug.Assert(str.Length >= startsWith.Length); 228 229 int length = startsWith.Length; 230 231 fixed (char* ap = &str._firstChar) fixed (char* bp = &startsWith._firstChar) 232 { 233 char* a = ap; 234 char* b = bp; 235 236 #if BIT64 237 // Single int read aligns pointers for the following long reads 238 // No length check needed as this method is called when length >= 2 239 Debug.Assert(length >= 2); 240 if (*(int*)a != *(int*)b) goto ReturnFalse; 241 length -= 2; a += 2; b += 2; 242 243 while (length >= 12) 244 { 245 if (*(long*)a != *(long*)b) goto ReturnFalse; 246 if (*(long*)(a + 4) != *(long*)(b + 4)) goto ReturnFalse; 247 if (*(long*)(a + 8) != *(long*)(b + 8)) goto ReturnFalse; 248 length -= 12; a += 12; b += 12; 249 } 250 #else 251 while (length >= 10) 252 { 253 if (*(int*)a != *(int*)b) goto ReturnFalse; 254 if (*(int*)(a + 2) != *(int*)(b + 2)) goto ReturnFalse; 255 if (*(int*)(a + 4) != *(int*)(b + 4)) goto ReturnFalse; 256 if (*(int*)(a + 6) != *(int*)(b + 6)) goto ReturnFalse; 257 if (*(int*)(a + 8) != *(int*)(b + 8)) goto ReturnFalse; 258 length -= 10; a += 10; b += 10; 259 } 260 #endif 261 262 while (length >= 2) 263 { 264 if (*(int*)a != *(int*)b) goto ReturnFalse; 265 length -= 2; a += 2; b += 2; 266 } 267 268 // PERF: This depends on the fact that the String objects are always zero terminated 269 // and that the terminating zero is not included in the length. For even string sizes 270 // this compare can include the zero terminator. Bitwise OR avoids a branch. 271 return length == 0 | *a == *b; 272 273 ReturnFalse: 274 return false; 275 } 276 } 277 CompareOrdinalHelper(String strA, String strB)278 private static unsafe int CompareOrdinalHelper(String strA, String strB) 279 { 280 Debug.Assert(strA != null); 281 Debug.Assert(strB != null); 282 283 // NOTE: This may be subject to change if eliminating the check 284 // in the callers makes them small enough to be inlined 285 Debug.Assert(strA._firstChar == strB._firstChar, 286 "For performance reasons, callers of this method should " + 287 "check/short-circuit beforehand if the first char is the same."); 288 289 int length = Math.Min(strA.Length, strB.Length); 290 291 fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar) 292 { 293 char* a = ap; 294 char* b = bp; 295 296 // Check if the second chars are different here 297 // The reason we check if _firstChar is different is because 298 // it's the most common case and allows us to avoid a method call 299 // to here. 300 // The reason we check if the second char is different is because 301 // if the first two chars the same we can increment by 4 bytes, 302 // leaving us word-aligned on both 32-bit (12 bytes into the string) 303 // and 64-bit (16 bytes) platforms. 304 305 // For empty strings, the second char will be null due to padding. 306 // The start of the string is the EE type pointer + string length, 307 // which takes up 8 bytes on 32-bit, 12 on x64. For empty strings, 308 // the null terminator immediately follows, leaving us with an object 309 // 10/14 bytes in size. Since everything needs to be a multiple 310 // of 4/8, this will get padded and zeroed out. 311 312 // For one-char strings the second char will be the null terminator. 313 314 // NOTE: If in the future there is a way to read the second char 315 // without pinning the string (e.g. System.Runtime.CompilerServices.Unsafe 316 // is exposed to mscorlib, or a future version of C# allows inline IL), 317 // then do that and short-circuit before the fixed. 318 319 if (*(a + 1) != *(b + 1)) goto DiffOffset1; 320 321 // Since we know that the first two chars are the same, 322 // we can increment by 2 here and skip 4 bytes. 323 // This leaves us 8-byte aligned, which results 324 // on better perf for 64-bit platforms. 325 length -= 2; a += 2; b += 2; 326 327 // unroll the loop 328 #if BIT64 329 while (length >= 12) 330 { 331 if (*(long*)a != *(long*)b) goto DiffOffset0; 332 if (*(long*)(a + 4) != *(long*)(b + 4)) goto DiffOffset4; 333 if (*(long*)(a + 8) != *(long*)(b + 8)) goto DiffOffset8; 334 length -= 12; a += 12; b += 12; 335 } 336 #else // BIT64 337 while (length >= 10) 338 { 339 if (*(int*)a != *(int*)b) goto DiffOffset0; 340 if (*(int*)(a + 2) != *(int*)(b + 2)) goto DiffOffset2; 341 if (*(int*)(a + 4) != *(int*)(b + 4)) goto DiffOffset4; 342 if (*(int*)(a + 6) != *(int*)(b + 6)) goto DiffOffset6; 343 if (*(int*)(a + 8) != *(int*)(b + 8)) goto DiffOffset8; 344 length -= 10; a += 10; b += 10; 345 } 346 #endif // BIT64 347 348 // Fallback loop: 349 // go back to slower code path and do comparison on 4 bytes at a time. 350 // This depends on the fact that the String objects are 351 // always zero terminated and that the terminating zero is not included 352 // in the length. For odd string sizes, the last compare will include 353 // the zero terminator. 354 while (length > 0) 355 { 356 if (*(int*)a != *(int*)b) goto DiffNextInt; 357 length -= 2; 358 a += 2; 359 b += 2; 360 } 361 362 // At this point, we have compared all the characters in at least one string. 363 // The longer string will be larger. 364 return strA.Length - strB.Length; 365 366 #if BIT64 367 DiffOffset8: a += 4; b += 4; 368 DiffOffset4: a += 4; b += 4; 369 #else // BIT64 370 // Use jumps instead of falling through, since 371 // otherwise going to DiffOffset8 will involve 372 // 8 add instructions before getting to DiffNextInt 373 DiffOffset8: a += 8; b += 8; goto DiffOffset0; 374 DiffOffset6: a += 6; b += 6; goto DiffOffset0; 375 DiffOffset4: a += 2; b += 2; 376 DiffOffset2: a += 2; b += 2; 377 #endif // BIT64 378 379 DiffOffset0: 380 // If we reached here, we already see a difference in the unrolled loop above 381 #if BIT64 382 if (*(int*)a == *(int*)b) 383 { 384 a += 2; b += 2; 385 } 386 #endif // BIT64 387 388 DiffNextInt: 389 if (*a != *b) return *a - *b; 390 391 DiffOffset1: 392 Debug.Assert(*(a + 1) != *(b + 1), "This char must be different if we reach here!"); 393 return *(a + 1) - *(b + 1); 394 } 395 } 396 CompareOrdinalHelper(string strA, int indexA, int countA, string strB, int indexB, int countB)397 internal static unsafe int CompareOrdinalHelper(string strA, int indexA, int countA, string strB, int indexB, int countB) 398 { 399 // Argument validation should be handled by callers. 400 Debug.Assert(strA != null && strB != null); 401 Debug.Assert(indexA >= 0 && indexB >= 0); 402 Debug.Assert(countA >= 0 && countB >= 0); 403 Debug.Assert(countA <= strA.Length - indexA); 404 Debug.Assert(countB <= strB.Length - indexB); 405 406 // Set up the loop variables. 407 fixed (char* pStrA = &strA._firstChar, pStrB = &strB._firstChar) 408 { 409 char* strAChars = pStrA + indexA; 410 char* strBChars = pStrB + indexB; 411 return FastCompareStringHelper((uint*)strAChars, countA, (uint*)strBChars, countB); 412 } 413 } 414 Compare(String strA, String strB)415 public static int Compare(String strA, String strB) 416 { 417 return Compare(strA, strB, StringComparison.CurrentCulture); 418 } 419 Compare(String strA, String strB, bool ignoreCase)420 public static int Compare(String strA, String strB, bool ignoreCase) 421 { 422 var comparisonType = ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture; 423 return Compare(strA, strB, comparisonType); 424 } 425 426 // Provides a more flexible function for string comparision. See StringComparison 427 // for meaning of different comparisonType. Compare(String strA, String strB, StringComparison comparisonType)428 public static int Compare(String strA, String strB, StringComparison comparisonType) 429 { 430 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) 431 { 432 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); 433 } 434 435 if (object.ReferenceEquals(strA, strB)) 436 { 437 return 0; 438 } 439 440 // They can't both be null at this point. 441 if (strA == null) 442 { 443 return -1; 444 } 445 if (strB == null) 446 { 447 return 1; 448 } 449 450 451 switch (comparisonType) 452 { 453 case StringComparison.CurrentCulture: 454 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, 0, strA.Length, strB, 0, strB.Length, CompareOptions.None); 455 456 case StringComparison.CurrentCultureIgnoreCase: 457 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, 0, strA.Length, strB, 0, strB.Length, CompareOptions.IgnoreCase); 458 459 case StringComparison.Ordinal: 460 // Most common case: first character is different. 461 // Returns false for empty strings. 462 if (strA._firstChar != strB._firstChar) 463 { 464 return strA._firstChar - strB._firstChar; 465 } 466 467 return CompareOrdinalHelper(strA, strB); 468 469 case StringComparison.OrdinalIgnoreCase: 470 return CompareInfo.CompareOrdinalIgnoreCase(strA, 0, strA.Length, strB, 0, strB.Length); 471 472 case StringComparison.InvariantCulture: 473 return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, strB, CompareOptions.None); 474 475 case StringComparison.InvariantCultureIgnoreCase: 476 return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreCase); 477 478 default: 479 throw new NotSupportedException(SR.NotSupported_StringComparison); 480 } 481 } 482 483 // Provides a culture-correct string comparison. strA is compared to strB 484 // to determine whether it is lexicographically less, equal, or greater, and then a 485 // negative integer, 0, or a positive integer is returned; respectively. 486 // Compare(String strA, String strB, CultureInfo culture, CompareOptions options)487 public static int Compare(String strA, String strB, CultureInfo culture, CompareOptions options) 488 { 489 if (culture == null) 490 { 491 throw new ArgumentNullException(nameof(culture)); 492 } 493 494 return culture.CompareInfo.Compare(strA, strB, options); 495 } 496 497 // Provides a culture-correct string comparison. strA is compared to strB 498 // to determine whether it is lexicographically less, equal, or greater, and then a 499 // negative integer, 0, or a positive integer is returned; respectively. 500 // The case-sensitive option is set by ignoreCase, and the culture is set 501 // by culture 502 // Compare(String strA, String strB, bool ignoreCase, CultureInfo culture)503 public static int Compare(String strA, String strB, bool ignoreCase, CultureInfo culture) 504 { 505 CompareOptions options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None; 506 return Compare(strA, strB, culture, options); 507 } 508 509 // Determines whether two string regions match. The substring of strA beginning 510 // at indexA of length length is compared with the substring of strB 511 // beginning at indexB of the same length. 512 // 513 Compare(String strA, int indexA, String strB, int indexB, int length)514 public static int Compare(String strA, int indexA, String strB, int indexB, int length) 515 { 516 // NOTE: It's important we call the boolean overload, and not the StringComparison 517 // one. The two have some subtly different behavior (see notes in the former). 518 return Compare(strA, indexA, strB, indexB, length, ignoreCase: false); 519 } 520 521 // Determines whether two string regions match. The substring of strA beginning 522 // at indexA of length count is compared with the substring of strB 523 // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean. 524 // 525 Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)526 public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase) 527 { 528 // Ideally we would just forward to the string.Compare overload that takes 529 // a StringComparison parameter, and just pass in CurrentCulture/CurrentCultureIgnoreCase. 530 // That function will return early if an optimization can be applied, e.g. if 531 // (object)strA == strB && indexA == indexB then it will return 0 straightaway. 532 // There are a couple of subtle behavior differences that prevent us from doing so 533 // however: 534 // - string.Compare(null, -1, null, -1, -1, StringComparison.CurrentCulture) works 535 // since that method also returns early for nulls before validation. It shouldn't 536 // for this overload. 537 // - Since we originally forwarded to FormatProvider for all of the argument 538 // validation logic, the ArgumentOutOfRangeExceptions thrown will contain different 539 // parameter names. 540 // Therefore, we have to duplicate some of the logic here. 541 542 int lengthA = length; 543 int lengthB = length; 544 545 if (strA != null) 546 { 547 lengthA = Math.Min(lengthA, strA.Length - indexA); 548 } 549 550 if (strB != null) 551 { 552 lengthB = Math.Min(lengthB, strB.Length - indexB); 553 } 554 555 CompareOptions options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None; 556 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options); 557 } 558 559 // Determines whether two string regions match. The substring of strA beginning 560 // at indexA of length length is compared with the substring of strB 561 // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean, 562 // and the culture is set by culture. 563 // Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)564 public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture) 565 { 566 CompareOptions options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None; 567 return Compare(strA, indexA, strB, indexB, length, culture, options); 568 } 569 570 // Determines whether two string regions match. The substring of strA beginning 571 // at indexA of length length is compared with the substring of strB 572 // beginning at indexB of the same length. 573 // Compare(String strA, int indexA, String strB, int indexB, int length, CultureInfo culture, CompareOptions options)574 public static int Compare(String strA, int indexA, String strB, int indexB, int length, CultureInfo culture, CompareOptions options) 575 { 576 if (culture == null) 577 { 578 throw new ArgumentNullException(nameof(culture)); 579 } 580 581 int lengthA = length; 582 int lengthB = length; 583 584 if (strA != null) 585 { 586 lengthA = Math.Min(lengthA, strA.Length - indexA); 587 } 588 589 if (strB != null) 590 { 591 lengthB = Math.Min(lengthB, strB.Length - indexB); 592 } 593 594 return culture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options); 595 } 596 Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType)597 public static int Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType) 598 { 599 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) 600 { 601 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); 602 } 603 604 if (strA == null || strB == null) 605 { 606 if (object.ReferenceEquals(strA, strB)) 607 { 608 // They're both null 609 return 0; 610 } 611 612 return strA == null ? -1 : 1; 613 } 614 615 // @TODO: Spec#: Figure out what to do here with the return statement above. 616 if (length < 0) 617 { 618 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength); 619 } 620 621 if (indexA < 0 || indexB < 0) 622 { 623 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB); 624 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index); 625 } 626 627 if (strA.Length - indexA < 0 || strB.Length - indexB < 0) 628 { 629 string paramName = strA.Length - indexA < 0 ? nameof(indexA) : nameof(indexB); 630 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index); 631 } 632 633 if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB)) 634 { 635 return 0; 636 } 637 638 int lengthA = Math.Min(length, strA.Length - indexA); 639 int lengthB = Math.Min(length, strB.Length - indexB); 640 641 switch (comparisonType) 642 { 643 case StringComparison.CurrentCulture: 644 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None); 645 646 case StringComparison.CurrentCultureIgnoreCase: 647 return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase); 648 649 case StringComparison.Ordinal: 650 return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB); 651 652 case StringComparison.OrdinalIgnoreCase: 653 return CompareInfo.CompareOrdinalIgnoreCase(strA, indexA, lengthA, strB, indexB, lengthB); 654 655 case StringComparison.InvariantCulture: 656 return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None); 657 658 case StringComparison.InvariantCultureIgnoreCase: 659 return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase); 660 661 default: 662 throw new ArgumentException(SR.NotSupported_StringComparison); 663 } 664 } 665 666 // Compares strA and strB using an ordinal (code-point) comparison. 667 // 668 CompareOrdinal(String strA, String strB)669 public static int CompareOrdinal(String strA, String strB) 670 { 671 if (object.ReferenceEquals(strA, strB)) 672 { 673 return 0; 674 } 675 676 // They can't both be null at this point. 677 if (strA == null) 678 { 679 return -1; 680 } 681 if (strB == null) 682 { 683 return 1; 684 } 685 686 // Most common case, first character is different. 687 // This will return false for empty strings. 688 if (strA._firstChar != strB._firstChar) 689 { 690 return strA._firstChar - strB._firstChar; 691 } 692 693 return CompareOrdinalHelper(strA, strB); 694 } 695 696 // TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly? CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)697 internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB) 698 { 699 // TODO: This needs to be optimized / unrolled. It can't just use CompareOrdinalHelper(str, str) 700 // (changed to accept spans) because its implementation is based on a string layout, 701 // in a way that doesn't work when there isn't guaranteed to be a null terminator. 702 703 int minLength = Math.Min(strA.Length, strB.Length); 704 for (int i = 0; i < minLength; i++) 705 { 706 if (strA[i] != strB[i]) 707 { 708 return strA[i] - strB[i]; 709 } 710 } 711 712 return strA.Length - strB.Length; 713 } 714 CompareOrdinal(String strA, int indexA, String strB, int indexB, int length)715 public static int CompareOrdinal(String strA, int indexA, String strB, int indexB, int length) 716 { 717 if (strA == null || strB == null) 718 { 719 if (object.ReferenceEquals(strA, strB)) 720 { 721 // They're both null 722 return 0; 723 } 724 725 return strA == null ? -1 : 1; 726 } 727 728 // COMPAT: Checking for nulls should become before the arguments are validated, 729 // but other optimizations which allow us to return early should come after. 730 731 if (length < 0) 732 { 733 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeCount); 734 } 735 736 if (indexA < 0 || indexB < 0) 737 { 738 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB); 739 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index); 740 } 741 742 int lengthA = Math.Min(length, strA.Length - indexA); 743 int lengthB = Math.Min(length, strB.Length - indexB); 744 745 if (lengthA < 0 || lengthB < 0) 746 { 747 string paramName = lengthA < 0 ? nameof(indexA) : nameof(indexB); 748 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index); 749 } 750 751 if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB)) 752 { 753 return 0; 754 } 755 756 return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB); 757 } 758 759 // Compares this String to another String (cast as object), returning an integer that 760 // indicates the relationship. This method returns a value less than 0 if this is less than value, 0 761 // if this is equal to value, or a value greater than 0 if this is greater than value. 762 // 763 CompareTo(Object value)764 public int CompareTo(Object value) 765 { 766 if (value == null) 767 { 768 return 1; 769 } 770 771 string other = value as string; 772 773 if (other == null) 774 { 775 throw new ArgumentException(SR.Arg_MustBeString); 776 } 777 778 return CompareTo(other); // will call the string-based overload 779 } 780 781 // Determines the sorting relation of StrB to the current instance. 782 // 783 CompareTo(String strB)784 public int CompareTo(String strB) 785 { 786 return string.Compare(this, strB, StringComparison.CurrentCulture); 787 } 788 789 // Determines whether a specified string is a suffix of the the current instance. 790 // 791 // The case-sensitive and culture-sensitive option is set by options, 792 // and the default culture is used. 793 // 794 EndsWith(String value)795 public Boolean EndsWith(String value) 796 { 797 return EndsWith(value, StringComparison.CurrentCulture); 798 } 799 EndsWith(String value, StringComparison comparisonType)800 public Boolean EndsWith(String value, StringComparison comparisonType) 801 { 802 if ((Object)value == null) 803 { 804 throw new ArgumentNullException(nameof(value)); 805 } 806 807 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) 808 { 809 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); 810 } 811 812 if ((Object)this == (Object)value) 813 { 814 return true; 815 } 816 817 if (value.Length == 0) 818 { 819 return true; 820 } 821 822 switch (comparisonType) 823 { 824 case StringComparison.CurrentCulture: 825 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None); 826 827 case StringComparison.CurrentCultureIgnoreCase: 828 return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase); 829 830 case StringComparison.Ordinal: 831 return this.Length < value.Length ? false : (CompareOrdinalHelper(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0); 832 833 case StringComparison.OrdinalIgnoreCase: 834 return this.Length < value.Length ? false : (CompareInfo.CompareOrdinalIgnoreCase(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0); 835 836 case StringComparison.InvariantCulture: 837 return CultureInfo.InvariantCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None); 838 839 case StringComparison.InvariantCultureIgnoreCase: 840 return CultureInfo.InvariantCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase); 841 842 default: 843 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); 844 } 845 } 846 EndsWith(String value, Boolean ignoreCase, CultureInfo culture)847 public Boolean EndsWith(String value, Boolean ignoreCase, CultureInfo culture) 848 { 849 if (null == value) 850 { 851 throw new ArgumentNullException(nameof(value)); 852 } 853 854 if ((object)this == (object)value) 855 { 856 return true; 857 } 858 859 CultureInfo referenceCulture; 860 if (culture == null) 861 referenceCulture = CultureInfo.CurrentCulture; 862 else 863 referenceCulture = culture; 864 865 return referenceCulture.CompareInfo.IsSuffix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); 866 } 867 EndsWith(char value)868 public bool EndsWith(char value) 869 { 870 int thisLen = Length; 871 return thisLen != 0 && this[thisLen - 1] == value; 872 } 873 874 // Determines whether two strings match. 875 Equals(Object obj)876 public override bool Equals(Object obj) 877 { 878 if (Object.ReferenceEquals(this, obj)) 879 return true; 880 881 String str = obj as String; 882 if (str == null) 883 return false; 884 885 if (this.Length != str.Length) 886 return false; 887 888 return EqualsHelper(this, str); 889 } 890 891 // Determines whether two strings match. 892 893 Equals(String value)894 public bool Equals(String value) 895 { 896 if (Object.ReferenceEquals(this, value)) 897 return true; 898 899 // NOTE: No need to worry about casting to object here. 900 // If either side of an == comparison between strings 901 // is null, Roslyn generates a simple ceq instruction 902 // instead of calling string.op_Equality. 903 if (value == null) 904 return false; 905 906 if (this.Length != value.Length) 907 return false; 908 909 return EqualsHelper(this, value); 910 } 911 912 Equals(String value, StringComparison comparisonType)913 public bool Equals(String value, StringComparison comparisonType) 914 { 915 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) 916 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); 917 918 if ((Object)this == (Object)value) 919 { 920 return true; 921 } 922 923 if ((Object)value == null) 924 { 925 return false; 926 } 927 928 switch (comparisonType) 929 { 930 case StringComparison.CurrentCulture: 931 return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, 0, this.Length, value, 0, value.Length, CompareOptions.None) == 0); 932 933 case StringComparison.CurrentCultureIgnoreCase: 934 return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, 0, this.Length, value, 0, value.Length, CompareOptions.IgnoreCase) == 0); 935 936 case StringComparison.Ordinal: 937 if (this.Length != value.Length) 938 return false; 939 return EqualsHelper(this, value); 940 941 case StringComparison.OrdinalIgnoreCase: 942 if (this.Length != value.Length) 943 return false; 944 else 945 { 946 return CompareInfo.CompareOrdinalIgnoreCase(this, 0, this.Length, value, 0, value.Length) == 0; 947 } 948 949 case StringComparison.InvariantCulture: 950 return (CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0); 951 952 case StringComparison.InvariantCultureIgnoreCase: 953 return (CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0); 954 955 default: 956 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); 957 } 958 } 959 960 // Determines whether two Strings match. 961 Equals(String a, String b)962 public static bool Equals(String a, String b) 963 { 964 if ((Object)a == (Object)b) 965 { 966 return true; 967 } 968 969 if ((Object)a == null || (Object)b == null || a.Length != b.Length) 970 { 971 return false; 972 } 973 974 return EqualsHelper(a, b); 975 } 976 Equals(String a, String b, StringComparison comparisonType)977 public static bool Equals(String a, String b, StringComparison comparisonType) 978 { 979 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) 980 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); 981 982 if ((Object)a == (Object)b) 983 { 984 return true; 985 } 986 987 if ((Object)a == null || (Object)b == null) 988 { 989 return false; 990 } 991 992 switch (comparisonType) 993 { 994 case StringComparison.CurrentCulture: 995 return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, 0, a.Length, b, 0, b.Length, CompareOptions.None) == 0); 996 997 case StringComparison.CurrentCultureIgnoreCase: 998 return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, 0, a.Length, b, 0, b.Length, CompareOptions.IgnoreCase) == 0); 999 1000 case StringComparison.Ordinal: 1001 if (a.Length != b.Length) 1002 return false; 1003 return EqualsHelper(a, b); 1004 1005 case StringComparison.OrdinalIgnoreCase: 1006 if (a.Length != b.Length) 1007 return false; 1008 else 1009 { 1010 return CompareInfo.CompareOrdinalIgnoreCase(a, 0, a.Length, b, 0, b.Length) == 0; 1011 } 1012 1013 case StringComparison.InvariantCulture: 1014 return (CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0); 1015 1016 case StringComparison.InvariantCultureIgnoreCase: 1017 return (CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0); 1018 1019 default: 1020 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); 1021 } 1022 } 1023 operator ==(String a, String b)1024 public static bool operator ==(String a, String b) 1025 { 1026 if (Object.ReferenceEquals(a, b)) 1027 return true; 1028 if (a == null || b == null || a.Length != b.Length) 1029 return false; 1030 return EqualsHelper(a, b); 1031 } 1032 operator !=(String a, String b)1033 public static bool operator !=(String a, String b) 1034 { 1035 if (Object.ReferenceEquals(a, b)) 1036 return false; 1037 if (a == null || b == null || a.Length != b.Length) 1038 return true; 1039 return !EqualsHelper(a, b); 1040 } 1041 1042 // Gets a hash code for this string. If strings A and B are such that A.Equals(B), then 1043 // they will return the same hash code. GetHashCode()1044 public override int GetHashCode() 1045 { 1046 return Marvin.ComputeHash32(ref Unsafe.As<char, byte>(ref _firstChar), _stringLength * 2, Marvin.DefaultSeed); 1047 } 1048 1049 // Gets a hash code for this string and this comparison. If strings A and B and comparison C are such 1050 // that String.Equals(A, B, C), then they will return the same hash code with this comparison C. 1051 public int GetHashCode(StringComparison comparisonType) => StringComparer.FromComparison(comparisonType).GetHashCode(this); 1052 1053 // Use this if and only if you need the hashcode to not change across app domains (e.g. you have an app domain agile 1054 // hash table). GetLegacyNonRandomizedHashCode()1055 internal int GetLegacyNonRandomizedHashCode() 1056 { 1057 unsafe 1058 { 1059 fixed (char* src = &_firstChar) 1060 { 1061 Debug.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'"); 1062 Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary"); 1063 #if BIT64 1064 int hash1 = 5381; 1065 #else // !BIT64 (32) 1066 int hash1 = (5381<<16) + 5381; 1067 #endif 1068 int hash2 = hash1; 1069 1070 #if BIT64 1071 int c; 1072 char* s = src; 1073 while ((c = s[0]) != 0) 1074 { 1075 hash1 = ((hash1 << 5) + hash1) ^ c; 1076 c = s[1]; 1077 if (c == 0) 1078 break; 1079 hash2 = ((hash2 << 5) + hash2) ^ c; 1080 s += 2; 1081 } 1082 #else // !BIT64 (32) 1083 // 32 bit machines. 1084 int* pint = (int *)src; 1085 int len = this.Length; 1086 while (len > 2) 1087 { 1088 hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0]; 1089 hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1]; 1090 pint += 2; 1091 len -= 4; 1092 } 1093 1094 if (len > 0) 1095 { 1096 hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0]; 1097 } 1098 #endif 1099 return hash1 + (hash2 * 1566083941); 1100 } 1101 } 1102 } 1103 1104 // Determines whether a specified string is a prefix of the current instance 1105 // StartsWith(String value)1106 public Boolean StartsWith(String value) 1107 { 1108 if ((Object)value == null) 1109 { 1110 throw new ArgumentNullException(nameof(value)); 1111 } 1112 return StartsWith(value, StringComparison.CurrentCulture); 1113 } 1114 StartsWith(String value, StringComparison comparisonType)1115 public Boolean StartsWith(String value, StringComparison comparisonType) 1116 { 1117 if ((Object)value == null) 1118 { 1119 throw new ArgumentNullException(nameof(value)); 1120 } 1121 1122 if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) 1123 { 1124 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); 1125 } 1126 1127 if ((Object)this == (Object)value) 1128 { 1129 return true; 1130 } 1131 1132 if (value.Length == 0) 1133 { 1134 return true; 1135 } 1136 1137 switch (comparisonType) 1138 { 1139 case StringComparison.CurrentCulture: 1140 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None); 1141 1142 case StringComparison.CurrentCultureIgnoreCase: 1143 return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase); 1144 1145 case StringComparison.Ordinal: 1146 if (this.Length < value.Length || _firstChar != value._firstChar) 1147 { 1148 return false; 1149 } 1150 return (value.Length == 1) ? 1151 true : // First char is the same and thats all there is to compare 1152 StartsWithOrdinalHelper(this, value); 1153 1154 case StringComparison.OrdinalIgnoreCase: 1155 if (this.Length < value.Length) 1156 { 1157 return false; 1158 } 1159 return CompareInfo.CompareOrdinalIgnoreCase(this, 0, value.Length, value, 0, value.Length) == 0; 1160 1161 case StringComparison.InvariantCulture: 1162 return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None); 1163 1164 case StringComparison.InvariantCultureIgnoreCase: 1165 return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase); 1166 1167 default: 1168 throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType)); 1169 } 1170 } 1171 StartsWith(String value, Boolean ignoreCase, CultureInfo culture)1172 public Boolean StartsWith(String value, Boolean ignoreCase, CultureInfo culture) 1173 { 1174 if (null == value) 1175 { 1176 throw new ArgumentNullException(nameof(value)); 1177 } 1178 1179 if ((object)this == (object)value) 1180 { 1181 return true; 1182 } 1183 1184 CultureInfo referenceCulture; 1185 if (culture == null) 1186 referenceCulture = CultureInfo.CurrentCulture; 1187 else 1188 referenceCulture = culture; 1189 1190 return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); 1191 } 1192 StartsWith(char value)1193 public bool StartsWith(char value) => Length != 0 && _firstChar == value; 1194 } 1195 } 1196