Skip to content

Commit d8a149f

Browse files
AlexRadchstephentoub
authored andcommitted
Implement IEnumerator<T> on ref struct enumerators
1 parent 08e6c2e commit d8a149f

File tree

17 files changed

+734
-24
lines changed

17 files changed

+734
-24
lines changed

src/libraries/System.Memory/ref/System.Memory.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -513,14 +513,17 @@ public static void Sort<TKey, TValue, TComparer>(this System.Span<TKey> keys, Sy
513513
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; }
514514
public static bool TryWrite(this Span<char> destination, System.IFormatProvider? provider, System.Text.CompositeFormat format, out int charsWritten, params object?[] args) { throw null; }
515515
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; }
516-
public ref struct SpanSplitEnumerator<T> where T : System.IEquatable<T>
516+
public ref struct SpanSplitEnumerator<T> : System.Collections.Generic.IEnumerator<System.Range>, System.Collections.IEnumerator, System.IDisposable where T : System.IEquatable<T>
517517
{
518518
private object _dummy;
519519
private int _dummyPrimitive;
520520
public readonly System.Range Current { get { throw null; } }
521521
public readonly System.ReadOnlySpan<T> Source { get { throw null; } }
522522
public System.MemoryExtensions.SpanSplitEnumerator<T> GetEnumerator() { throw null; }
523523
public bool MoveNext() { throw null; }
524+
object System.Collections.IEnumerator.Current { get { throw null; } }
525+
void System.Collections.IEnumerator.Reset() { throw null; }
526+
void System.IDisposable.Dispose() { throw null; }
524527
}
525528
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
526529
[System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute]
@@ -801,20 +804,26 @@ public static partial class Utf8Parser
801804
}
802805
namespace System.Text
803806
{
804-
public ref partial struct SpanLineEnumerator
807+
public ref partial struct SpanLineEnumerator : System.Collections.Generic.IEnumerator<System.ReadOnlySpan<char>>, System.Collections.IEnumerator, System.IDisposable
805808
{
806809
private object _dummy;
807810
private int _dummyPrimitive;
808811
public System.ReadOnlySpan<char> Current { get { throw null; } }
809812
public System.Text.SpanLineEnumerator GetEnumerator() { throw null; }
810813
public bool MoveNext() { throw null; }
814+
object System.Collections.IEnumerator.Current { get { throw null; } }
815+
void System.Collections.IEnumerator.Reset() { throw null; }
816+
void IDisposable.Dispose() { throw null; }
811817
}
812-
public ref partial struct SpanRuneEnumerator
818+
public ref partial struct SpanRuneEnumerator : System.Collections.Generic.IEnumerator<System.Text.Rune>, System.Collections.IEnumerator, System.IDisposable
813819
{
814820
private object _dummy;
815821
private int _dummyPrimitive;
816822
public System.Text.Rune Current { get { throw null; } }
817823
public System.Text.SpanRuneEnumerator GetEnumerator() { throw null; }
818824
public bool MoveNext() { throw null; }
825+
object System.Collections.IEnumerator.Current { get { throw null; } }
826+
void System.Collections.IEnumerator.Reset() { throw null; }
827+
void IDisposable.Dispose() { throw null; }
819828
}
820829
}

src/libraries/System.Memory/tests/ReadOnlySpan/GetEnumerator.cs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using Xunit;
5-
using System.Linq;
4+
using System.Collections;
65
using System.Collections.Generic;
6+
using System.Linq;
7+
using Xunit;
78

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

3132
Assert.Equal(Enumerable.Sum(array), sum);
33+
Assert.Equal(Enumerable.Sum(array), SumGI(span.GetEnumerator()));
34+
Assert.Equal(Enumerable.Sum(array), SumI(span.GetEnumerator()));
3235
}
3336

3437
[Theory]
@@ -48,13 +51,79 @@ public static void GetEnumerator_Manual_AllValuesReturnedCorrectly(int[] array)
4851
Assert.False(e.MoveNext());
4952

