Home > Net >  ref foreach with List
ref foreach with List

Time:08-13

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();
}

CodePudding user response:

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  ;
}

CodePudding user response:

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.

CodePudding user response:

See this article: https://docs.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay

emphasis added

The Span indexer takes advantage of a C# language feature introduced in C# 7.0 called ref returns. The indexer is declared with a “ref T” return type, which provides semantics like that of indexing into arrays, returning a reference to the actual storage location rather than returning a copy of what lives at that location

public ref T this[int index] { get { ... } }

The impact of this ref-returning indexer is most obvious via example, such as by comparing it with the List indexer, which is not ref returning. Here’s an example:

struct MutableStruct { public int Value; }
...
Span<MutableStruct> spanOfStructs = new MutableStruct[1];
spanOfStructs[0].Value = 42;
Assert.Equal(42, spanOfStructs[0].Value);
var listOfStructs = new List<MutableStruct> { new MutableStruct() };
listOfStructs[0].Value = 42; // Error CS1612: the return value is not a variable
  •  Tags:  
  • c#
  • Related