-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Provide IEnumerable<T> support for Memory<T> #23950
Comments
Would other collection interfaces make sense too? Like |
How is Memory any more susceptible than IEnumerable on other types like List and T[]? |
Why can’t Memory{T} implement IEnumerable{T} directly? That way you wouldn’t need to call a method, nor alloc a throw away object. |
It is just as susceptible. The difference being we can change
See Rationale section:
|
You always need to alloc a throw away object to get IEnumerable out of memory. If Memory implemented IEnumerable directly, casting to IEnumerable would box. |
Oh right. I missed that Memory{T} is a struct. |
This is an implementation detail, but we should use the same allocated object as both the enumerable and enumerator in the common case, as is done by the compiler with iterators and in LINQ... this could potentially even just be implemented with an iterator: public static IEnumerable<T> ToEnumerable(this Memory<T> memory)
{
for (int i = 0; i < memory.Length; i++) yield return memory.Span[i];
} |
Only tangentially related to this issue, have we considered giving Span an instance or extension GetEnumerator that returns a ref struct Enumerator? And/or have there been any discussions about enabling the compiler to support foreach'ing a span and generating optimal code for that? Just as I can foreach an array and end up with asm equivalent to walking through each element, it'd be nice to be able to do that with span as well, e.g. foreach (T item in span)
{
...
} being the same as: for (int i = 0; i < span.Length; i++)
{
T item = span[i];
...
} EDIT: C# doesn't currently support extension GetEnumerators in foreach; either it would need to be an instance method, or the language would need to be updated to either special-case span or enable extension GetEnumerators. cc: @VSadov, @MadsTorgersen |
Language support for foreach would be great. |
Looks good as proposed. |
I opened dotnet/csharplang#1085 for |
FYI: The API review discussion was recorded - see https://youtu.be/HnKmLJcEM74?t=391 (13 min duration) |
@ahsonkhan, should we close this issue now? We added the enumerator. [EDIT] Editing correct @ahsonkhan by @karelz |
We added one to span. I don't think we added one to memory. |
I assume the one added to Memory would be a by ref struct, i.e. it would cache the span? If yes, I think it's fine to add it. Otherwise I think it's a perf trap. |
I would assume not, as you'd most likely want to use it with LINQ and other such consumers. And if you did want a byref one, you could instead enumerate .Span. |
We tried the same route with ImmutableArray - add an adapter class and ToEnumerable method (or was it AsList ? ) - we did not like boxing of ImmutableArray. |
Do we still want to add |
|
isn't that exactly what we're already doing right here (though as Enumerator, not IEnumerator)? Or did you mean that you can't put a Memory into the Enumerator (which makes sense because it's not by-ref)? |
No, that's a ref struct, and it doesn't implement
We're not adding an |
Thanks @stephentoub, I've got some better context now. Your comments in The API Review video on this topic helped too :) |
There's pushback in dotnet/corefx#26284 about this being implemented as an extension method so I closed it so we can decide for sure what the API shape will look like and where the API should live (MemoryMarshal? Some new class?) |
Consume Span moves - [Move Span.DangerousCreate to MemoryMarshal.CreateSpan](#26139) - [Move ReadOnlySpan.DangerousCreate to MemoryMarshal.CreateReadOnlySpan](#26139) - [Move Span.NonPortableCast to MemoryMarshal.Cast](#26368) - [Move ReadOnlySpan.NonPortableCast to MemoryMarshal.Cast](#26368) - [Add ToString override to Span and ReadOnlySpan](#26295) - [Add ToEnumerable function to MemoryMarshal that takes a Memory](#24854)
Any news? |
@GrabYourPitchforks are you driving this? If not, who should be? |
@terrajobst I'm not currently driving this because it seems to be low-priority. The salient point in this issue that people really reacted to was Steve's comment at #23950 (comment), and we did indeed eventually get foreach support over If the consensus is that we should add this API, I'm very much partial to my most recent comments starting at #23950 (comment), as I think those largely address the performance trap that people were concerned with. |
Sounds good. |
Hey guys, so we can do: foreach on spans, and AFAIK .NET makes a promise to use Duck-Typing for such constructs, like GetPinnableReference() to be used in fixed(....) but why is the compiler not smart enough (.NET 6.0 ) to infer that Span is duck-typed to an IEnumerable without implementing it Cause when an extension method takes a T, where T : IEnumerable>T>, it could use internal Duck-Typing to infer, the type actually does the same as it being an IEnumerable. Would be really nice if .NET could make ducktyping a thing, i mean coding all these LINQ things will be quite ugly :D |
C# compiler does infer that in .NET 6. |
I already said that, good Sir, namely below:
This is a good thing, its just, we cannot make use of the Enumerable-LINQ-Extension methods sadly, cause the Compiler still strictly wants the callee of these methods to be of type IEnumerable. That is my Dilemma |
The C# compiler couldn't fabricate a wrapper IEnumerable for the same reason Span doesn't implement IEnumerable: it's a ref struct that can't be boxed or otherwise copied to the heap. |
@stephentoub yea ok, wanted just to get sure on this! Thanks and have a good and healthy Week! |
@Shpendicus For what it's worth: SpanLinq |
@Joe4evr i think they are on a really good track, I also saw a nuget package called: NoAlloc maybe you can try that one aswell 🗡️ |
Marking as ready-for-review in light of the discussion in #77209 (comment). It has been suggested that IEqualityComparer<ReadOnlyMemory<T>> CreateMemoryComparer<T>(IEqualityComparer<T>) method and we can specialize the already approved IEqualityComparer<TEnumerable> CreateEnumerableComparer<TEnumerable, T>(IEqualityComparer<T>) where TEnumerable : IEnumerable<T> method instead. |
This came up in API Review, and the main question that we had was "why is |
@bartonjs : I just now looked into it. It looks like this issue is an example of discovery fail. Oof. I would never think to look in namespace interopservices for something like that. It's also a pretty terrible implementation, performance-wise. I left behind a much faster one in a comment above. |
That's fair. I don't recall why we put it there but it seems to suggest the intent was to make it less discoverable.
I think this depends on what you want to achieve. If you just want to enumerate the contents in a But if you need to pass the memory to an API that wants an Come to think of it, I believe we didn't want an API like |
"And I don't think the implementation can avoid requesting the span for each element" Yes it can. Since it is inside System.Private.CoreLib it can unwrap the Memory, which is what I posted. Oh well. |
I can't seem to find your comment. Could you link it? |
The APIs for unwrapping a The problem is when the |
I see, you're suggesting an implementation for Personally, I'm somewhat questioning how common of a problem memory->enumerable is in practice. To some extent, I think of these features as opposites. |
We're essentially entering the space of "niche within niche" here, but if you want to allow as many cases to be efficient as possible, it may make sense to add a new namespace System.Buffers;
public abstract partial class MemoryManager<T>
{
public virtual IEnumerable<T> ToEnumerable()
{
// Default implementation using 'yield return'
}
} An example of the kind of code that might benefit from this is in the community toolkit. There are APIs in the toolkit that offer the equivalent of Is that worth it for such a niche use case though? I'm skeptical. |
#89274 I put up earlier fixes that. |
@stephentoub Ah good that problem's going away. |
Is there a reason this wasn't exposed as an extension method? |
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 interfaces
Proposed API
Usage
Taking the above example, it would look as follows:
Partial Implementation
cc @eerhardt, @KrzysztofCwalina, @stephentoub, @jkotas, @terrajobst, @karelz, @ericstj
The text was updated successfully, but these errors were encountered: