Could someone explain why this code is running in infinity loop? Why MoveNext()
return true
always?
var x = new { TempList = new List<int> { 1, 3, 6, 9 }.GetEnumerator() };
while (x.TempList.MoveNext())
{
Console.WriteLine("Hello World");
}
Could someone explain why this code is running in infinity loop? Why MoveNext()
return true
always?
var x = new { TempList = new List<int> { 1, 3, 6, 9 }.GetEnumerator() };
while (x.TempList.MoveNext())
{
Console.WriteLine("Hello World");
}
List<T>.GetEnumerator()
returns a mutable value type (List<T>.Enumerator
). You're storing that value in the anonymous type.
Now, let's have a look at what this does:
while (x.TempList.MoveNext())
{
// Ignore this
}
That's equivalent to:
while (true)
{
var tmp = x.TempList;
var result = tmp.MoveNext();
if (!result)
{
break;
}
// Original loop body
}
Now note what we're calling MoveNext()
on - the copy of the value which is in the anonymous type. You can't actually change the value in the anonymous type - all you've got is a property you can call, which will give you a copy of the value.
If you change the code to:
var x = new { TempList = (IEnumerable<int>) new List<int> { 1, 3, 6, 9 }.GetEnumerator() };
... then you'll end up getting a reference in the anonymous type. A reference to a box containing the mutable value. When you call MoveNext()
on that reference, the value inside the box will be mutated, so it'll do what you want.
For analysis on a very similar situation (again using List<T>.GetEnumerator()
) see my 2010 blog post "Iterate, damn you!".
IEnumerable<int>
?? @JonSkeet can you explain it more in simple words –
Basilius TempList
is IEnumerable<int>
, access to it just copies a reference. Acting on that reference will mutate the boxed value, and next time you copy a reference it will still refer to the same boxed value, so you see the change. Compare that with copying the value type value and mutating that copy - when you next copy the original value, you won't see the previous change. –
Maladminister TempList
is List<T>.Enumerator
, which is a value type. –
Maladminister foreach
in critical-path code, because each use requires allocating an Enumerator
." For this reason, I always assumed Enumerator
s were reference-types. If they are struct
s, does that mean that advice is incorrect? –
Nitriding List<T>.Enumerator
is a struct... but don't forget that it only helps if the compile-time type of the variable is List<T>
. If it's just IEnumerable<T>
, the GetEnumerator()
method has to return IEnumerator<T>
, so any value type enumerator will be boxed. –
Maladminister While the foreach
construct in C# and the For Each
loop in VB.NET are often used with types that implement IEnumerable<T>
, they will accept any type which includes a GetEnumerator
method whose return type provides a suitable MoveNext
function and Current
property. Having GetEnumerator
return a value type will in many cases allow foreach
to be implemented more efficiently than would be possible if it returned IEnumerator<T>
.
Unfortunately, because there is no means by which a type can supply a value-type enumerator when invoked from foreach
without also supplying one when invoked by a GetEnumerator
method call, the authors of List<T>
faced a slight trade-off of performance versus semantics. At the time, because C# did not support variable-type inference, any code using the value returned from List<T>.GetEnumerator
would have to declare a variable of type IEnumerator<T>
or List<T>.Enumerator
. Code using the former type would behave as though List<T>.Enumerator
was a reference type, and a programmer using the latter could be presumed to realize that it was a structure type. When C# added type inference, however, that assumption ceased to hold. Code could very easily end up using type List<T>.Enumerator
without the programmer knowing of that type's existence.
If C# were ever to define a struct-method attribute which could be used to tag methods which shouldn't be invokable on read-only structures, and if List<T>.Enumerator
made use of it, code such as yours could properly yield a compile-time error on the call to MoveNext
rather that yielding bogus behavior. I know of no particular plans to add such an attribute, however.
© 2022 - 2024 — McMap. All rights reserved.