// 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;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Toolkit.HighPerformance.Enumerables;
using Microsoft.Toolkit.HighPerformance.Helpers.Internals;
namespace Microsoft.Toolkit.HighPerformance.Extensions
{
///
/// Helpers for working with the type.
///
public static class StringExtensions
{
///
/// Returns a reference to the first element within a given , with no bounds checks.
///
/// The input instance.
/// A reference to the first element within , or the location it would have used, if is empty.
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref char DangerousGetReference(this string text)
{
#if NETCOREAPP3_1
return ref Unsafe.AsRef(text.GetPinnableReference());
#elif NETCOREAPP2_1
var stringData = Unsafe.As(text);
return ref stringData.Data;
#else
return ref MemoryMarshal.GetReference(text.AsSpan());
#endif
}
///
/// Returns a reference to an element at a specified index within a given , with no bounds checks.
///
/// The input instance.
/// The index of the element to retrieve within .
/// A reference to the element within at the index specified by .
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref char DangerousGetReferenceAt(this string text, int i)
{
#if NETCOREAPP3_1
ref char r0 = ref Unsafe.AsRef(text.GetPinnableReference());
#elif NETCOREAPP2_1
ref char r0 = ref Unsafe.As(text).Data;
#else
ref char r0 = ref MemoryMarshal.GetReference(text.AsSpan());
#endif
ref char ri = ref Unsafe.Add(ref r0, i);
return ref ri;
}
#if NETCOREAPP2_1
// Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285.
// CLR strings are laid out in memory as follows:
// [ sync block || pMethodTable || length || string data .. ]
// ^ ^
// | \-- ref Unsafe.As(text).Data
// \-- string
// The reference to RawStringData.Data points to the first character in the
// string, skipping over the sync block, method table and string length.
[StructLayout(LayoutKind.Explicit)]
private sealed class RawStringData
{
#pragma warning disable CS0649 // Unassigned fields
#pragma warning disable SA1401 // Fields should be private
[FieldOffset(4)]
public char Data;
#pragma warning restore CS0649
#pragma warning restore SA1401
}
#endif
///
/// Counts the number of occurrences of a given character into a target instance.
///
/// The input instance to read.
/// The character to look for.
/// The number of occurrences of in .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count(this string text, char c)
{
ref char r0 = ref text.DangerousGetReference();
IntPtr length = (IntPtr)text.Length;
return SpanHelper.Count(ref r0, length, c);
}
///
/// Enumerates the items in the input instance, as pairs of value/index values.
/// This extension should be used directly within a loop:
///
/// string text = "Hello, world!";
///
/// foreach (var item in text.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// string value = item.Value;
/// }
///
/// The compiler will take care of properly setting up the loop with the type returned from this method.
///
/// The source to enumerate.
/// A wrapper type that will handle the value/index enumeration for .
/// The returned value shouldn't be used directly: use this extension in a loop.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanEnumerable Enumerate(this string text)
{
return new ReadOnlySpanEnumerable(text.AsSpan());
}
///
/// Tokenizes the values in the input instance using a specified separator.
/// This extension should be used directly within a loop:
///
/// string text = "Hello, world!";
///
/// foreach (var token in text.Tokenize(','))
/// {
/// // Access the tokens here...
/// }
///
/// The compiler will take care of properly setting up the loop with the type returned from this method.
///
/// The source to tokenize.
/// The separator character to use.
/// A wrapper type that will handle the tokenization for .
/// The returned value shouldn't be used directly: use this extension in a loop.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanTokenizer Tokenize(this string text, char separator)
{
return new ReadOnlySpanTokenizer(text.AsSpan(), separator);
}
///
/// Gets a content hash from the input instance using the Djb2 algorithm.
/// For more info, see the documentation for .
///
/// The source to enumerate.
/// The Djb2 value for the input instance.
/// The Djb2 hash is fully deterministic and with no random components.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetDjb2HashCode(this string text)
{
ref char r0 = ref text.DangerousGetReference();
IntPtr length = (IntPtr)text.Length;
return SpanHelper.GetDjb2HashCode(ref r0, length);
}
}
}