Reputation: 35925
Why an Enumerator
does not keep track of the item in the same function but not if the MoveNext
operation happens in other function ?
Example:
public static void Test()
{
var array = new List<Int32>(new Int32[] { 1, 2, 3, 4, 5 });
var e = array.GetEnumerator();
e.MoveNext();
e.MoveNext();
Console.WriteLine(e.Current); // 2
Incremenet(e);
Console.WriteLine(e.Current); //2
}
static void Incremenet(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
}
I was expecting to get 5 in the last CW, but I get 2, like it was never incremented. Why the MoveNext
inside the Increment
function are forgotten when the function returns?
Cheers.
Upvotes: 2
Views: 157
Reputation: 44285
For the same reason test
is 1 after increment in the following test case. This is normal behavior for a value type.
static void Main(string[] args)
{
int test = 1;
Increment(test);
Console.WriteLine("After increment: " + test);
}
static void Increment(int test)//add ref and the original variable will also update
{
test += 1;
Console.WriteLine(test);
}
As Servy pointed out technically, the example does differ in that the local variable test
is immutable. In reality the behavior we see is because the variable is copied to the Increment
method. However, my point is that this type of behavior is consistent across value types (both properties and local variables). For further evidence of this fact:
struct MutableStruct
{
public int EvilInt { get; set; }
}
class Program
{
static void Main(string[] args)
{
var testStruct = new MutableStruct();
testStruct.EvilInt = 1;
int test = 1;
Increment(test, testStruct);
Console.WriteLine("After increment: " + test + " and..." + testStruct.EvilInt);//both 1
}
static void Increment(int test, MutableStruct test2)
{
test2.EvilInt += 1;
test += 1;
Console.WriteLine(test + " and..." + test2.EvilInt);//both 2
}
}
As we can see here this behavior is normal across value types. In both the case of local immutable value types and mutable structs the behavior remains consistent.
Upvotes: 1
Reputation: 56536
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.
Upvotes: 6