Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added SpanEnumerator<T> type #6

Merged
merged 3 commits into from
Feb 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions Microsoft.Toolkit.HighPerformance/Enumerables/SpanEnumerable{T}.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Microsoft.Toolkit.HighPerformance.Enumerables
{
/// <summary>
/// A <see langword="ref"/> <see langword="struct"/> that enumerates the items in a given <see cref="Span{T}"/> instance.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300", Justification = "The type is not meant to be used directly by users")]
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206", Justification = "The type is a ref struct")]
public readonly ref struct SpanEnumerable<T>
{
/// <summary>
/// The source <see cref="Span{T}"/> instance
/// </summary>
private readonly Span<T> span;

/// <summary>
/// Initializes a new instance of the <see cref="SpanEnumerable{T}"/> struct.
/// </summary>
/// <param name="span">The source <see cref="Span{T}"/> to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanEnumerable(Span<T> span)
{
this.span = span;
}

/// <summary>
/// Implements the duck-typed <see cref="IEnumerable{T}.GetEnumerator"/> method.
/// </summary>
/// <returns>An <see cref="Enumerator"/> instance targeting the current <see cref="Span{T}"/> value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(this.span);

/// <summary>
/// An enumerator for a source <see cref="Span{T}"/> instance.
/// </summary>
public ref struct Enumerator
{
/// <summary>
/// The source <see cref="Span{T}"/> instance.
/// </summary>
private readonly Span<T> span;

/// <summary>
/// The current index within <see cref="span"/>.
/// </summary>
private int index;

/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator(Span<T> span)
{
this.span = span;
this.index = -1;
}

/// <summary>
/// Implements the duck-typed <see cref="System.Collections.IEnumerator.MoveNext"/> method.
/// </summary>
/// <returns><see langword="true"/> whether a new element is available, <see langword="false"/> otherwise</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int newIndex = this.index + 1;

if (newIndex < this.span.Length)
{
this.index = newIndex;

return true;
}

return false;
}

/// <summary>
/// Gets the duck-typed <see cref="IEnumerator{T}.Current"/> property.
/// </summary>
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1008", Justification = "ValueTuple<T1,T2> return type")]
public Item Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if NETSTANDARD2_1
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T ri = ref Unsafe.Add(ref r0, index);

/* On .NET Standard 2.1 we can save 4 bytes by piggybacking
* the current index in the length of the wrapped span.
* We're going to use the first item as the target reference,
* and the length as a host for the current original offset.
* This is not possible on .NET Standard 2.1 as we lack
* the API to create spans from arbitrary references. */
return new Item(MemoryMarshal.CreateSpan(ref ri, index));
#else
return new Item(span, index);
#endif
}
}
}

/// <summary>
/// An item from a source <see cref="Span{T}"/> instance.
/// </summary>
public readonly ref struct Item
{
/// <summary>
/// The source <see cref="Span{T}"/> instance.
/// </summary>
private readonly Span<T> span;

#if NETSTANDARD2_1
/// <summary>
/// Initializes a new instance of the <see cref="Item"/> struct.
/// </summary>
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Item(Span<T> span)
{
this.span = span;
}
#else
/// <summary>
/// The current index within <see cref="span"/>.
/// </summary>
private readonly int index;

/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> struct.
/// </summary>
/// <param name="span">The source <see cref="Span{T}"/> instance.</param>
/// <param name="index">The current index within <paramref name="span"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Item(Span<T> span, int index)
{
this.span = span;
this.index = index;
}
#endif
/// <summary>
/// Gets the reference to the current value.
/// </summary>
public ref T Value
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if NETSTANDARD2_1
return ref MemoryMarshal.GetReference(span);
#else
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T ri = ref Unsafe.Add(ref r0, index);

return ref ri;
#endif
}
}

/// <summary>
/// Gets the current index.
/// </summary>
public int Index
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if NETSTANDARD2_1
return span.Length;
#else
return index;
#endif
}
}
}
}
}
14 changes: 7 additions & 7 deletions Microsoft.Toolkit.HighPerformance/Extensions/ArrayExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,29 +89,29 @@ public static int Count<T>(this T[] array, T value)
}