5053
Assert.Equal(Enumerable.Sum(array), sum);
54+
Assert.Equal(Enumerable.Sum(array), SumGI(span.GetEnumerator()));
55+
Assert.Equal(Enumerable.Sum(array), SumI(span.GetEnumerator()));
5156
}
5257

5358
[Fact]
5459
public static void GetEnumerator_MoveNextOnDefault_ReturnsFalse()
5560
{
5661
Assert.False(default(ReadOnlySpan<int>.Enumerator).MoveNext());
5762
Assert.ThrowsAny<Exception>(() => default(ReadOnlySpan<int>.Enumerator).Current);
63+
64+
TestGI(default(ReadOnlySpan<int>.Enumerator));
65+
TestI(default(ReadOnlySpan<int>.Enumerator));
66+
67+
static void TestGI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator<int>, allows ref struct
68+
{
69+
Assert.False(enumerator.MoveNext());
70+
enumerator.Dispose();
71+
enumerator.Reset();
72+
Assert.False(enumerator.MoveNext());
73+
}
74+
75+
static void TestI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct
76+
{
77+
Assert.False(enumerator.MoveNext());
78+
enumerator.Reset();
79+
Assert.False(enumerator.MoveNext());
80+
}
81+
}
82+
83+
private static int SumGI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator<int>, allows ref struct
84+
{
85+
int sum1 = 0;
86+
enumerator.Dispose();
87+
while (enumerator.MoveNext())
88+
{
89+
sum1 += enumerator.Current;
90+
enumerator.Dispose();
91+
}
92+
Assert.False(enumerator.MoveNext());
93+
94+
int sum2 = 0;
95+
enumerator.Reset();
96+
enumerator.Dispose();
97+
while (enumerator.MoveNext())
98+
{
99+
sum2 += enumerator.Current;
100+
enumerator.Dispose();
101+
}
102+
Assert.False(enumerator.MoveNext());
103+
104+
Assert.Equal(sum1, sum2);
105+
return sum2;
106+
}
107+
108+
private static int SumI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct
109+
{
110+
int sum1 = 0;
111+
while (enumerator.MoveNext())
112+
{
113+
sum1 += (int)enumerator.Current;
114+
}
115+
Assert.False(enumerator.MoveNext());
116+
117+
int sum2 = 0;
118+
enumerator.Reset();
119+
while (enumerator.MoveNext())
120+
{
121+
sum2 += (int)enumerator.Current;
122+
}
123+
Assert.False(enumerator.MoveNext());
124+
125+
Assert.Equal(sum1, sum2);
126+
return sum2;
58127
}
59128
}
60129
}

src/libraries/System.Memory/tests/ReadOnlySpan/Split.T.cs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Buffers;
6+
using System.Collections;
7+
using System.Collections.Generic;
68
using Xunit;
79

