枚举lambda不会正确绑定范围?

时间:2014-03-05 14:14:49

标签: c# lambda mono yield

考虑以下C#程序:

using System;
using System.Linq;
using System.Collections.Generic;

public class Test
{
    static IEnumerable<Action> Get()
    {
        for (int i = 0; i < 2; i++)
        {
            int capture = i;
            yield return () => Console.WriteLine(capture.ToString());
        }
    }

    public static void Main(string[] args)
    {
        foreach (var a in Get()) a();
        foreach (var a in Get().ToList()) a();
    }
}

在Mono编译器下执行时(例如Mono 2.10.2.0 - 粘贴到here),它会写入以下输出:

0
1
1
1

这对我来说似乎完全不合逻辑。当直接迭代yield函数时,for循环的范围是“正确地”(根据我的理解)使用的。但是当我首先将结果存储在列表中时,范围始终是最后一个操作?!

我可以假设这是Mono编译器中的一个错误,还是我遇到了C#的lambda和yield-stuff的神秘角落?

顺便说一句:当使用Visual Studio编译器(以及MS.NET或单声道执行)时,结果是预期的0 1 0 1

2 个答案:

答案 0 :(得分:1)

我会告诉你原因0 1 1 1

foreach (var a in Get()) a();

在这里你进入Get并开始迭代:

i = 0 => return Console.WriteLine(i);

yield返回函数并执行函数,向屏幕打印0,然后返回Get()方法并继续。

i = 1 => return Console.WriteLine(i);

yield返回函数并执行函数,将1打印到屏幕,然后返回Get()方法并继续(仅发现它必须停止)。

但是现在,你没有迭代每个项目,你正在构建一个列表,然后迭代该列表。

foreach (var a in Get().ToList()) a();

你正在做的不像上面那样,Get().ToList()返回一个列表或数组(不确定是哪一个)。所以现在发生这种情况:

i = 0 => return Console.WriteLine(i);

在你Main()函数中,你在内存中得到以下内容:

var i = 0;
var list = new List
{
    Console.WriteLine(i)
}

您将返回Get()函数:

i = 1 => return Console.WriteLine(i);

返回Main()

var i = 1;
var list = new List
{
    Console.WriteLine(i),
    Console.WriteLine(i)
}

然后呢

foreach (var a in list) a();

将打印出1 1

似乎忽略了你确保在返回函数之前封装了值。

答案 1 :(得分:0)

@Armaron - .ToList()扩展返回类型为T的List,因为命名约定意味着ToArray()返回T [],但我认为你的回答是正确的。

这听起来像编译器的一个问题。我同意Servy认为这可能是一个错误,但是,您是否尝试过以下操作?

public class Test
{
    private static int capture = 0;    

    static IEnumerable<Action> Get()
    {
        for (int i = 0; i < 2; i++)
        {
            capture++;
            yield return () => Console.WriteLine(capture.ToString());
        }
    }
}

此外,您可能想要尝试静态方法,也许这将执行更准确的转换,因为您的函数是静态的。

List<T> list = Enumerable.ToList(Get()); 

当调用ToList()时,似乎它没有为每个值执行单次迭代,而是:

return new List<T>(Get());

除非您需要向List对象添加/删除其他操作,否则代码中每个代码的第二个对我来说在实现中没有意义,除非您需要执行其他操作,否则它将是必要的或有益的。第一个是完美的意义,因为你所做的只是遍历对象并执行相关的操作。我的理解是,在转换期间通过执行整个迭代来计算静态IEnumerbale对象范围内的整数,并且该操作由于范围而将int保留为静态int。另外,请记住,IEnumerable只是一个由List实现的接口,它实现了IList,并且可能包含内置转换的逻辑。

话虽如此,我有兴趣看到/听到您的发现,因为这是一篇有趣的帖子。我肯定会提出这个问题。如果我说的任何内容需要澄清或者有些事情是假的,请提出问题,尽管我对使用IEnumerable的yield关键字有信心,但这是一个独特的问题。