/// <summary>
/// Enumerates the items in the input <typeparamref name="T"/> array instance, as pairs of value/index values.
/// Enumerates the items in the input <typeparamref name="T"/> array instance, as pairs of reference/index values.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// string[] words = new[] { "Hello", ", ", "world", "!" };
/// int[] numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
///
/// foreach (var item in words.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// string value = item.Value;
/// ref int value = ref item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
/// <param name="array">The source <typeparamref name="T"/> array to enumerate.</param>
/// <returns>A wrapper type that will handle the value/index enumeration for <paramref name="array"/>.</returns>
/// <remarks>The returned <see cref="ReadOnlySpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
/// <returns>A wrapper type that will handle the reference/index enumeration for <paramref name="array"/>.</returns>
/// <remarks>The returned <see cref="SpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanEnumerable<T> Enumerate<T>(this T[] array)
public static SpanEnumerable<T> Enumerate<T>(this T[] array)
{
return new ReadOnlySpanEnumerable<T>(array);
return new SpanEnumerable<T>(array);
}

/// <summary>
Expand Down
14 changes: 7 additions & 7 deletions Microsoft.Toolkit.HighPerformance/Extensions/SpanExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,29 @@ public static int Count<T>(this Span<T> span, T value)
}

/// <summary>
/// Enumerates the items in the input <see cref="Span{T}"/> instance, as pairs of value/index values.
/// Enumerates the items in the input <see cref="Span{T}"/> instance, as pairs of reference/index values.
/// This extension should be used directly within a <see langword="foreach"/> loop:
/// <code>
/// Span&lt;string&gt; words = new[] { "Hello", ", ", "world", "!" };
/// Span&lt;int&gt; words = new[] { 1, 2, 3, 4, 5, 6, 7 };
///
/// foreach (var item in words.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// string value = item.Value;
/// ref int value = ref item.Value;
/// }
/// </code>
/// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method.
/// </summary>
/// <typeparam name="T">The type of items to enumerate.</typeparam>
/// <param name="span">The source <see cref="Span{T}"/> to enumerate.</param>
/// <returns>A wrapper type that will handle the value/index enumeration for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="ReadOnlySpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
/// <returns>A wrapper type that will handle the reference/index enumeration for <paramref name="span"/>.</returns>
/// <remarks>The returned <see cref="SpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanEnumerable<T> Enumerate<T>(this Span<T> span)
public static SpanEnumerable<T> Enumerate<T>(this Span<T> span)
{
return new ReadOnlySpanEnumerable<T>(span);
return new SpanEnumerable<T>(span);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests.HighPerformance.Extensions
{
[TestClass]
public class Test_SpanExtensions
{
[TestCategory("SpanExtensions")]
[TestMethod]
public void Test_SpanExtensions_DangerousGetReference()
{
Span<int> data = new[] { 1, 2, 3, 4, 5, 6, 7 };

ref int r0 = ref Unsafe.AsRef(data.DangerousGetReference());
ref int r1 = ref Unsafe.AsRef(data[0]);

Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}

[TestCategory("SpanExtensions")]
[TestMethod]
public void Test_SpanExtensions_DangerousGetReferenceAt_Zero()
{
Span<int> data = new[] { 1, 2, 3, 4, 5, 6, 7 };

ref int r0 = ref Unsafe.AsRef(data.DangerousGetReference());
ref int r1 = ref Unsafe.AsRef(data.DangerousGetReferenceAt(0));

Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}

[TestCategory("SpanExtensions")]
[TestMethod]
public void Test_SpanExtensions_DangerousGetReferenceAt_Index()
{
Span<int> data = new[] { 1, 2, 3, 4, 5, 6, 7 };

ref int r0 = ref Unsafe.AsRef(data.DangerousGetReferenceAt(5));
ref int r1 = ref Unsafe.AsRef(data[5]);

Assert.IsTrue(Unsafe.AreSame(ref r0, ref r1));
}

[TestCategory("SpanExtensions")]
[TestMethod]
public void Test_SpanExtensions_Enumerate()
{
Span<int> data = new[] { 1, 2, 3, 4, 5, 6, 7 };

List<(int Index, int Value)> values = new List<(int, int)>();

foreach (var item in data.Enumerate())
{
values.Add((item.Index, item.Value));

item.Value = item.Index * 10;
}

Assert.AreEqual(values.Count, data.Length);

for (int i = 0; i < data.Length; i++)
{
Assert.AreEqual(data[i], i * 10);
Assert.AreEqual(i, values[i].Index);
Assert.AreEqual(i + 1, values[i].Value);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ArrayPoolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_HashCodeExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_SpanExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlySpanExtensions.Count.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_ReadOnlySpanExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\Test_SpinLockExtensions.cs" />
Expand Down