summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/String.Comparison.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/mscorlib/src/System/String.Comparison.cs')
-rw-r--r--src/mscorlib/src/System/String.Comparison.cs1160
1 files changed, 1160 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/String.Comparison.cs b/src/mscorlib/src/System/String.Comparison.cs
new file mode 100644
index 0000000000..a05f9f201a
--- /dev/null
+++ b/src/mscorlib/src/System/String.Comparison.cs
@@ -0,0 +1,1160 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ public partial class String
+ {
+ //
+ //Native Static Methods
+ //
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ private unsafe static int CompareOrdinalIgnoreCaseHelper(String strA, String strB)
+ {
+ Contract.Requires(strA != null);
+ Contract.Requires(strB != null);
+ Contract.EndContractBlock();
+ int length = Math.Min(strA.Length, strB.Length);
+
+ fixed (char* ap = &strA.m_firstChar) fixed (char* bp = &strB.m_firstChar)
+ {
+ char* a = ap;
+ char* b = bp;
+
+ while (length != 0)
+ {
+ int charA = *a;
+ int charB = *b;
+
+ Contract.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
+
+ // uppercase both chars - notice that we need just one compare per char
+ if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
+ if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
+
+ //Return the (case-insensitive) difference between them.
+ if (charA != charB)
+ return charA - charB;
+
+ // Next char
+ a++; b++;
+ length--;
+ }
+
+ return strA.Length - strB.Length;
+ }
+ }
+
+ // native call to COMString::CompareOrdinalEx
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern int CompareOrdinalHelper(String strA, int indexA, int countA, String strB, int indexB, int countB);
+
+ //This will not work in case-insensitive mode for any character greater than 0x80.
+ //We'll throw an ArgumentException.
+ [System.Security.SecurityCritical] // auto-generated
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ unsafe internal static extern int nativeCompareOrdinalIgnoreCaseWC(String strA, sbyte *strBBytes);
+
+ //
+ //
+ // NATIVE INSTANCE METHODS
+ //
+ //
+
+ //
+ // Search/Query methods
+ //
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ private unsafe static bool EqualsHelper(String strA, String strB)
+ {
+ Contract.Requires(strA != null);
+ Contract.Requires(strB != null);
+ Contract.Requires(strA.Length == strB.Length);
+
+ int length = strA.Length;
+
+ fixed (char* ap = &strA.m_firstChar) fixed (char* bp = &strB.m_firstChar)
+ {
+ char* a = ap;
+ char* b = bp;
+
+#if BIT64
+ // Single int read aligns pointers for the following long reads
+ // PERF: No length check needed as there is always an int32 worth of string allocated
+ // This read can also include the null terminator which both strings will have
+ if (*(int*)a != *(int*)b) return false;
+ length -= 2; a += 2; b += 2;
+
+ // for AMD64 bit platform we unroll by 12 and
+ // check 3 qword at a time. This is less code
+ // than the 32 bit case and is a shorter path length.
+
+ while (length >= 12)
+ {
+ if (*(long*)a != *(long*)b) goto ReturnFalse;
+ if (*(long*)(a + 4) != *(long*)(b + 4)) goto ReturnFalse;
+ if (*(long*)(a + 8) != *(long*)(b + 8)) goto ReturnFalse;
+ length -= 12; a += 12; b += 12;
+ }
+#else
+ while (length >= 10)
+ {
+ if (*(int*)a != *(int*)b) goto ReturnFalse;
+ if (*(int*)(a + 2) != *(int*)(b + 2)) goto ReturnFalse;
+ if (*(int*)(a + 4) != *(int*)(b + 4)) goto ReturnFalse;
+ if (*(int*)(a + 6) != *(int*)(b + 6)) goto ReturnFalse;
+ if (*(int*)(a + 8) != *(int*)(b + 8)) goto ReturnFalse;
+ length -= 10; a += 10; b += 10;
+ }
+#endif
+
+ // This depends on the fact that the String objects are
+ // always zero terminated and that the terminating zero is not included
+ // in the length. For odd string sizes, the last compare will include
+ // the zero terminator.
+ while (length > 0)
+ {
+ if (*(int*)a != *(int*)b) goto ReturnFalse;
+ length -= 2; a += 2; b += 2;
+ }
+
+ return true;
+
+ ReturnFalse:
+ return false;
+ }
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ private unsafe static bool StartsWithOrdinalHelper(String str, String startsWith)
+ {
+ Contract.Requires(str != null);
+ Contract.Requires(startsWith != null);
+ Contract.Requires(str.Length >= startsWith.Length);
+
+ int length = startsWith.Length;
+
+ fixed (char* ap = &str.m_firstChar) fixed (char* bp = &startsWith.m_firstChar)
+ {
+ char* a = ap;
+ char* b = bp;
+
+#if BIT64
+ // Single int read aligns pointers for the following long reads
+ // No length check needed as this method is called when length >= 2
+ Contract.Assert(length >= 2);
+ if (*(int*)a != *(int*)b) goto ReturnFalse;
+ length -= 2; a += 2; b += 2;
+
+ while (length >= 12)
+ {
+ if (*(long*)a != *(long*)b) goto ReturnFalse;
+ if (*(long*)(a + 4) != *(long*)(b + 4)) goto ReturnFalse;
+ if (*(long*)(a + 8) != *(long*)(b + 8)) goto ReturnFalse;
+ length -= 12; a += 12; b += 12;
+ }
+#else
+ while (length >= 10)
+ {
+ if (*(int*)a != *(int*)b) goto ReturnFalse;
+ if (*(int*)(a+2) != *(int*)(b+2)) goto ReturnFalse;
+ if (*(int*)(a+4) != *(int*)(b+4)) goto ReturnFalse;
+ if (*(int*)(a+6) != *(int*)(b+6)) goto ReturnFalse;
+ if (*(int*)(a+8) != *(int*)(b+8)) goto ReturnFalse;
+ length -= 10; a += 10; b += 10;
+ }
+#endif
+
+ while (length >= 2)
+ {
+ if (*(int*)a != *(int*)b) goto ReturnFalse;
+ length -= 2; a += 2; b += 2;
+ }
+
+ // PERF: This depends on the fact that the String objects are always zero terminated
+ // and that the terminating zero is not included in the length. For even string sizes
+ // this compare can include the zero terminator. Bitwise OR avoids a branch.
+ return length == 0 | *a == *b;
+
+ ReturnFalse:
+ return false;
+ }
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ private unsafe static int CompareOrdinalHelper(String strA, String strB)
+ {
+ Contract.Requires(strA != null);
+ Contract.Requires(strB != null);
+
+ // NOTE: This may be subject to change if eliminating the check
+ // in the callers makes them small enough to be inlined by the JIT
+ Contract.Assert(strA.m_firstChar == strB.m_firstChar,
+ "For performance reasons, callers of this method should " +
+ "check/short-circuit beforehand if the first char is the same.");
+
+ int length = Math.Min(strA.Length, strB.Length);
+
+ fixed (char* ap = &strA.m_firstChar) fixed (char* bp = &strB.m_firstChar)
+ {
+ char* a = ap;
+ char* b = bp;
+
+ // Check if the second chars are different here
+ // The reason we check if m_firstChar is different is because
+ // it's the most common case and allows us to avoid a method call
+ // to here.
+ // The reason we check if the second char is different is because
+ // if the first two chars the same we can increment by 4 bytes,
+ // leaving us word-aligned on both 32-bit (12 bytes into the string)
+ // and 64-bit (16 bytes) platforms.
+
+ // For empty strings, the second char will be null due to padding.
+ // The start of the string (not including sync block pointer)
+ // is the method table pointer + string length, which takes up
+ // 8 bytes on 32-bit, 12 on x64. For empty strings the null
+ // terminator immediately follows, leaving us with an object
+ // 10/14 bytes in size. Since everything needs to be a multiple
+ // of 4/8, this will get padded and zeroed out.
+
+ // For one-char strings the second char will be the null terminator.
+
+ // NOTE: If in the future there is a way to read the second char
+ // without pinning the string (e.g. System.Runtime.CompilerServices.Unsafe
+ // is exposed to mscorlib, or a future version of C# allows inline IL),
+ // then do that and short-circuit before the fixed.
+
+ if (*(a + 1) != *(b + 1)) goto DiffOffset1;
+
+ // Since we know that the first two chars are the same,
+ // we can increment by 2 here and skip 4 bytes.
+ // This leaves us 8-byte aligned, which results
+ // on better perf for 64-bit platforms.
+ length -= 2; a += 2; b += 2;
+
+ // unroll the loop
+#if BIT64
+ while (length >= 12)
+ {
+ if (*(long*)a != *(long*)b) goto DiffOffset0;
+ if (*(long*)(a + 4) != *(long*)(b + 4)) goto DiffOffset4;
+ if (*(long*)(a + 8) != *(long*)(b + 8)) goto DiffOffset8;
+ length -= 12; a += 12; b += 12;
+ }
+#else // BIT64
+ while (length >= 10)
+ {
+ if (*(int*)a != *(int*)b) goto DiffOffset0;
+ if (*(int*)(a + 2) != *(int*)(b + 2)) goto DiffOffset2;
+ if (*(int*)(a + 4) != *(int*)(b + 4)) goto DiffOffset4;
+ if (*(int*)(a + 6) != *(int*)(b + 6)) goto DiffOffset6;
+ if (*(int*)(a + 8) != *(int*)(b + 8)) goto DiffOffset8;
+ length -= 10; a += 10; b += 10;
+ }
+#endif // BIT64
+
+ // Fallback loop:
+ // go back to slower code path and do comparison on 4 bytes at a time.
+ // This depends on the fact that the String objects are
+ // always zero terminated and that the terminating zero is not included
+ // in the length. For odd string sizes, the last compare will include
+ // the zero terminator.
+ while (length > 0)
+ {
+ if (*(int*)a != *(int*)b) goto DiffNextInt;
+ length -= 2;
+ a += 2;
+ b += 2;
+ }
+
+ // At this point, we have compared all the characters in at least one string.
+ // The longer string will be larger.
+ return strA.Length - strB.Length;
+
+#if BIT64
+ DiffOffset8: a += 4; b += 4;
+ DiffOffset4: a += 4; b += 4;
+#else // BIT64
+ // Use jumps instead of falling through, since
+ // otherwise going to DiffOffset8 will involve
+ // 8 add instructions before getting to DiffNextInt
+ DiffOffset8: a += 8; b += 8; goto DiffOffset0;
+ DiffOffset6: a += 6; b += 6; goto DiffOffset0;
+ DiffOffset4: a += 2; b += 2;
+ DiffOffset2: a += 2; b += 2;
+#endif // BIT64
+
+ DiffOffset0:
+ // If we reached here, we already see a difference in the unrolled loop above
+#if BIT64
+ if (*(int*)a == *(int*)b)
+ {
+ a += 2; b += 2;
+ }
+#endif // BIT64
+
+ DiffNextInt:
+ if (*a != *b) return *a - *b;
+
+ DiffOffset1:
+ Contract.Assert(*(a + 1) != *(b + 1), "This char must be different if we reach here!");
+ return *(a + 1) - *(b + 1);
+ }
+ }
+
+ // Provides a culture-correct string comparison. StrA is compared to StrB
+ // to determine whether it is lexicographically less, equal, or greater, and then returns
+ // either a negative integer, 0, or a positive integer; respectively.
+ //
+ [Pure]
+ public static int Compare(String strA, String strB)
+ {
+ return Compare(strA, strB, StringComparison.CurrentCulture);
+ }
+
+
+ // Provides a culture-correct string comparison. strA is compared to strB
+ // to determine whether it is lexicographically less, equal, or greater, and then a
+ // negative integer, 0, or a positive integer is returned; respectively.
+ // The case-sensitive option is set by ignoreCase
+ //
+ [Pure]
+ public static int Compare(String strA, String strB, bool ignoreCase)
+ {
+ var comparisonType = ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
+ return Compare(strA, strB, comparisonType);
+ }
+
+
+ // Provides a more flexible function for string comparision. See StringComparison
+ // for meaning of different comparisonType.
+ [Pure]
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public static int Compare(String strA, String strB, StringComparison comparisonType)
+ {
+ // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase]
+ if ((uint)(comparisonType - StringComparison.CurrentCulture) > (uint)(StringComparison.OrdinalIgnoreCase - StringComparison.CurrentCulture))
+ {
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
+ }
+ Contract.EndContractBlock();
+
+ if (object.ReferenceEquals(strA, strB))
+ {
+ return 0;
+ }
+
+ // They can't both be null at this point.
+ if (strA == null)
+ {
+ return -1;
+ }
+ if (strB == null)
+ {
+ return 1;
+ }
+
+ switch (comparisonType) {
+ case StringComparison.CurrentCulture:
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.None);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreCase);
+
+ case StringComparison.InvariantCulture:
+ return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, strB, CompareOptions.None);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreCase);
+
+ case StringComparison.Ordinal:
+ // Most common case: first character is different.
+ // Returns false for empty strings.
+ if (strA.m_firstChar != strB.m_firstChar)
+ {
+ return strA.m_firstChar - strB.m_firstChar;
+ }
+
+ return CompareOrdinalHelper(strA, strB);
+
+ case StringComparison.OrdinalIgnoreCase:
+ // If both strings are ASCII strings, we can take the fast path.
+ if (strA.IsAscii() && strB.IsAscii()) {
+ return (CompareOrdinalIgnoreCaseHelper(strA, strB));
+ }
+
+#if FEATURE_COREFX_GLOBALIZATION
+ return CompareInfo.CompareOrdinalIgnoreCase(strA, 0, strA.Length, strB, 0, strB.Length);
+#else
+ // Take the slow path.
+ return TextInfo.CompareOrdinalIgnoreCase(strA, strB);
+#endif
+
+ default:
+ throw new NotSupportedException(Environment.GetResourceString("NotSupported_StringComparison"));
+ }
+ }
+
+
+ // Provides a culture-correct string comparison. strA is compared to strB
+ // to determine whether it is lexicographically less, equal, or greater, and then a
+ // negative integer, 0, or a positive integer is returned; respectively.
+ //
+ [Pure]
+ public static int Compare(String strA, String strB, CultureInfo culture, CompareOptions options) {
+ if (culture == null)
+ {
+ throw new ArgumentNullException("culture");
+ }
+ Contract.EndContractBlock();
+
+ return culture.CompareInfo.Compare(strA, strB, options);
+ }
+
+
+
+ // Provides a culture-correct string comparison. strA is compared to strB
+ // to determine whether it is lexicographically less, equal, or greater, and then a
+ // negative integer, 0, or a positive integer is returned; respectively.
+ // The case-sensitive option is set by ignoreCase, and the culture is set
+ // by culture
+ //
+ [Pure]
+ public static int Compare(String strA, String strB, bool ignoreCase, CultureInfo culture)
+ {
+ var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
+ return Compare(strA, strB, culture, options);
+ }
+
+ // Determines whether two string regions match. The substring of strA beginning
+ // at indexA of length count is compared with the substring of strB
+ // beginning at indexB of the same length.
+ //
+ [Pure]
+ public static int Compare(String strA, int indexA, String strB, int indexB, int length)
+ {
+ // NOTE: It's important we call the boolean overload, and not the StringComparison
+ // one. The two have some subtly different behavior (see notes in the former).
+ return Compare(strA, indexA, strB, indexB, length, ignoreCase: false);
+ }
+
+ // Determines whether two string regions match. The substring of strA beginning
+ // at indexA of length count is compared with the substring of strB
+ // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean.
+ //
+ [Pure]
+ public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
+ {
+ // Ideally we would just forward to the string.Compare overload that takes
+ // a StringComparison parameter, and just pass in CurrentCulture/CurrentCultureIgnoreCase.
+ // That function will return early if an optimization can be applied, e.g. if
+ // (object)strA == strB && indexA == indexB then it will return 0 straightaway.
+ // There are a couple of subtle behavior differences that prevent us from doing so
+ // however:
+ // - string.Compare(null, -1, null, -1, -1, StringComparison.CurrentCulture) works
+ // since that method also returns early for nulls before validation. It shouldn't
+ // for this overload.
+ // - Since we originally forwarded to CompareInfo.Compare for all of the argument
+ // validation logic, the ArgumentOutOfRangeExceptions thrown will contain different
+ // parameter names.
+ // Therefore, we have to duplicate some of the logic here.
+
+ int lengthA = length;
+ int lengthB = length;
+
+ if (strA != null)
+ {
+ lengthA = Math.Min(lengthA, strA.Length - indexA);
+ }
+
+ if (strB != null)
+ {
+ lengthB = Math.Min(lengthB, strB.Length - indexB);
+ }
+
+ var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
+ }
+
+ // Determines whether two string regions match. The substring of strA beginning
+ // at indexA of length length is compared with the substring of strB
+ // beginning at indexB of the same length. Case sensitivity is determined by the ignoreCase boolean,
+ // and the culture is set by culture.
+ //
+ [Pure]
+ public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
+ {
+ var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
+ return Compare(strA, indexA, strB, indexB, length, culture, options);
+ }
+
+
+ // Determines whether two string regions match. The substring of strA beginning
+ // at indexA of length length is compared with the substring of strB
+ // beginning at indexB of the same length.
+ //
+ [Pure]
+ public static int Compare(String strA, int indexA, String strB, int indexB, int length, CultureInfo culture, CompareOptions options)
+ {
+ if (culture == null)
+ {
+ throw new ArgumentNullException("culture");
+ }
+ Contract.EndContractBlock();
+
+ int lengthA = length;
+ int lengthB = length;
+
+ if (strA != null)
+ {
+ lengthA = Math.Min(lengthA, strA.Length - indexA);
+ }
+
+ if (strB != null)
+ {
+ lengthB = Math.Min(lengthB, strB.Length - indexB);
+ }
+
+ return culture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
+ }
+
+ [Pure]
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public static int Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType) {
+ if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) {
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
+ }
+ Contract.EndContractBlock();
+
+ if (strA == null || strB == null)
+ {
+ if (object.ReferenceEquals(strA, strB))
+ {
+ // They're both null
+ return 0;
+ }
+
+ return strA == null ? -1 : 1;
+ }
+
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NegativeLength"));
+ }
+
+ if (indexA < 0 || indexB < 0)
+ {
+ string paramName = indexA < 0 ? "indexA" : "indexB";
+ throw new ArgumentOutOfRangeException(paramName, Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+
+ if (strA.Length - indexA < 0 || strB.Length - indexB < 0)
+ {
+ string paramName = strA.Length - indexA < 0 ? "indexA" : "indexB";
+ throw new ArgumentOutOfRangeException(paramName, Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+
+ if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
+ {
+ return 0;
+ }
+
+ int lengthA = Math.Min(length, strA.Length - indexA);
+ int lengthB = Math.Min(length, strB.Length - indexB);
+
+ switch (comparisonType) {
+ case StringComparison.CurrentCulture:
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
+
+ case StringComparison.InvariantCulture:
+ return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CultureInfo.InvariantCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
+
+ case StringComparison.Ordinal:
+ return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
+
+ case StringComparison.OrdinalIgnoreCase:
+#if FEATURE_COREFX_GLOBALIZATION
+ return (CompareInfo.CompareOrdinalIgnoreCase(strA, indexA, lengthA, strB, indexB, lengthB));
+#else
+ return (TextInfo.CompareOrdinalIgnoreCaseEx(strA, indexA, strB, indexB, lengthA, lengthB));
+#endif
+
+ default:
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"));
+ }
+
+ }
+
+ // Compares strA and strB using an ordinal (code-point) comparison.
+ //
+ [Pure]
+ public static int CompareOrdinal(String strA, String strB)
+ {
+ if (object.ReferenceEquals(strA, strB))
+ {
+ return 0;
+ }
+
+ // They can't both be null at this point.
+ if (strA == null)
+ {
+ return -1;
+ }
+ if (strB == null)
+ {
+ return 1;
+ }
+
+ // Most common case, first character is different.
+ // This will return false for empty strings.
+ if (strA.m_firstChar != strB.m_firstChar)
+ {
+ return strA.m_firstChar - strB.m_firstChar;
+ }
+
+ return CompareOrdinalHelper(strA, strB);
+ }
+
+
+ // Compares strA and strB using an ordinal (code-point) comparison.
+ //
+ [Pure]
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public static int CompareOrdinal(String strA, int indexA, String strB, int indexB, int length)
+ {
+ if (strA == null || strB == null)
+ {
+ if (object.ReferenceEquals(strA, strB))
+ {
+ // They're both null
+ return 0;
+ }
+
+ return strA == null ? -1 : 1;
+ }
+
+ // COMPAT: Checking for nulls should become before the arguments are validated,
+ // but other optimizations which allow us to return early should come after.
+
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount"));
+ }
+
+ if (indexA < 0 || indexB < 0)
+ {
+ string paramName = indexA < 0 ? "indexA" : "indexB";
+ throw new ArgumentOutOfRangeException(paramName, Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+
+ int lengthA = Math.Min(length, strA.Length - indexA);
+ int lengthB = Math.Min(length, strB.Length - indexB);
+
+ if (lengthA < 0 || lengthB < 0)
+ {
+ string paramName = lengthA < 0 ? "indexA" : "indexB";
+ throw new ArgumentOutOfRangeException(paramName, Environment.GetResourceString("ArgumentOutOfRange_Index"));
+ }
+
+ if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
+ {
+ return 0;
+ }
+
+ return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
+ }
+
+ // Compares this String to another String (cast as object), returning an integer that
+ // indicates the relationship. This method returns a value less than 0 if this is less than value, 0
+ // if this is equal to value, or a value greater than 0 if this is greater than value.
+ //
+ [Pure]
+ public int CompareTo(Object value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+
+ string other = value as string;
+
+ if (other == null)
+ {
+ throw new ArgumentException(Environment.GetResourceString("Arg_MustBeString"));
+ }
+
+ return CompareTo(other); // will call the string-based overload
+ }
+
+ // Determines the sorting relation of StrB to the current instance.
+ //
+ [Pure]
+ public int CompareTo(String strB)
+ {
+ return string.Compare(this, strB, StringComparison.CurrentCulture);
+ }
+
+ // Determines whether a specified string is a suffix of the the current instance.
+ //
+ // The case-sensitive and culture-sensitive option is set by options,
+ // and the default culture is used.
+ //
+ [Pure]
+ public Boolean EndsWith(String value) {
+ return EndsWith(value, StringComparison.CurrentCulture);
+ }
+
+ [Pure]
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [ComVisible(false)]
+ public Boolean EndsWith(String value, StringComparison comparisonType) {
+ if( (Object)value == null) {
+ throw new ArgumentNullException("value");
+ }
+
+ if( comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) {
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
+ }
+ Contract.EndContractBlock();
+
+ if( (Object)this == (Object)value) {
+ return true;
+ }
+
+ if( value.Length == 0) {
+ return true;
+ }
+
+ switch (comparisonType) {
+ case StringComparison.CurrentCulture:
+ return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase);
+
+ case StringComparison.InvariantCulture:
+ return CultureInfo.InvariantCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CultureInfo.InvariantCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase);
+
+ case StringComparison.Ordinal:
+ return this.Length < value.Length ? false : (CompareOrdinalHelper(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
+
+ case StringComparison.OrdinalIgnoreCase:
+#if FEATURE_COREFX_GLOBALIZATION
+ return this.Length < value.Length ? false : (CompareInfo.CompareOrdinalIgnoreCase(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
+#else
+ return this.Length < value.Length ? false : (TextInfo.CompareOrdinalIgnoreCaseEx(this, this.Length - value.Length, value, 0, value.Length, value.Length) == 0);
+#endif
+ default:
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
+ }
+ }
+
+ [Pure]
+ public Boolean EndsWith(String value, Boolean ignoreCase, CultureInfo culture) {
+ if (null==value) {
+ throw new ArgumentNullException("value");
+ }
+ Contract.EndContractBlock();
+
+ if((object)this == (object)value) {
+ return true;
+ }
+
+ CultureInfo referenceCulture;
+ if (culture == null)
+ referenceCulture = CultureInfo.CurrentCulture;
+ else
+ referenceCulture = culture;
+
+ return referenceCulture.CompareInfo.IsSuffix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
+ }
+
+ [Pure]
+ internal bool EndsWith(char value) {
+ int thisLen = this.Length;
+ if (thisLen != 0) {
+ if (this[thisLen - 1] == value)
+ return true;
+ }
+ return false;
+ }
+
+ // Determines whether two strings match.
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public override bool Equals(Object obj)
+ {
+ if (this == null) // this is necessary to guard against reverse-pinvokes and
+ throw new NullReferenceException(); // other callers who do not use the callvirt instruction
+
+ if (object.ReferenceEquals(this, obj))
+ return true;
+
+ string str = obj as string;
+ if (str == null)
+ return false;
+
+ if (this.Length != str.Length)
+ return false;
+
+ return EqualsHelper(this, str);
+ }
+
+ // Determines whether two strings match.
+ [Pure]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public bool Equals(String value)
+ {
+ if (this == null) // this is necessary to guard against reverse-pinvokes and
+ throw new NullReferenceException(); // other callers who do not use the callvirt instruction
+
+ if (object.ReferenceEquals(this, value))
+ return true;
+
+ // NOTE: No need to worry about casting to object here.
+ // If either side of an == comparison between strings
+ // is null, Roslyn generates a simple ceq instruction
+ // instead of calling string.op_Equality.
+ if (value == null)
+ return false;
+
+ if (this.Length != value.Length)
+ return false;
+
+ return EqualsHelper(this, value);
+ }
+
+ [Pure]
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public bool Equals(String value, StringComparison comparisonType) {
+ if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
+ Contract.EndContractBlock();
+
+ if ((Object)this == (Object)value) {
+ return true;
+ }
+
+ if ((Object)value == null) {
+ return false;
+ }
+
+ switch (comparisonType) {
+ case StringComparison.CurrentCulture:
+ return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0);
+
+ case StringComparison.InvariantCulture:
+ return (CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return (CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0);
+
+ case StringComparison.Ordinal:
+ if (this.Length != value.Length)
+ return false;
+ return EqualsHelper(this, value);
+
+ case StringComparison.OrdinalIgnoreCase:
+ if (this.Length != value.Length)
+ return false;
+
+ // If both strings are ASCII strings, we can take the fast path.
+ if (this.IsAscii() && value.IsAscii()) {
+ return (CompareOrdinalIgnoreCaseHelper(this, value) == 0);
+ }
+
+#if FEATURE_COREFX_GLOBALIZATION
+ return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, this.Length, value, 0, value.Length) == 0);
+#else
+ // Take the slow path.
+ return (TextInfo.CompareOrdinalIgnoreCase(this, value) == 0);
+#endif
+
+ default:
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
+ }
+ }
+
+
+ // Determines whether two Strings match.
+ [Pure]
+ public static bool Equals(String a, String b) {
+ if ((Object)a==(Object)b) {
+ return true;
+ }
+
+ if ((Object)a == null || (Object)b == null || a.Length != b.Length) {
+ return false;
+ }
+
+ return EqualsHelper(a, b);
+ }
+
+ [Pure]
+ [System.Security.SecuritySafeCritical] // auto-generated
+ public static bool Equals(String a, String b, StringComparison comparisonType) {
+ if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
+ Contract.EndContractBlock();
+
+ if ((Object)a==(Object)b) {
+ return true;
+ }
+
+ if ((Object)a==null || (Object)b==null) {
+ return false;
+ }
+
+ switch (comparisonType) {
+ case StringComparison.CurrentCulture:
+ return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0);
+
+ case StringComparison.InvariantCulture:
+ return (CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return (CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0);
+
+ case StringComparison.Ordinal:
+ if (a.Length != b.Length)
+ return false;
+
+ return EqualsHelper(a, b);
+
+ case StringComparison.OrdinalIgnoreCase:
+ if (a.Length != b.Length)
+ return false;
+ else {
+ // If both strings are ASCII strings, we can take the fast path.
+ if (a.IsAscii() && b.IsAscii()) {
+ return (CompareOrdinalIgnoreCaseHelper(a, b) == 0);
+ }
+ // Take the slow path.
+
+#if FEATURE_COREFX_GLOBALIZATION
+ return (CompareInfo.CompareOrdinalIgnoreCase(a, 0, a.Length, b, 0, b.Length) == 0);
+#else
+ return (TextInfo.CompareOrdinalIgnoreCase(a, b) == 0);
+#endif
+ }
+
+ default:
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
+ }
+ }
+
+ public static bool operator == (String a, String b) {
+ return String.Equals(a, b);
+ }
+
+ public static bool operator != (String a, String b) {
+ return !String.Equals(a, b);
+ }
+
+#if FEATURE_RANDOMIZED_STRING_HASHING
+ // Do not remove!
+ // This method is called by reflection in System.Xml
+ [System.Security.SecurityCritical]
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern int InternalMarvin32HashString(string s, int strLen, long additionalEntropy);
+
+ [System.Security.SecuritySafeCritical]
+ internal static bool UseRandomizedHashing() {
+ return InternalUseRandomizedHashing();
+ }
+
+ [System.Security.SecurityCritical]
+ [System.Security.SuppressUnmanagedCodeSecurity]
+ [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
+ private static extern bool InternalUseRandomizedHashing();
+#endif
+
+ // Gets a hash code for this string. If strings A and B are such that A.Equals(B), then
+ // they will return the same hash code.
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public override int GetHashCode()
+ {
+#if FEATURE_RANDOMIZED_STRING_HASHING
+ if (HashHelpers.s_UseRandomizedStringHashing)
+ {
+ return InternalMarvin32HashString(this, this.Length, 0);
+ }
+#endif // FEATURE_RANDOMIZED_STRING_HASHING
+
+ return GetLegacyNonRandomizedHashCode();
+ }
+
+ // Use this if and only if you need the hashcode to not change across app domains (e.g. you have an app domain agile
+ // hash table).
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ internal int GetLegacyNonRandomizedHashCode() {
+ unsafe {
+ fixed (char* src = &m_firstChar) {
+ Contract.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'");
+ Contract.Assert( ((int)src)%4 == 0, "Managed string should start at 4 bytes boundary");
+#if BIT64
+ int hash1 = 5381;
+#else // !BIT64 (32)
+ int hash1 = (5381<<16) + 5381;
+#endif
+ int hash2 = hash1;
+
+#if BIT64
+ int c;
+ char *s = src;
+ while ((c = s[0]) != 0) {
+ hash1 = ((hash1 << 5) + hash1) ^ c;
+ c = s[1];
+ if (c == 0)
+ break;
+ hash2 = ((hash2 << 5) + hash2) ^ c;
+ s += 2;
+ }
+#else // !BIT64 (32)
+ // 32 bit machines.
+ int* pint = (int *)src;
+ int len = this.Length;
+ while (len > 2)
+ {
+ hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
+ hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
+ pint += 2;
+ len -= 4;
+ }
+
+ if (len > 0)
+ {
+ hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
+ }
+#endif
+#if DEBUG
+ // We want to ensure we can change our hash function daily.
+ // This is perfectly fine as long as you don't persist the
+ // value from GetHashCode to disk or count on String A
+ // hashing before string B. Those are bugs in your code.
+ hash1 ^= ThisAssembly.DailyBuildNumber;
+#endif
+ return hash1 + (hash2 * 1566083941);
+ }
+ }
+ }
+
+ // Determines whether a specified string is a prefix of the current instance
+ //
+ [Pure]
+ public Boolean StartsWith(String value) {
+ if ((Object)value == null) {
+ throw new ArgumentNullException("value");
+ }
+ Contract.EndContractBlock();
+ return StartsWith(value, StringComparison.CurrentCulture);
+ }
+
+ [Pure]
+ [System.Security.SecuritySafeCritical] // auto-generated
+ [ComVisible(false)]
+ public Boolean StartsWith(String value, StringComparison comparisonType) {
+ if( (Object)value == null) {
+ throw new ArgumentNullException("value");
+ }
+
+ if( comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) {
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
+ }
+ Contract.EndContractBlock();
+
+ if( (Object)this == (Object)value) {
+ return true;
+ }
+
+ if( value.Length == 0) {
+ return true;
+ }
+
+ switch (comparisonType) {
+ case StringComparison.CurrentCulture:
+ return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
+
+ case StringComparison.InvariantCulture:
+ return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
+
+ case StringComparison.Ordinal:
+ if( this.Length < value.Length || m_firstChar != value.m_firstChar) {
+ return false;
+ }
+ return (value.Length == 1) ?
+ true : // First char is the same and thats all there is to compare
+ StartsWithOrdinalHelper(this, value);
+
+ case StringComparison.OrdinalIgnoreCase:
+ if( this.Length < value.Length) {
+ return false;
+ }
+
+#if FEATURE_COREFX_GLOBALIZATION
+ return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, value.Length, value, 0, value.Length) == 0);
+#else
+ return (TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0);
+#endif
+
+ default:
+ throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
+ }
+ }
+
+ [Pure]
+ public Boolean StartsWith(String value, Boolean ignoreCase, CultureInfo culture) {
+ if (null==value) {
+ throw new ArgumentNullException("value");
+ }
+ Contract.EndContractBlock();
+
+ if((object)this == (object)value) {
+ return true;
+ }
+
+ CultureInfo referenceCulture;
+ if (culture == null)
+ referenceCulture = CultureInfo.CurrentCulture;
+ else
+ referenceCulture = culture;
+
+ return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
+ }
+ }
+} \ No newline at end of file