Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ref foreach with List

Tags:

c#

Why can't ref var be used within a foreach loop that iterates a List<T>?

Random rand = new();

// This is fine
Span<int> numbers = new int[] { 3, 14, 15, 92, 6 };
foreach (ref var number in numbers)
{
    number = rand.Next();
}

// This is not fine
List<int> nums = new() { 3, 14, 15, 92, 6 };
foreach (ref var number in nums)
{
    number = rand.Next();
}
like image 716
Dan Avatar asked Mar 31 '26 22:03

Dan


2 Answers

It's because the Span<T> is equipped with an enumerator that has a ref Current property, and the List<T> isn't.

Span<T>.Enumerator.Current property:

public ref T Current { get; }

List<T>.Enumerator.Current property:

public T Current { get; }

If you want, you can get access to the internal storage of a List<T> with the CollectionsMarshal.AsSpan<T> method.


Update: Actually as @GuruStron pointed out in a comment, the C# compiler doesn't even use the Span<T>.Enumerator struct, and instead it translates a foreach loop to a fast while loop. For example the code below:

foreach (ref int item in span)
{
    //...
}

...is translated to:

int i = 0;
while (i < span.Length)
{
    ref int item = ref span[i];
    //...
    i++;
}
like image 167
Theodor Zoulias Avatar answered Apr 02 '26 11:04

Theodor Zoulias


I found the following https://github.com/dotnet/csharplang/issues/1085 :

Proposal: Support Span and ReadOnlySpan in foreach, such that:

foreach (T item in span)
{
    ...
}

is equivalent to:

for (int i = 0; i < span.Length; i++)
{
    T item = span[i];
    ...
}

If it is implemented with a for-loop, it makes sense to handle the ref.

Also from the Span.Enumerator Struct Microsoft Documentation:

Unlike some other enumerator structures in .NET, the Span.Enumerator:

  • Does not implement the IEnumerator or IEnumerator interface. This is because Span.Enumerator is a ref struct.
like image 41
izlin Avatar answered Apr 02 '26 11:04

izlin