* Add ReadOnlySpan string-like Equals/CompareTo/IndexOf/Contains API with globalization support
* Address PR feedback.
* Fix unix implementation
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOf")]
internal unsafe static extern int IndexOf(SafeSortHandle sortHandle, string target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options, int* matchLengthPtr);
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOf")]
+ internal unsafe static extern int IndexOf(SafeSortHandle sortHandle, char* target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options, int* matchLengthPtr);
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_LastIndexOf")]
internal unsafe static extern int LastIndexOf(SafeSortHandle sortHandle, string target, int cwTargetLength, char* pSource, int cwSourceLength, CompareOptions options);
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")]
internal unsafe static extern int IndexOfOrdinalIgnoreCase(string target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast);
+ [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IndexOfOrdinalIgnoreCase")]
+ internal unsafe static extern int IndexOfOrdinalIgnoreCase(char* target, int cwTargetLength, char* pSource, int cwSourceLength, bool findLast);
[DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_StartsWith")]
[return: MarshalAs(UnmanagedType.Bool)]
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
+using System.Runtime.InteropServices;
namespace System.Globalization
{
}
}
+ internal static unsafe int InvariantIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pValue = &MemoryMarshal.GetReference(value))
+ {
+ return InvariantFindString(pSource, source.Length, pValue, value.Length, ignoreCase, start: true);
+ }
+ }
+
internal static unsafe int InvariantLastIndexOf(string source, string value, int startIndex, int count, bool ignoreCase)
{
Debug.Assert(source != null);
return (Compare(string1, string2, CompareOptions.None));
}
- public unsafe virtual int Compare(string string1, string string2, CompareOptions options)
+ public virtual int Compare(string string1, string string2, CompareOptions options)
{
if (options == CompareOptions.OrdinalIgnoreCase)
{
// TODO https://github.com/dotnet/coreclr/issues/13827:
// This method shouldn't be necessary, as we should be able to just use the overload
// that takes two spans. But due to this issue, that's adding significant overhead.
- internal unsafe int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions options)
+ internal int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions options)
{
if (options == CompareOptions.OrdinalIgnoreCase)
{
}
// TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly?
- internal unsafe virtual int Compare(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
+ internal virtual int Compare(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
{
if (options == CompareOptions.OrdinalIgnoreCase)
{
////////////////////////////////////////////////////////////////////////
- public unsafe virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2)
+ public virtual int Compare(string string1, int offset1, int length1, string string2, int offset2, int length2)
{
return Compare(string1, offset1, length1, string2, offset2, length2, 0);
}
// it assumes the strings are Ascii string till we hit non Ascii character in strA or strB and then we continue the comparison by
// calling the OS.
//
- internal static unsafe int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB)
+ internal static int CompareOrdinalIgnoreCase(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB)
{
Debug.Assert(indexA + lengthA <= strA.Length);
Debug.Assert(indexB + lengthB <= strB.Length);
return IndexOfCore(source, value, startIndex, count, options, null);
}
+ internal virtual int IndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(!_invariantMode);
+ return IndexOfOrdinalCore(source, value, ignoreCase);
+ }
+
+ internal unsafe virtual int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
+ {
+ Debug.Assert(!_invariantMode);
+ return IndexOfCore(source, value, options, null);
+ }
+
// The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated
// and the caller is passing a valid matchLengthPtr pointer.
internal unsafe int IndexOf(string source, string value, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
/// </summary>
public static class Span
{
+ public static bool Contains(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ return (IndexOf(span, value, comparisonType) >= 0);
+ }
+
+ public static bool Equals(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ StringSpanHelpers.CheckStringComparison(comparisonType);
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return (CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.None) == 0);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return (CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.IgnoreCase) == 0);
+
+ case StringComparison.InvariantCulture:
+ return (CompareInfo.Invariant.Compare(span, value, CompareOptions.None) == 0);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return (CompareInfo.Invariant.Compare(span, value, CompareOptions.IgnoreCase) == 0);
+
+ case StringComparison.Ordinal:
+ if (span.Length != value.Length)
+ return false;
+ if (value.Length == 0) // span.Length == value.Length == 0
+ return true;
+ return OrdinalHelper(span, value, value.Length);
+
+ case StringComparison.OrdinalIgnoreCase:
+ if (span.Length != value.Length)
+ return false;
+ if (value.Length == 0) // span.Length == value.Length == 0
+ return true;
+ return (CompareInfo.CompareOrdinalIgnoreCase(span, value) == 0);
+ }
+
+ Debug.Fail("StringComparison outside range");
+ return false;
+ }
+
+ public static int CompareTo(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ StringSpanHelpers.CheckStringComparison(comparisonType);
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.None);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return CultureInfo.CurrentCulture.CompareInfo.Compare(span, value, CompareOptions.IgnoreCase);
+
+ case StringComparison.InvariantCulture:
+ return CompareInfo.Invariant.Compare(span, value, CompareOptions.None);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return CompareInfo.Invariant.Compare(span, value, CompareOptions.IgnoreCase);
+
+ case StringComparison.Ordinal:
+ if (span.Length == 0 || value.Length == 0)
+ return span.Length - value.Length;
+ return string.CompareOrdinal(span, value);
+
+ case StringComparison.OrdinalIgnoreCase:
+ return CompareInfo.CompareOrdinalIgnoreCase(span, value);
+ }
+
+ Debug.Fail("StringComparison outside range");
+ return 0;
+ }
+
+ public static int IndexOf(this ReadOnlySpan<char> span, ReadOnlySpan<char> value, StringComparison comparisonType)
+ {
+ StringSpanHelpers.CheckStringComparison(comparisonType);
+
+ if (value.Length == 0)
+ {
+ return 0;
+ }
+
+ if (span.Length == 0)
+ {
+ return -1;
+ }
+
+ switch (comparisonType)
+ {
+ case StringComparison.CurrentCulture:
+ return IndexOfCultureHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
+
+ case StringComparison.CurrentCultureIgnoreCase:
+ return IndexOfCultureIgnoreCaseHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
+
+ case StringComparison.InvariantCulture:
+ return IndexOfCultureHelper(span, value, CompareInfo.Invariant);
+
+ case StringComparison.InvariantCultureIgnoreCase:
+ return IndexOfCultureIgnoreCaseHelper(span, value, CompareInfo.Invariant);
+
+ case StringComparison.Ordinal:
+ return IndexOfOrdinalHelper(span, value, ignoreCase: false);
+
+ case StringComparison.OrdinalIgnoreCase:
+ return IndexOfOrdinalHelper(span, value, ignoreCase: true);
+ }
+
+ Debug.Fail("StringComparison outside range");
+ return -1;
+ }
+
+ internal static int IndexOfCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
+ {
+ Debug.Assert(span.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ if (GlobalizationMode.Invariant)
+ {
+ return CompareInfo.InvariantIndexOf(span, value, ignoreCase: false);
+ }
+
+ return compareInfo.IndexOf(span, value, CompareOptions.None);
+ }
+
+ internal static int IndexOfCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
+ {
+ Debug.Assert(span.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ if (GlobalizationMode.Invariant)
+ {
+ return CompareInfo.InvariantIndexOf(span, value, ignoreCase: true);
+ }
+
+ return compareInfo.IndexOf(span, value, CompareOptions.IgnoreCase);
+ }
+
+ internal static int IndexOfOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(span.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ if (GlobalizationMode.Invariant)
+ {
+ return CompareInfo.InvariantIndexOf(span, value, ignoreCase);
+ }
+
+ return CompareInfo.Invariant.IndexOfOrdinal(span, value, ignoreCase);
+ }
+
/// <summary>
/// Copies the characters from the source span into the destination, converting each character to lowercase.
/// </summary>
switch (comparisonType)
{
case StringComparison.CurrentCulture:
- return StartsWithCultureHelper(span, value);
+ return StartsWithCultureHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
case StringComparison.CurrentCultureIgnoreCase:
- return StartsWithCultureIgnoreCaseHelper(span, value);
+ return StartsWithCultureIgnoreCaseHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
case StringComparison.InvariantCulture:
- return StartsWithCultureHelper(span, value, invariantCulture: true);
+ return StartsWithCultureHelper(span, value, CompareInfo.Invariant);
case StringComparison.InvariantCultureIgnoreCase:
- return StartsWithCultureIgnoreCaseHelper(span, value, invariantCulture: true);
+ return StartsWithCultureIgnoreCaseHelper(span, value, CompareInfo.Invariant);
case StringComparison.Ordinal:
return StartsWithOrdinalHelper(span, value);
}
}
- internal static bool StartsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
+ internal static bool StartsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
{
Debug.Assert(value.Length != 0);
{
return false;
}
- return invariantCulture ?
- CompareInfo.Invariant.IsPrefix(span, value, CompareOptions.None) :
- CultureInfo.CurrentCulture.CompareInfo.IsPrefix(span, value, CompareOptions.None);
+ return compareInfo.IsPrefix(span, value, CompareOptions.None);
}
- internal static bool StartsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
+ internal static bool StartsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
{
Debug.Assert(value.Length != 0);
{
return false;
}
- return invariantCulture ?
- CompareInfo.Invariant.IsPrefix(span, value, CompareOptions.IgnoreCase) :
- CultureInfo.CurrentCulture.CompareInfo.IsPrefix(span, value, CompareOptions.IgnoreCase);
+ return compareInfo.IsPrefix(span, value, CompareOptions.IgnoreCase);
}
internal static bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
return CompareInfo.CompareOrdinalIgnoreCase(span.Slice(0, value.Length), value) == 0;
}
- internal static unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
+ internal static bool StartsWithOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
{
Debug.Assert(value.Length != 0);
switch (comparisonType)
{
case StringComparison.CurrentCulture:
- return EndsWithCultureHelper(span, value);
+ return EndsWithCultureHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
case StringComparison.CurrentCultureIgnoreCase:
- return EndsWithCultureIgnoreCaseHelper(span, value);
+ return EndsWithCultureIgnoreCaseHelper(span, value, CultureInfo.CurrentCulture.CompareInfo);
case StringComparison.InvariantCulture:
- return EndsWithCultureHelper(span, value, invariantCulture: true);
+ return EndsWithCultureHelper(span, value, CompareInfo.Invariant);
case StringComparison.InvariantCultureIgnoreCase:
- return EndsWithCultureIgnoreCaseHelper(span, value, invariantCulture: true);
+ return EndsWithCultureIgnoreCaseHelper(span, value, CompareInfo.Invariant);
case StringComparison.Ordinal:
return EndsWithOrdinalHelper(span, value);
}
}
- internal static bool EndsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
+ internal static bool EndsWithCultureHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
{
Debug.Assert(value.Length != 0);
{
return false;
}
- return invariantCulture ?
- CompareInfo.Invariant.IsSuffix(span, value, CompareOptions.None) :
- CultureInfo.CurrentCulture.CompareInfo.IsSuffix(span, value, CompareOptions.None);
+ return compareInfo.IsSuffix(span, value, CompareOptions.None);
}
- internal static bool EndsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, bool invariantCulture = false)
+ internal static bool EndsWithCultureIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value, CompareInfo compareInfo)
{
Debug.Assert(value.Length != 0);
{
return false;
}
- return invariantCulture ?
- CompareInfo.Invariant.IsSuffix(span, value, CompareOptions.IgnoreCase) :
- CultureInfo.CurrentCulture.CompareInfo.IsSuffix(span, value, CompareOptions.IgnoreCase);
+ return compareInfo.IsSuffix(span, value, CompareOptions.IgnoreCase);
}
internal static bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
return (CompareInfo.CompareOrdinalIgnoreCase(span.Slice(span.Length - value.Length), value) == 0);
}
- internal static unsafe bool EndsWithOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
+ internal static bool EndsWithOrdinalHelper(ReadOnlySpan<char> span, ReadOnlySpan<char> value)
{
Debug.Assert(value.Length != 0);
return -1;
}
+ internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ if (source.Length < value.Length)
+ {
+ return -1;
+ }
+
+ if (ignoreCase)
+ {
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pValue = &MemoryMarshal.GetReference(value))
+ {
+ int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(pValue, value.Length, pSource, source.Length, findLast: false);
+ return index;
+ }
+ }
+
+ int endIndex = source.Length - value.Length;
+ for (int i = 0; i <= endIndex; i++)
+ {
+ int valueIndex, sourceIndex;
+
+ for (valueIndex = 0, sourceIndex = i;
+ valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
+ valueIndex++, sourceIndex++)
+ ;
+
+ if (valueIndex == value.Length)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
internal static unsafe int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
{
Debug.Assert(!GlobalizationMode.Invariant);
}
}
+ // For now, this method is only called from Span APIs with either options == CompareOptions.None or CompareOptions.IgnoreCase
+ internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(target.Length != 0);
+
+ if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
+ {
+ if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
+ {
+ return IndexOfOrdinalIgnoreCaseHelper(source, target, options, matchLengthPtr);
+ }
+ else
+ {
+ return IndexOfOrdinalHelper(source, target, options, matchLengthPtr);
+ }
+ }
+ else
+ {
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pTarget = &MemoryMarshal.GetReference(target))
+ {
+ return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options, matchLengthPtr);
+ }
+ }
+ }
+
+ private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!target.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(target))
+ {
+ char* a = ap;
+ char* b = bp;
+ int endIndex = source.Length - target.Length;
+
+ if (endIndex < 0)
+ goto InteropCall;
+
+ for (int j = 0; j < target.Length; j++)
+ {
+ char targetChar = *(b + j);
+ if (targetChar >= 0x80 || s_highCharTable[targetChar])
+ goto InteropCall;
+ }
+
+ int i = 0;
+ for (; i <= endIndex; i++)
+ {
+ int targetIndex = 0;
+ int sourceIndex = i;
+
+ for (; targetIndex < target.Length; targetIndex++)
+ {
+ char valueChar = *(a + sourceIndex);
+ char targetChar = *(b + targetIndex);
+
+ if (valueChar == targetChar && valueChar < 0x80 && !s_highCharTable[valueChar])
+ {
+ sourceIndex++;
+ continue;
+ }
+
+ // uppercase both chars - notice that we need just one compare per char
+ if ((uint)(valueChar - 'a') <= ('z' - 'a'))
+ valueChar = (char)(valueChar - 0x20);
+ if ((uint)(targetChar - 'a') <= ('z' - 'a'))
+ targetChar = (char)(targetChar - 0x20);
+
+ if (valueChar >= 0x80 || s_highCharTable[valueChar])
+ goto InteropCall;
+ else if (valueChar != targetChar)
+ break;
+ sourceIndex++;
+ }
+
+ if (targetIndex == target.Length)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = target.Length;
+ return i;
+ }
+ }
+ if (i > endIndex)
+ return -1;
+ InteropCall:
+ return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
+ }
+ }
+
+ private unsafe int IndexOfOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(!source.IsEmpty);
+ Debug.Assert(!target.IsEmpty);
+ Debug.Assert(_isAsciiEqualityOrdinal);
+
+ fixed (char* ap = &MemoryMarshal.GetReference(source))
+ fixed (char* bp = &MemoryMarshal.GetReference(target))
+ {
+ char* a = ap;
+ char* b = bp;
+ int endIndex = source.Length - target.Length;
+
+ if (endIndex < 0)
+ goto InteropCall;
+
+ for (int j = 0; j < target.Length; j++)
+ {
+ char targetChar = *(b + j);
+ if (targetChar >= 0x80 || s_highCharTable[targetChar])
+ goto InteropCall;
+ }
+
+ int i = 0;
+ for (; i <= endIndex; i++)
+ {
+ int targetIndex = 0;
+ int sourceIndex = i;
+
+ for (; targetIndex < target.Length; targetIndex++)
+ {
+ char valueChar = *(a + sourceIndex);
+ char targetChar = *(b + targetIndex);
+ if (valueChar >= 0x80 || s_highCharTable[valueChar])
+ goto InteropCall;
+ else if (valueChar != targetChar)
+ break;
+ sourceIndex++;
+ }
+
+ if (targetIndex == target.Length)
+ {
+ if (matchLengthPtr != null)
+ *matchLengthPtr = target.Length;
+ return i;
+ }
+ }
+ if (i > endIndex)
+ return -1;
+ InteropCall:
+ return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
+ }
+ }
+
private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options)
{
Debug.Assert(!_invariantMode);
}
}
+ private static unsafe int FindStringOrdinal(
+ uint dwFindStringOrdinalFlags,
+ ReadOnlySpan<char> source,
+ ReadOnlySpan<char> value,
+ bool bIgnoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ fixed (char* pSource = &MemoryMarshal.GetReference(source))
+ fixed (char* pValue = &MemoryMarshal.GetReference(value))
+ {
+ int ret = Interop.Kernel32.FindStringOrdinal(
+ dwFindStringOrdinalFlags,
+ pSource,
+ source.Length,
+ pValue,
+ value.Length,
+ bIgnoreCase ? 1 : 0);
+ return ret;
+ }
+ }
+
internal static int IndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
{
Debug.Assert(!GlobalizationMode.Invariant);
return FindStringOrdinal(FIND_FROMSTART, source, startIndex, count, value, value.Length, ignoreCase);
}
+ internal static int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
+ {
+ Debug.Assert(!GlobalizationMode.Invariant);
+
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(value.Length != 0);
+
+ return FindStringOrdinal(FIND_FROMSTART, source, value, ignoreCase);
+ }
+
internal static int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
{
Debug.Assert(!GlobalizationMode.Invariant);
return -1;
}
+ internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
+ {
+ Debug.Assert(!_invariantMode);
+
+ Debug.Assert(source.Length != 0);
+ Debug.Assert(target.Length != 0);
+ Debug.Assert((options == CompareOptions.None || options == CompareOptions.IgnoreCase));
+
+ int retValue = FindString(FIND_FROMSTART | (uint)GetNativeCompareFlags(options), source, target, matchLengthPtr);
+ return retValue;
+ }
+
private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options)
{
Debug.Assert(!_invariantMode);
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
+using Internal.Runtime.CompilerServices;
+
+#if BIT64
+using nuint = System.UInt64;
+#else
+using nuint = System.UInt32;
+#endif
+
namespace System
{
public partial class String
// TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly?
internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
{
- // TODO: This needs to be optimized / unrolled. It can't just use CompareOrdinalHelper(str, str)
- // (changed to accept spans) because its implementation is based on a string layout,
- // in a way that doesn't work when there isn't guaranteed to be a null terminator.
+ // TODO: Add a vectorized code path, similar to SequenceEqual
+ // https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/SpanHelpers.byte.cs#L900
int minLength = Math.Min(strA.Length, strB.Length);
- for (int i = 0; i < minLength; i++)
+ ref char first = ref MemoryMarshal.GetReference(strA);
+ ref char second = ref MemoryMarshal.GetReference(strB);
+
+ int i = 0;
+ if (minLength >= sizeof(nuint) / sizeof(char))
{
- if (strA[i] != strB[i])
+ while (i < minLength - sizeof(nuint) / sizeof(char))
{
- return strA[i] - strB[i];
+ if (Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
+ Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
+ {
+ break;
+ }
+ i += sizeof(nuint) / sizeof(char);
}
}
-
+ while (i < minLength)
+ {
+ char a = Unsafe.Add(ref first, i);
+ char b = Unsafe.Add(ref second, i);
+ if (a != b)
+ {
+ return a - b;
+ }
+ i++;
+ }
return strA.Length - strB.Length;
}