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

Implement IEnumerator<T> on ref struct enumerators #106309

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
15 changes: 12 additions & 3 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -415,13 +415,16 @@ public static void Sort<TKey, TValue, TComparer>(this System.Span<TKey> keys, Sy
public static bool TryWrite<TArg0, TArg1, TArg2>(this System.Span<char> destination, System.IFormatProvider? provider, System.Text.CompositeFormat format, out int charsWritten, TArg0 arg0, TArg1 arg1, TArg2 arg2) { throw null; }
public static bool TryWrite(this Span<char> destination, System.IFormatProvider? provider, System.Text.CompositeFormat format, out int charsWritten, params object?[] args) { throw null; }
public static bool TryWrite(this Span<char> destination, System.IFormatProvider? provider, System.Text.CompositeFormat format, out int charsWritten, params System.ReadOnlySpan<object?> args) { throw null; }
public ref struct SpanSplitEnumerator<T> where T : System.IEquatable<T>
public ref struct SpanSplitEnumerator<T> : System.Collections.Generic.IEnumerator<System.Range>, System.Collections.IEnumerator, System.IDisposable where T : System.IEquatable<T>
{
private object _dummy;
private int _dummyPrimitive;
public readonly System.Range Current { get { throw null; } }
public System.MemoryExtensions.SpanSplitEnumerator<T> GetEnumerator() { throw null; }
public bool MoveNext() { throw null; }
object System.Collections.IEnumerator.Current { get { throw null; } }
void System.Collections.IEnumerator.Reset() { throw null; }
void System.IDisposable.Dispose() { throw null; }
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute]
Expand Down Expand Up @@ -702,20 +705,26 @@ public static partial class Utf8Parser
}
namespace System.Text
{
public ref partial struct SpanLineEnumerator
public ref partial struct SpanLineEnumerator : System.Collections.Generic.IEnumerator<System.ReadOnlySpan<char>>, System.Collections.IEnumerator, System.IDisposable
{
private object _dummy;
private int _dummyPrimitive;
public System.ReadOnlySpan<char> Current { get { throw null; } }
public System.Text.SpanLineEnumerator GetEnumerator() { throw null; }
public bool MoveNext() { throw null; }
object System.Collections.IEnumerator.Current { get { throw null; } }
void System.Collections.IEnumerator.Reset() { throw null; }
void IDisposable.Dispose() { throw null; }
}
public ref partial struct SpanRuneEnumerator
public ref partial struct SpanRuneEnumerator : System.Collections.Generic.IEnumerator<System.Text.Rune>, System.Collections.IEnumerator, System.IDisposable
{
private object _dummy;
private int _dummyPrimitive;
public System.Text.Rune Current { get { throw null; } }
public System.Text.SpanRuneEnumerator GetEnumerator() { throw null; }
public bool MoveNext() { throw null; }
object System.Collections.IEnumerator.Current { get { throw null; } }
void System.Collections.IEnumerator.Reset() { throw null; }
void IDisposable.Dispose() { throw null; }
}
}
73 changes: 73 additions & 0 deletions src/libraries/System.Memory/tests/ReadOnlySpan/GetEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Xunit;
using System.Linq;
using System.Collections.Generic;
using System.Collections;

namespace System.SpanTests
{
Expand All @@ -29,6 +30,8 @@ public static void GetEnumerator_ForEach_AllValuesReturnedCorrectly(int[] array)
}

Assert.Equal(Enumerable.Sum(array), sum);
Assert.Equal(Enumerable.Sum(array), SumGI(span.GetEnumerator()));
Assert.Equal(Enumerable.Sum(array), SumI(span.GetEnumerator()));
}

[Theory]
Expand All @@ -48,13 +51,83 @@ public static void GetEnumerator_Manual_AllValuesReturnedCorrectly(int[] array)
Assert.False(e.MoveNext());

Assert.Equal(Enumerable.Sum(array), sum);
Assert.Equal(Enumerable.Sum(array), SumGI(span.GetEnumerator()));
Assert.Equal(Enumerable.Sum(array), SumI(span.GetEnumerator()));
}

[Fact]
public static void GetEnumerator_MoveNextOnDefault_ReturnsFalse()
{
Assert.False(default(ReadOnlySpan<int>.Enumerator).MoveNext());
Assert.ThrowsAny<Exception>(() => default(ReadOnlySpan<int>.Enumerator).Current);

TestGI(default(ReadOnlySpan<int>.Enumerator));
TestI(default(ReadOnlySpan<int>.Enumerator));

static void TestGI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator<int>, allows ref struct
{
Assert.False(enumerator.MoveNext());
try { _ = enumerator.Current; } catch (Exception) { }
enumerator.Dispose();
enumerator.Reset();
Assert.False(enumerator.MoveNext());
try { _ = enumerator.Current; } catch (Exception) { }
}

static void TestI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct
{
Assert.False(enumerator.MoveNext());
try { _ = enumerator.Current; } catch (Exception) { }
enumerator.Reset();
Assert.False(enumerator.MoveNext());
try { _ = enumerator.Current; } catch (Exception) { }
}
}

private static int SumGI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator<int>, allows ref struct
{
int sum1 = 0;
enumerator.Dispose();
while (enumerator.MoveNext())
{
sum1 += enumerator.Current;
enumerator.Dispose();
}
Assert.False(enumerator.MoveNext());

int sum2 = 0;
enumerator.Reset();
enumerator.Dispose();
while (enumerator.MoveNext())
{
sum2 += enumerator.Current;
enumerator.Dispose();
}
Assert.False(enumerator.MoveNext());

Assert.Equal(sum1, sum2);
return sum2;
}

