Is this a bug, or is there another method? The doc remarks of the method already state that it should not be used directly, but what syntax sugar does the C# compiler do to make it work?
The foreach loop in C# is based on type shapes. The object to be enumerated doesn’t have to implement IEnumerable, it only needs to have a public GetEnumerator method which in turn returns an object following the enumerator pattern (Current, MoveNext).
We don’t support the concept of type shapes, our loop takes an object which needs to implement IEnumerable which the Span does not. We would need to find an idea how to model such type shapes in our world. So for now you’ll need to either make a copy (like mentioned by @nissidis) or operate on the span with C# which is easy enough to do in VL.
Yes, I’ve tried to avoid a copy because it will be about 40k elements, but here is a vibe coded process node that re-uses an array:
namespace Main
{
/// <summary>
/// Generic helper node that copies a ReadOnlySpan<T> into a cached array.
/// The array is only reallocated when the input span length changes.
/// This avoids repeated allocations when the span contents change but the count remains constant.
/// </summary>
[ProcessNode]
public class SpanToArray<T>
{
// Cached array instance. Never null.
private T[] _cached = Array.Empty<T>();
// Last observed count. Initialized to -1 so first call will allocate appropriately.
private int _lastCount = -1;
/// <summary>
/// Copy the provided ReadOnlySpan<T> into a cached array and return it.
/// The returned array is owned by this node and may be reused on subsequent calls.
/// The array is re-created only when <paramref name="span"/>.Length != previously stored length.
/// </summary>
/// <param name="span">Input span to copy.</param>
/// <returns>An array containing the span's elements. The array is cached between calls.</returns>
public T[] Update(ReadOnlySpan<T> span)
{
int len = span.Length;
if (len != _lastCount)
{
// Recreate backing array only when length changes.
_cached = len == 0 ? Array.Empty<T>() : new T[len];
_lastCount = len;
}
if (len > 0)
{
// Copy the content into the cached array. Array implicitly converts to Span<T>.
span.CopyTo(_cached);
}
return _cached;
}
}
}