Skip to content
This repository has been archived by the owner on Aug 8, 2024. It is now read-only.

Commit

Permalink
Index and Range updates (dotnet/coreclr#22331)
Browse files Browse the repository at this point in the history
* Index and Range updates

* Address @mikedn  feedback

* Address Feedback

* more feedback

* Use Deconstruct in Range.GetOffsetAndLength

* Rename GetArrayRange to GetSubArray

* Temporary disable the old Corefx Range tests

* Return back the TimeSpan test disabling

* Fix Range jit test

* Exclude the jit test

* revert the changes in the jit Range test

* Address Suggested Feedback

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
(cherry picked from commit b0b1617)
  • Loading branch information
tarekgh authored and Martin Baulig committed Feb 7, 2019
1 parent feaa66c commit 029568f
Show file tree
Hide file tree
Showing 10 changed files with 517 additions and 66 deletions.
122 changes: 112 additions & 10 deletions src/Common/src/CoreLib/System/Index.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,138 @@
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace System
{
/// <summary>Represent a type can be used to index a collection either from the start or the end.</summary>
/// <remarks>
/// Index is used by the C# compiler to support the new index syntax
/// <code>
/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
/// int lastElement = someArray[^1]; // lastElement = 5
/// </code>
/// </remarks>
public readonly struct Index : IEquatable<Index>
{
private readonly int _value;

public Index(int value, bool fromEnd)
/// <summary>Construct an Index using a value and indicating if the index is from the start or from the end.</summary>
/// <param name="value">The index value. it has to be zero or positive number.</param>
/// <param name="fromEnd">Indicating if the index is from the start or from the end.</param>
/// <remarks>
/// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Index(int value, bool fromEnd = false)
{
if (value < 0)
{
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
}

_value = fromEnd ? ~value : value;
if (fromEnd)
_value = ~value;
else
_value = value;
}

public int Value => _value < 0 ? ~_value : _value;
public bool FromEnd => _value < 0;
// The following private constructors mainly created for perf reason to avoid the checks
private Index(int value)
{
_value = value;
}

/// <summary>Create an Index pointing at first element.</summary>
public static Index Start => new Index(0);

/// <summary>Create an Index pointing at beyond last element.</summary>
public static Index End => new Index(~0);

/// <summary>Create an Index from the start at the position indicated by the value.</summary>
/// <param name="value">The index value from the start.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Index FromStart(int value)
{
if (value < 0)
{
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
}

return new Index(value);
}

/// <summary>Create an Index from the end at the position indicated by the value.</summary>
/// <param name="value">The index value from the end.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Index FromEnd(int value)
{
if (value < 0)
{
ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException();
}

return new Index(~value);
}

/// <summary>Returns the index value.</summary>
public int Value
{
get
{
if (_value < 0)
return ~_value;
else
return _value;
}
}

/// <summary>Indicates whether the index is from the start or the end.</summary>
public bool IsFromEnd => _value < 0;

/// <summary>Calculate the offset from the start using the giving collection length.</summary>
/// <param name="length">The length of the collection that the Index will be used with. length has to be a positive value</param>
/// <remarks>
/// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
/// we don't validate either the returned offset is greater than the input length.
/// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
/// then used to index a collection will get out of range exception which will be same affect as the validation.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetOffset(int length)
{
int offset;

if (IsFromEnd)
offset = length - (~_value);
else
offset = _value;

return offset;
}

/// <summary>Indicates whether the current Index object is equal to another object of the same type.</summary>
/// <param name="value">An object to compare with this object</param>
public override bool Equals(object value) => value is Index && _value == ((Index)value)._value;

/// <summary>Indicates whether the current Index object is equal to another Index object.</summary>
/// <param name="other">An object to compare with this object</param>
public bool Equals (Index other) => _value == other._value;

public override int GetHashCode()
/// <summary>Returns the hash code for this instance.</summary>
public override int GetHashCode() => _value;

/// <summary>Converts integer number to an Index.</summary>
public static implicit operator Index(int value) => FromStart(value);

/// <summary>Converts the value of the current Index object to its equivalent string representation.</summary>
public override string ToString()
{
return _value;
}
if (IsFromEnd)
return ToStringFromEnd();

public override string ToString() => FromEnd ? ToStringFromEnd() : ((uint)Value).ToString();
return ((uint)Value).ToString();
}

private string ToStringFromEnd()
{
Expand All @@ -41,7 +145,5 @@ private string ToStringFromEnd()
return new string(span.Slice(0, charsWritten + 1));
}

public static implicit operator Index(int value)
=> new Index(value, fromEnd: false);
}
}
29 changes: 29 additions & 0 deletions src/Common/src/CoreLib/System/Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,35 @@ public Memory<T> Slice(int start, int length)
return new Memory<T>(_object, _index + start, length | (capturedLength & ~RemoveFlagsBitMask));
}