private static int SumI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct
{
int sum1 = 0;
while (enumerator.MoveNext())
{
sum1 += (int)enumerator.Current;
}
Assert.False(enumerator.MoveNext());

int sum2 = 0;
enumerator.Reset();
while (enumerator.MoveNext())
{
sum2 += (int)enumerator.Current;
}
Assert.False(enumerator.MoveNext());

Assert.Equal(sum1, sum2);
return sum2;
}
}
}
77 changes: 75 additions & 2 deletions src/libraries/System.Memory/tests/ReadOnlySpan/Split.T.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
// See the LICENSE file in the project root for more information.

using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using Xunit;

namespace System.SpanTests
{
public static partial class ReadOnlySpanTests
{
[Fact]
public static void DefaultSpanSplitEnumeratorBehaviour()
public static void SpanSplitEnumerator_Default()
{
var charSpanEnumerator = new MemoryExtensions.SpanSplitEnumerator<char>();
Assert.Equal(new Range(0, 0), charSpanEnumerator.Current);
Expand All @@ -19,10 +21,34 @@ public static void DefaultSpanSplitEnumeratorBehaviour()
// Implicit DoesNotThrow assertion
charSpanEnumerator.GetEnumerator();

TestGI(charSpanEnumerator);
TestI(charSpanEnumerator);

var stringSpanEnumerator = new MemoryExtensions.SpanSplitEnumerator<string>();
Assert.Equal(new Range(0, 0), stringSpanEnumerator.Current);
Assert.False(stringSpanEnumerator.MoveNext());
stringSpanEnumerator.GetEnumerator();
TestGI(stringSpanEnumerator);
TestI(stringSpanEnumerator);

static void TestGI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator<Range>, allows ref struct
{
Assert.Equal(new Range(0, 0), enumerator.Current);
Assert.False(enumerator.MoveNext());
try { enumerator.Reset(); } catch (NotSupportedException) { }
enumerator.Dispose();
Assert.Equal(new Range(0, 0), enumerator.Current);
Assert.False(enumerator.MoveNext());
}

static void TestI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct
{
Assert.Equal(new Range(0, 0), enumerator.Current);
Assert.False(enumerator.MoveNext());
try { enumerator.Reset(); } catch (NotSupportedException) { }
Assert.Equal(new Range(0, 0), enumerator.Current);
Assert.False(enumerator.MoveNext());
}
}

[Fact]
Expand Down Expand Up @@ -132,7 +158,7 @@ static void Test<T>(T[] value, T[] separator, Range[] result) where T : IEquatab
}

[Fact]
public static void SplitAnySeparatorData()
public static void SplitAny_SeparatorData()
{
// Split no separators
Test((char[])['a', ' ', 'b'], (char[])[], (Range[])[0..1, 2..3]); // an empty span of separators for char is handled as all whitespace being separators
Expand Down Expand Up @@ -196,6 +222,9 @@ separator is char[] separators &&

private static void AssertEnsureCorrectEnumeration<T>(MemoryExtensions.SpanSplitEnumerator<T> enumerator, Range[] result) where T : IEquatable<T>
{
AssertEnsureCorrectEnumerationGI<T, MemoryExtensions.SpanSplitEnumerator<T>>(enumerator, result);
AssertEnsureCorrectEnumerationI<T, MemoryExtensions.SpanSplitEnumerator<T>>(enumerator, result);

Assert.Equal(new Range(0, 0), enumerator.Current);

for (int i = 0; i < result.Length; i++)
Expand All @@ -207,6 +236,50 @@ private static void AssertEnsureCorrectEnumeration<T>(MemoryExtensions.SpanSplit
Assert.False(enumerator.MoveNext());
}

private static void AssertEnsureCorrectEnumerationGI<T, TEnumerator>(TEnumerator enumerator, Range[] result)
where T : IEquatable<T>
where TEnumerator : IEnumerator<Range>, allows ref struct
{
Assert.Equal(new Range(0, 0), enumerator.Current);
try { enumerator.Reset(); } catch (NotSupportedException) { }
enumerator.Dispose();
Assert.Equal(new Range(0, 0), enumerator.Current);

for (int i = 0; i < result.Length; i++)
{
enumerator.Dispose();
try { enumerator.Reset(); } catch (NotSupportedException) { }
Assert.True(enumerator.MoveNext());
Assert.Equal(result[i], enumerator.Current);
}

Assert.False(enumerator.MoveNext());
try { enumerator.Reset(); } catch (NotSupportedException) { }
enumerator.Dispose();
Assert.False(enumerator.MoveNext());
}

private static void AssertEnsureCorrectEnumerationI<T, TEnumerator>(TEnumerator enumerator, Range[] result)
where T : IEquatable<T>
where TEnumerator : IEnumerator, allows ref struct
{
Assert.Equal(new Range(0, 0), enumerator.Current);

try { enumerator.Reset(); } catch (NotSupportedException) { }
Assert.Equal(new Range(0, 0), enumerator.Current);

for (int i = 0; i < result.Length; i++)
{
try { enumerator.Reset(); } catch (NotSupportedException) { }
Assert.True(enumerator.MoveNext());
Assert.Equal(result[i], enumerator.Current);
}

Assert.False(enumerator.MoveNext());
try { enumerator.Reset(); } catch (NotSupportedException) { }
Assert.False(enumerator.MoveNext());
}

public record struct CustomStruct(int value) : IEquatable<CustomStruct>;

public record class CustomClass(int value) : IEquatable<CustomClass>;
Expand Down
Loading
Loading