810
namespace System.SpanTests
911
{
1012
public static partial class ReadOnlySpanTests
1113
{
1214
[Fact]
13-
public static void DefaultSpanSplitEnumeratorBehaviour()
15+
public static void SpanSplitEnumerator_Default()
1416
{
1517
var charSpanEnumerator = new MemoryExtensions.SpanSplitEnumerator<char>();
1618
Assert.Equal(new Range(0, 0), charSpanEnumerator.Current);
@@ -19,10 +21,34 @@ public static void DefaultSpanSplitEnumeratorBehaviour()
1921
// Implicit DoesNotThrow assertion
2022
charSpanEnumerator.GetEnumerator();
2123

24+
TestGI(charSpanEnumerator);
25+
TestI(charSpanEnumerator);
26+
2227
var stringSpanEnumerator = new MemoryExtensions.SpanSplitEnumerator<string>();
2328
Assert.Equal(new Range(0, 0), stringSpanEnumerator.Current);
2429
Assert.False(stringSpanEnumerator.MoveNext());
2530
stringSpanEnumerator.GetEnumerator();
31+
TestGI(stringSpanEnumerator);
32+
TestI(stringSpanEnumerator);
33+
34+
static void TestGI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator<Range>, allows ref struct
35+
{
36+
Assert.Equal(new Range(0, 0), enumerator.Current);
37+
Assert.False(enumerator.MoveNext());
38+
try { enumerator.Reset(); } catch (NotSupportedException) { }
39+
enumerator.Dispose();
40+
Assert.Equal(new Range(0, 0), enumerator.Current);
41+
Assert.False(enumerator.MoveNext());
42+
}
43+
44+
static void TestI<TEnumerator>(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct
45+
{
46+
Assert.Equal(new Range(0, 0), enumerator.Current);
47+
Assert.False(enumerator.MoveNext());
48+
try { enumerator.Reset(); } catch (NotSupportedException) { }
49+
Assert.Equal(new Range(0, 0), enumerator.Current);
50+
Assert.False(enumerator.MoveNext());
51+
}
2652
}
2753

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

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

197223
private static void AssertEnsureCorrectEnumeration<T>(MemoryExtensions.SpanSplitEnumerator<T> enumerator, Range[] result) where T : IEquatable<T>
198224
{
225+
AssertEnsureCorrectEnumerationGI<T, MemoryExtensions.SpanSplitEnumerator<T>>(enumerator, result);
226+
AssertEnsureCorrectEnumerationI<T, MemoryExtensions.SpanSplitEnumerator<T>>(enumerator, result);
227+
199228
Assert.Equal(new Range(0, 0), enumerator.Current);
200229

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

239+
private static void AssertEnsureCorrectEnumerationGI<T, TEnumerator>(TEnumerator enumerator, Range[] result)
240+
where T : IEquatable<T>
241+
where TEnumerator : IEnumerator<Range>, allows ref struct
242+
{
243+
Assert.Equal(new Range(0, 0), enumerator.Current);
244+
try { enumerator.Reset(); } catch (NotSupportedException) { }
245+
enumerator.Dispose();
246+
Assert.Equal(new Range(0, 0), enumerator.Current);
247+
248+
for (int i = 0; i < result.Length; i++)
249+
{
250+
enumerator.Dispose();
251+
try { enumerator.Reset(); } catch (NotSupportedException) { }
252+
Assert.True(enumerator.MoveNext());
253+
Assert.Equal(result[i], enumerator.Current);
254+
}
255+
256+
Assert.False(enumerator.MoveNext());
257+
try { enumerator.Reset(); } catch (NotSupportedException) { }
258+
enumerator.Dispose();
259+
Assert.False(enumerator.MoveNext());
260+
}
261+
262+
private static void AssertEnsureCorrectEnumerationI<T, TEnumerator>(TEnumerator enumerator, Range[] result)
263+
where T : IEquatable<T>
264+
where TEnumerator : IEnumerator, allows ref struct
265+
{
266+
Assert.Equal(new Range(0, 0), enumerator.Current);
267+
268+
try { enumerator.Reset(); } catch (NotSupportedException) { }
269+
Assert.Equal(new Range(0, 0), enumerator.Current);
270+
271+
for (int i = 0; i < result.Length; i++)
272+
{
273+
try { enumerator.Reset(); } catch (NotSupportedException) { }
274+
Assert.True(enumerator.MoveNext());
275+
Assert.Equal(result[i], enumerator.Current);
276+
}
277+
278+
Assert.False(enumerator.MoveNext());
279+
try { enumerator.Reset(); } catch (NotSupportedException) { }
280+
Assert.False(enumerator.MoveNext());
281+
}
282+
210283
public record struct CustomStruct(int value) : IEquatable<CustomStruct>;
211284

212285
public record class CustomClass(int value) : IEquatable<CustomClass>;

0 commit comments

Comments
 (0)