diff options
Diffstat (limited to 'src/mscorlib/src/System/String.Comparison.cs')
-rw-r--r-- | src/mscorlib/src/System/String.Comparison.cs | 1160 |
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 |