diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
index 89aca914d0..3bb6b8d336 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
@@ -33,5 +33,15 @@ namespace SixLabors.ImageSharp.Memory
/// the image buffers internally.
///
bool IsValid { get; }
+
+ ///
+ /// Returns a value-type implementing an allocation-free enumerator of the memory groups in the current
+ /// instance. The return type shouldn't be used directly: just use a block on
+ /// the instance in use and the C# compiler will automatically invoke this
+ /// method behind the scenes. This method takes precedence over the
+ /// implementation, which is still available when casting to one of the underlying interfaces.
+ ///
+ /// A new instance mapping the current values in use.
+ new MemoryGroupEnumerator GetEnumerator();
}
}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs
new file mode 100644
index 0000000000..1bc44e33e1
--- /dev/null
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Memory
+{
+ ///
+ /// A value-type enumerator for instances.
+ ///
+ /// The element type.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ref struct MemoryGroupEnumerator
+ where T : struct
+ {
+ private readonly IMemoryGroup memoryGroup;
+ private readonly int count;
+ private int index;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal MemoryGroupEnumerator(MemoryGroup.Owned memoryGroup)
+ {
+ this.memoryGroup = memoryGroup;
+ this.count = memoryGroup.Count;
+ this.index = -1;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal MemoryGroupEnumerator(MemoryGroup.Consumed memoryGroup)
+ {
+ this.memoryGroup = memoryGroup;
+ this.count = memoryGroup.Count;
+ this.index = -1;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal MemoryGroupEnumerator(MemoryGroupView memoryGroup)
+ {
+ this.memoryGroup = memoryGroup;
+ this.count = memoryGroup.Count;
+ this.index = -1;
+ }
+
+ ///
+ public Memory Current
+ {
+ [MethodImpl(InliningOptions.ShortMethod)]
+ get => this.memoryGroup[this.index];
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public bool MoveNext()
+ {
+ int index = this.index + 1;
+
+ if (index < this.count)
+ {
+ this.index = index;
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
index 3f39ba12f5..1698f08d17 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
@@ -5,6 +5,7 @@ using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
@@ -37,6 +38,7 @@ namespace SixLabors.ImageSharp.Memory
public int Count
{
+ [MethodImpl(InliningOptions.ShortMethod)]
get
{
this.EnsureIsValid();
@@ -73,7 +75,15 @@ namespace SixLabors.ImageSharp.Memory
}
}
- public IEnumerator> GetEnumerator()
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public MemoryGroupEnumerator GetEnumerator()
+ {
+ return new MemoryGroupEnumerator(this);
+ }
+
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator()
{
this.EnsureIsValid();
for (int i = 0; i < this.Count; i++)
@@ -82,7 +92,8 @@ namespace SixLabors.ImageSharp.Memory
}
}
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator();
internal void Invalidate()
{
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
index f1fe4ed9c5..1dfbaea932 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
@@ -2,16 +2,17 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers;
using System.Collections.Generic;
-using System.Linq;
+using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
internal abstract partial class MemoryGroup
{
- // Analogous to the "consumed" variant of MemorySource
- private sealed class Consumed : MemoryGroup
+ ///
+ /// A implementation that consumes the underlying memory buffers.
+ ///
+ public sealed class Consumed : MemoryGroup, IEnumerable>
{
private readonly Memory[] source;
@@ -22,16 +23,31 @@ namespace SixLabors.ImageSharp.Memory
this.View = new MemoryGroupView(this);
}
- public override int Count => this.source.Length;
+ public override int Count
+ {
+ [MethodImpl(InliningOptions.ShortMethod)]
+ get => this.source.Length;
+ }
public override Memory this[int index] => this.source[index];
- public override IEnumerator> GetEnumerator()
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public override MemoryGroupEnumerator GetEnumerator()
+ {
+ return new MemoryGroupEnumerator(this);
+ }
+
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator()
{
- for (int i = 0; i < this.source.Length; i++)
- {
- yield return this.source[i];
- }
+ /* The runtime sees the Array class as if it implemented the
+ * type-generic collection interfaces explicitly, so here we
+ * can just cast the source array to IList> (or to
+ * an equivalent type), and invoke the generic GetEnumerator
+ * method directly from that interface reference. This saves
+ * having to create our own iterator block here. */
+ return ((IList>)this.source).GetEnumerator();
}
public override void Dispose()
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
index b42b90d286..5a86ac4268 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
@@ -5,13 +5,16 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
- // Analogous to the "owned" variant of MemorySource
internal abstract partial class MemoryGroup
{
- private sealed class Owned : MemoryGroup
+ ///
+ /// A implementation that owns the underlying memory buffers.
+ ///
+ public sealed class Owned : MemoryGroup, IEnumerable>
{
private IMemoryOwner[] memoryOwners;
@@ -29,6 +32,7 @@ namespace SixLabors.ImageSharp.Memory
public override int Count
{
+ [MethodImpl(InliningOptions.ShortMethod)]
get
{
this.EnsureNotDisposed();
@@ -45,7 +49,15 @@ namespace SixLabors.ImageSharp.Memory
}
}
- public override IEnumerator> GetEnumerator()
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public override MemoryGroupEnumerator GetEnumerator()
+ {
+ return new MemoryGroupEnumerator(this);
+ }
+
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator()
{
this.EnsureNotDisposed();
return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator();
@@ -69,14 +81,21 @@ namespace SixLabors.ImageSharp.Memory
this.IsValid = false;
}
+ [MethodImpl(InliningOptions.ShortMethod)]
private void EnsureNotDisposed()
{
- if (this.memoryOwners == null)
+ if (this.memoryOwners is null)
{
- throw new ObjectDisposedException(nameof(MemoryGroup));
+ ThrowObjectDisposedException();
}
}
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowObjectDisposedException()
+ {
+ throw new ObjectDisposedException(nameof(MemoryGroup));
+ }
+
internal static void SwapContents(Owned a, Owned b)
{
a.EnsureNotDisposed();
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
index 38de57b4ac..6fd93f12ea 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
@@ -6,7 +6,6 @@ using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
@@ -48,10 +47,21 @@ namespace SixLabors.ImageSharp.Memory
public abstract void Dispose();
///
- public abstract IEnumerator> GetEnumerator();
+ public abstract MemoryGroupEnumerator GetEnumerator();
///
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ IEnumerator> IEnumerable>.GetEnumerator()
+ {
+ /* This method is implemented in each derived class.
+ * Implementing the method here as non-abstract and throwing,
+ * then reimplementing it explicitly in each derived class, is
+ * a workaround for the lack of support for abstract explicit
+ * interface method implementations in C#. */
+ throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()");
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator();
///
/// Creates a new memory group, allocating it's buffers with the provided allocator.
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
index 694c4d32f6..2a5dafb27d 100644
--- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
@@ -1,8 +1,10 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
+using System.Collections;
+using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
@@ -165,7 +167,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
}
[Fact]
- public void Fill()
+ public void FillWithFastEnumerator()
{
using MemoryGroup group = this.CreateTestGroup(100, 10, true);
group.Fill(42);
@@ -177,6 +179,34 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
}
}
+ [Fact]
+ public void FillWithSlowGenericEnumerator()
+ {
+ using MemoryGroup group = this.CreateTestGroup(100, 10, true);
+ group.Fill(42);
+
+ int[] expectedRow = Enumerable.Repeat(42, 10).ToArray();
+ IReadOnlyList> groupAsList = group;
+ foreach (Memory memory in groupAsList)
+ {
+ Assert.True(memory.Span.SequenceEqual(expectedRow));
+ }
+ }
+
+ [Fact]
+ public void FillWithSlowEnumerator()
+ {
+ using MemoryGroup group = this.CreateTestGroup(100, 10, true);
+ group.Fill(42);
+
+ int[] expectedRow = Enumerable.Repeat(42, 10).ToArray();
+ IEnumerable groupAsList = group;
+ foreach (Memory memory in groupAsList)
+ {
+ Assert.True(memory.Span.SequenceEqual(expectedRow));
+ }
+ }
+
[Fact]
public void Clear()
{