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
Rationale
Users should be able to pass
Memory<T>to methods that take inIEnumerable<T>similar to array. Currently, this requires calling ToArray() to copy the contents of theMemory<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
https://github.com/Microsoft/CNTK/blob/master/bindings/csharp/CNTKLibraryManagedDll/ShimApiClasses/ValueShim.cs#L104
If
Memory<T>implementedIEnumerable<T>, then the ToArray() call would not be required. However, this would cause applications that reference System.Memory andMemory<T>for any scenario to be larger (when compiled AOT). Furthermore, ifMemory<T>was anIEnumerable<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 likeMemory<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 onMemory<T>.As an FYI,
Span<T>cannot implementIEnumerable<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 interfacesProposed API
Usage
Taking the above example, it would look as follows:
Partial Implementation
cc @eerhardt, @KrzysztofCwalina, @stephentoub, @jkotas, @terrajobst, @karelz, @ericstj