Skip to content

Provide IEnumerable<T> support for Memory<T> #23950

Description

@ahsonkhan

Rationale

Users should be able to pass Memory<T> to methods that take in IEnumerable<T> similar to array. Currently, this requires calling ToArray() to copy the contents of the Memory<T> into an array for it to work, and is unnecessary.

For example (from @eerhardt):
https://github.com/dotnet/corefxlab/blob/master/src/System.Numerics.Tensors/System/Numerics/DenseTensor.cs#L10

public void SomeMethod(DenseTensor<float> tensor)
{
   Memory<float> memory = tensor.Buffer;
   var value = CreateBatch<T>(..., memory.ToArray(), ...);
}

https://github.com/Microsoft/CNTK/blob/master/bindings/csharp/CNTKLibraryManagedDll/ShimApiClasses/ValueShim.cs#L104

public static Value CreateBatch<T>(NDShape sampleShape, IEnumerable<T> batch, DeviceDescriptor device, bool readOnly = false)
{
    T[] batchAsArray = batch.ToArray();
    return CreateBatch<T>(sampleShape, batchAsArray, 0, batchAsArray.Count(), device, readOnly);
}

If Memory<T> implemented IEnumerable<T>, then the ToArray() call would not be required. However, this would cause applications that reference System.Memory and Memory<T> for any scenario to be larger (when compiled AOT). Furthermore, if Memory<T> was an IEnumerable<T>, it might result in a pit of failure as it could lead to users unintentionally using the enumerator to iterate over the data rather than the more performant indexer on Memory.Span (especially for primitive types like Memory<byte>). To discourage unnecessary use of the enumerator but still provide support for the scenarios where you need an IEnumerable, the proposed solution is to add an adapter class and a ToEnumerable extension method on Memory<T>.

As an FYI, Span<T> cannot implement IEnumerable<T> since it is a stack-only, byref type, and casting it to an interface would cause boxing. The compiler will throw and error: 'Span<T>': ref structs cannot implement interfaces

Proposed API

namespace System
{
   public static class MemoryExtensions {
     public static IEnumerable<T> ToEnumerable(this Memory<T> memory);
   }
}

Usage

Taking the above example, it would look as follows:

public void SomeMethod(DenseTensor<float> tensor)
{
   Memory<float> memory = tensor.Buffer;
   var value = CreateBatch<T>(..., memory.ToEnumerable(), ...);
}

Partial Implementation

public static IEnumerable<T> ToEnumerable(this Memory<T> memory) => new MemoryEnumerable<T>(memory);

internal class MemoryEnumerable<T> : IEnumerable<T>
{
    ReadOnlyMemory<T> _memory;

    public MemoryEnumerable(ReadOnlyMemory<T> memory) => _memory = memory;

    public IEnumerator<T> GetEnumerator() => new MemoryEnumerator<T>(_memory);

    IEnumerator IEnumerable.GetEnumerator() => new MemoryEnumerator<T>(_memory);
}

internal class MemoryEnumerator<T> : IEnumerator<T>
{
    ReadOnlyMemory<T> _memory;
    int _index;

    public MemoryEnumerator(ReadOnlyMemory<T> memory) => _memory = memory;

    public T Current => _memory.Span[_index];

    object IEnumerator.Current => _memory.Span[_index];

    public void Dispose()
    {
        throw new NotImplementedException();
    }

    public bool MoveNext()
    {
        _index++;
        return _index <_memory.Length;
    }

    public void Reset()
    {
        _index = 0;
    }
}

cc @eerhardt, @KrzysztofCwalina, @stephentoub, @jkotas, @terrajobst, @karelz, @ericstj

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.Memory

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions