Question

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.

Was it helpful?

Solution

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 structs are evil.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top