List<T>
's enumerator type List<T>.Enumerator
is not a class
, but a struct
. Since GetEnumerator
exposes that the return type is List<T>.Enumerator
, when you use var
, e
's type is List<T>.Enumerator
, so when you pass it to Incremenet
, it is automatically boxed to be an IEnumerator<Int32>
object. This is the cause of the strange behavior you're seeing.
If you type e
as an IEnumerator<Int32>
, the boxing happens as soon as you get the object, so this strange behavior does not happen: it works the same whether you run the other code in Test
or in Increment
(I fixed the spelling on that method, by the way, it's not "Incremenet").
public static void Test()
{
var array = new List<Int32> { 1, 2, 3, 4, 5 };
IEnumerator<Int32> e = array.GetEnumerator(); // boxed here
e.MoveNext();
e.MoveNext();
Console.WriteLine(e.Current); // 2
Increment(e);
Console.WriteLine(e.Current); // now it's 4
}
static void Increment(IEnumerator<Int32> e)
{
Console.WriteLine("Inside " + e.Current); // 2
e.MoveNext();
Console.WriteLine("Inside " + e.Current); // 3
e.MoveNext();
Console.WriteLine("Inside " + e.Current); // 4
}
It is exposed as its type instead of IEnumerator<T>
for performance reasons. foreach
is smart enough to call MoveNext
and Current
without boxing or virtual dispatch in such a case, and handles value type semantics without a problem. It does cause confusion, as you've seen, when you don't take great care of how you handle it though, since mutable struct
s are evil.