35

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");
}
Dariusz Woźniak
  • 9,640
  • 6
  • 60
  • 73
ivamax9
  • 2,601
  • 24
  • 33

2 Answers2

41

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!".

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 3
    I'm looking at this and having trouble figuring out which element here is a value type for which the copy/reference distinction actually makes a difference. – Mason Wheeler Jul 23 '15 at 15:52
  • 1
    i didnt got it, how it works fine if we cast it to ``IEnumerable``?? @JonSkeet can you explain it more in simple words – Ehsan Sajjad Jul 23 '15 at 16:05
  • 1
    @EhsanSajjad: When the compile-time type of `TempList` is `IEnumerable`, 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. – Jon Skeet Jul 23 '15 at 16:13
  • 3
    @MasonWheeler: The compile-time type of `TempList` is `List.Enumerator`, which is a value type. – Jon Skeet Jul 23 '15 at 16:13
  • 1
    I often hear _"Don't use `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? – BlueRaja - Danny Pflughoeft Jul 23 '15 at 23:35
  • @BlueRaja-DannyPflughoeft: It can be, yes - but it depends on the implementation. That performance reason is precisely *why* `List.Enumerator` is a struct... but don't forget that it only helps if the compile-time type of the variable is `List`. If it's just `IEnumerable`, the `GetEnumerator()` method has to return `IEnumerator`, so any value type enumerator will be boxed. – Jon Skeet Jul 24 '15 at 05:46
3

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.

supercat
  • 77,689
  • 9
  • 166
  • 211