【问题标题】:Enumerator and function scopes枚举器和函数范围
【发布时间】:2013-10-30 16:33:24
【问题描述】:

为什么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,就像它从未增加过一样。为什么函数返回时忘记了Increment函数里面的MoveNext

干杯。

【问题讨论】:

    标签: c# ienumerable ienumerator


    【解决方案1】:

    List&lt;T&gt; 的枚举类型List&lt;T&gt;.Enumerator 不是class,而是struct。由于GetEnumerator暴露了返回类型是List&lt;T&gt;.Enumerator,所以当你使用var时,e的类型是List&lt;T&gt;.Enumerator,所以当你将它传递给Incremenet时,它会自动成为boxed IEnumerator&lt;Int32&gt; 对象。这就是您看到的奇怪行为的原因。

    如果您将e 键入为IEnumerator&lt;Int32&gt;,则在您获得对象后立即发生装箱,因此不会发生这种奇怪的行为:无论您在Test 中运行其他代码还是在Increment(我修正了那个方法的拼写,顺便说一下,它不是“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
    }
    

    出于性能原因,它被公开为它的类型而不是IEnumerator&lt;T&gt;foreach is smart enough 在这种情况下调用 MoveNextCurrent 无需装箱或虚拟分派,并且可以毫无问题地处理值类型语义。正如你所看到的,当你没有特别注意如何处理它时,它确实会引起混乱,因为mutable structs are evil

    【讨论】:

    • 他们不得不将它暴露在外部。如果他们没有将它暴露在外部,那么它会总是被装箱,如果它总是被装箱,那么它也可能只是一个开始的类。其目的是它几乎总是被foreach 使用,这将与值类型语义一起正常工作。如果结构没有公开暴露,那么foreach 不能在不装箱的情况下取出枚举器。 (至少没有特殊的编译器支持,例如对数组的支持。)
    • @Servy 好点,好信息,我已经改变了那部分的答案。
    • @Servy 我仍然不清楚为什么它是 struct 而不是带有 sealed/non-virtual 内容的 class。这将使您生成几乎相同的 IL,我认为它具有相似的性能。
    • 我知道 Eric Lippert 表示已经进行了广泛的测试,并且该结构在“标准”用法中的性能明显更好,并且足以保证进行更改。
    • 我注意到您编辑了 exact post 我在写第一条评论时正在考虑的内容。现在我不太担心我无法快速找到它,因此没有发布链接。
    【解决方案2】:

    出于同样的原因,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
        }
    }
    

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

    【讨论】:

    • 这不是真的:您的测试失败,因为您正在为 test 分配一个新值,该值是按值传递的。这不是他对枚举器所做的事情。
    • @dcastro 结构体为何如此做的细节最好留给学习 C# 规范的人来练习。值类型的范围和邪恶的可变性是我认为在这里相关的。
    • 但仅此而已。您的示例演示了 immutable 值类型的语义。列表枚举器是一个 mutable 值类型。这个答案甚至没有提到这一点。
    • @Servy - 很公平。我澄清了答案。
    猜你喜欢
    • 2011-11-06
    • 1970-01-01
    • 1970-01-01
    • 2019-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-06
    相关资源
    最近更新 更多