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