枚举器和函数范围

时间:2013-10-30 16:33:25

标签: c# ienumerable ienumerator

为什么Enumerator不会跟踪同一函数中的项目,但如果MoveNext操作在其他函数中发生则不会跟踪?

示例:

    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
    }

我期望在最后一个CW中获得5分,但是我得到2分,就像它从未增加过一样。当函数返回时,为什么忘记了MoveNext函数中的Increment

干杯。

2 个答案:

答案 0 :(得分:6)

List<T>的枚举器类型List<T>.Enumerator不是class,而是struct。由于GetEnumerator公开了返回类型为List<T>.Enumerator,因此当您使用var时,e的类型为List<T>.Enumerator,因此当您将其传递给{{ 1}},它自动boxed成为Incremenet个对象。这就是你所看到的奇怪行为的原因。

如果您将IEnumerator<Int32>键入为e,则只要您获得对象就会发生装箱,因此不会发生这种奇怪的行为:无论您是否运行{中的其他代码,它的工作方式都相同{1}}或IEnumerator<Int32>(我修正了该方法的拼写,顺便说一下,它不是“Incremenet”)。

Test

出于性能原因,它被公开为类型而不是Incrementpublic 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 } is smart enough在这种情况下无需装箱或虚拟调度即可调用IEnumerator<T>foreach,并且可以毫无问题地处理值类型语义。正如你所看到的那样,它确实引起了混乱,因为mutable structs are evil已经没有照顾好你的处理方式。

答案 1 :(得分:1)

出于同样的原因,test在以下测试用例中递增后为1。这是值类型的正常行为。

    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);
    }

正如Servy在技术上指出的那样,该示例的不同之处在于局部变量test是不可变的。实际上,我们看到的行为是因为变量被复制到Increment方法。但是,我的观点是这种行为在值类型(属性和局部变量)之间是一致的。有关这一事实的进一步证据:

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
    }
}

正如我们在这里看到的,这种行为在值类型中是正常的。在局部不可变值类型和可变结构的情况下,行为保持一致。