/// <summary>
/// Forms a slice out of the given memory, beginning at 'startIndex'
/// </summary>
/// <param name="startIndex">The index at which to begin this slice.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Memory<T> Slice(Index startIndex)
{
int actualIndex = startIndex.GetOffset(_length);
return Slice(actualIndex);
}

/// <summary>
/// Forms a slice out of the given memory using the range start and end indexes.
/// </summary>
/// <param name="range">The range used to slice the memory using its start and end indexes.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Memory<T> Slice(Range range)
{
(int start, int length) = range.GetOffsetAndLength(_length);
// It is expected for _index + start to be negative if the memory is already pre-pinned.
return new Memory<T>(_object, _index + start, length);
}

/// <summary>
/// Forms a slice out of the given memory using the range start and end indexes.
/// </summary>
/// <param name="range">The range used to slice the memory using its start and end indexes.</param>
public Memory<T> this[Range range] => Slice(range);

/// <summary>
/// Returns a span from the memory.
/// </summary>
Expand Down
88 changes: 88 additions & 0 deletions src/Common/src/CoreLib/System/MemoryExtensions.Fast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,54 @@ public static Span<T> AsSpan<T>(this T[] array, int start)
return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start), array.Length - start);
}

/// <summary>
/// Creates a new span over the portion of the target array.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(this T[] array, Index startIndex)
{
if (array == null)
{
if (!startIndex.Equals(Index.Start))
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);

return default;
}

if (default(T) == null && array.GetType() != typeof(T[]))
ThrowHelper.ThrowArrayTypeMismatchException();

int actualIndex = startIndex.GetOffset(array.Length);
if ((uint)actualIndex > (uint)array.Length)
ThrowHelper.ThrowArgumentOutOfRangeException();

return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), actualIndex), array.Length - actualIndex);
}

/// <summary>
/// Creates a new span over the portion of the target array.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(this T[] array, Range range)
{
if (array == null)
{
Index startIndex = range.Start;
Index endIndex = range.End;

if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start))
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);

return default;
}

if (default(T) == null && array.GetType() != typeof(T[]))
ThrowHelper.ThrowArrayTypeMismatchException();

(int start, int length) = range.GetOffsetAndLength(array.Length);
return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start), length);
}

/// <summary>
/// Creates a new readonly span over the portion of the target string.
/// </summary>
Expand Down Expand Up @@ -474,6 +522,26 @@ public static ReadOnlyMemory<char> AsMemory(this string text, int start)
return new ReadOnlyMemory<char>(text, start, text.Length - start);
}

/// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
/// <param name="text">The target string.</param>
/// <param name="startIndex">The index at which to begin this slice.</param>
public static ReadOnlyMemory<char> AsMemory(this string text, Index startIndex)
{
if (text == null)
{
if (!startIndex.Equals(Index.Start))
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);

return default;
}

int actualIndex = startIndex.GetOffset(text.Length);
if ((uint)actualIndex > (uint)text.Length)
ThrowHelper.ThrowArgumentOutOfRangeException();

return new ReadOnlyMemory<char>(text, actualIndex, text.Length - actualIndex);
}

/// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
/// <param name="text">The target string.</param>
/// <param name="start">The index at which to begin this slice.</param>
Expand All @@ -496,5 +564,25 @@ public static ReadOnlyMemory<char> AsMemory(this string text, int start, int len

return new ReadOnlyMemory<char>(text, start, length);
}

/// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
/// <param name="text">The target string.</param>
/// <param name="range">The range used to indicate the start and length of the sliced string.</param>
public static ReadOnlyMemory<char> AsMemory(this string text, Range range)
{
if (text == null)
{
Index startIndex = range.Start;
Index endIndex = range.End;

if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start))
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);

return default;
}

(int start, int length) = range.GetOffsetAndLength(text.Length);
return new ReadOnlyMemory<char>(text, start, length);
}
}
}
Loading

0 comments on commit 029568f

Please sign in to comment.