Allow foreach-ing over IEnumerator-like types #8684
Replies: 4 comments 14 replies
-
Can you make your enumerable a regular struct instead of ref struct? Then you could just store the ref field directly in the ref struct enumerator. Sadly ref fields to ref structs are not supported yet. |
Beta Was this translation helpful? Give feedback.
-
I've never done anything like this so take it with a grain of salt, but can you wrap the pixel pointer in a struct which contains the pixel pointer and the X/Y that you need? |
Beta Was this translation helpful? Give feedback.
-
I guess the question is whether it's worth a language feature for the |
Beta Was this translation helpful? Give feedback.
-
Currently there is a very nice feature in C# that allows certain language patterns to be used on types that do not implement the typically required interfaces as long as those types have certain members defined on them like
void Dispose()
forIDisposable
-like type andEnumerator<T> GetEnumerator()
forIEnumerable<T>
-like type.This is very useful as sometimes it is not feasible to have types implement these interfaces, and yet there is a need to allow these types to be used in language statements like
foreach
.I recently ran across a slight limitation with
foreach
though because of how it works:gets rewritten by the compiler to:
And here lies the problem: there is no way to get to
IEnumerableLikeStructEnumerator
from theforeach
- it is completely hidden from the user by the compiler.In most cases this is not an issue, however for certain patterns it does become an issue, especially where performance is needed, and hence both the
IEnumerable<T>
-like type andIEnumerator<T>
-like type have to bestructs
. If these types wereclass
es it would not be a problem, because one could have a class that acts both asIEnumerable
andIEnumerator
:It works for
class
es because they are reference types, and sopublic GetEnumerator() => this;
returns the same instance. Howeverstruct
s which have to be used for performance are value types and sopublic GetEnumerator() => this;
will always create a new instance on the stack when the loweredforeach
code callsIEnumerableLikeStruct.GetEnumerator()
. This is always true regardless of whether thestruct
isreadonly struct
or aref struct
or both.So what is the problem with having 2 distinct objects when foreaching? The problem is that we cannot access members (fields, properties in particular) on the "Enumerator" that gets returned by
IEnumerableLikeStruct.GetEnumerator()
.A concrete example:
I was trying to write a pixel enumerator that will produce pixels going stride at a time vertically and pixel at a time horizontally. And I really wanted to simply write:
foreach (PixelPointer* p in MyPixelEnumerator) ...
, and that worked fine as long as I did not need to access the enumerator. However I often need to know which X and Y the enumerator is currently returning me a pixel pointer for. So, as part of pixel enumerator implementation I expose these as properties:So, unfortunately the above approach does not work, because the
X
andY
that get updated are updated on the invisible-to-the-caller instance and not on theMyPixelEnumerator
instance within theforeach (var p in MyPixelEnumerator)
statement.Workarounds:
There are two but I do not like either:
GetEnumerator()
, it return a new specialized "hacky" nested type that will contain a pointer to the original PixelEnumerator, so that it can be properly modified by the actual enumerator:The problem with this approach is two-fold:
Current => pit->Current;
andpit->MoveNext();
really do add an overhead and likely prevent inlining.foreach
and usewhile
instead, slightly modifying theMoveNext
:But again, this does not look as nice.
The Suggestion:
And so I think it would be nice to allow
foreach
ing over types that also satisfy theIEnumerator
pattern (i.e. haveT Current
andbool MoveNext()
) instead of allowing strictly those that satisfy theIEnumerable
pattern.Beta Was this translation helpful? Give feedback.
All reactions