为什么不消耗IEnumerable?/与python相比,c#中的生成器如何工作?

Rom*_*her 16 c# python iteration coroutine

所以我认为我理解c#yield return与pythons yield大致相同,我认为我理解.我认为编译器将一个函数转换为一个对象,该对象带有一个指向执行应该恢复的位置的指针,当对象的下一个值的请求运行到下一个yield时,它会更新指针的恢复执行位置并返回一个值.

在python中,它的工作方式类似于延迟评估,因为它根据需要生成值,但是一旦使用了值,如果不保存在另一个变量中,则可以使用gc.尝试迭代这样一个函数的结果两次返回一个空的iterable,除非你将它转换为一个列表.

恩.

def y():
    list = [1,2,3,4]

    for i in list:
        yield str(i)

ys = y()
print "first ys:"
print ",".join(ys)
print "second ys:"
print ",".join(ys)
Run Code Online (Sandbox Code Playgroud)

输出

first ys:
1,2,3,4
second ys:
Run Code Online (Sandbox Code Playgroud)

直到最近我才认为c#也是如此,但在dotnetfiddle中尝试失败了.

http://dotnetfiddle.net/W5Cbv6

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

public class Program
{
    public static IEnumerable<string> Y()
    {
        var list = new List<string> {"1","2","3","4","5"};
        foreach(var i in list)
        {
            yield return i;
        }
    }

    public static void Main()
    {


        var ys = Y();
        Console.WriteLine("first ys");
        Console.WriteLine(string.Join(",", ys));
        Console.WriteLine("second ys");
        Console.WriteLine(string.Join(",", ys));

    }
}
Run Code Online (Sandbox Code Playgroud)

输出

first ys
1,2,3,4,5
second ys
1,2,3,4,5
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?是缓存结果吗?它不可能是正确的,否则File.ReadLines会炸毁巨大的文件?它只是第二次从顶部重启功能吗?

注意:我对发电机和协同程序的一些术语有点不确定,所以我试图避免标记.

Ser*_*rvy 13

很近.An IEnumerable是能够创建迭代器(an IEnumerator)的对象.的IEnumerator行为完全按照你所描述.

所以IEnumerable 生成生成器.

除非您不遗余力地生成在生成的迭代器之间共享的某种状态,IEnumerator否则对象不会相互影响,无论它们是来自对迭代器块的单独调用还是IEnumerator由其生成的另一个IEnumerable.

  • 这是正确的答案,也许可以澄清一下:string.Join在IEnumerable变量ys上调用GetEnumerator。调用GetEnumerator生成新的生成器,因此每次调用string.Join都在处理新的生成器。 (2认同)

Qwe*_*y01 6

查看代码的每个部分后,我认为它与IEnumerable <>有关.如果我们查看MSDN,IEnumerable本身不是枚举器,但每次调用GetEnumerator()时它都会创建一个枚举器.如果我们查看GetEnumerator,我们会看到foreach(我想象string.Join)调用GetEnumerator(),每次调用它时都会创建一个新状态.举个例子,这里是使用枚举器的代码:

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

public class Program
{
    public static IEnumerable<string> Y()
    {
        var list = new List<string> {"1","2","3","4","5"};
        foreach(var i in list)
        {
            yield return i;
        }
    }

    public static void Main()
    {


        var ys = Y();
        Console.WriteLine("first ys");
        Console.WriteLine(string.Join(",", ys));
        IEnumerator<string> i = ys.GetEnumerator();
        Console.WriteLine(""+i.MoveNext()+": "+i.Current);
        Console.WriteLine(""+i.MoveNext()+": "+i.Current);
        Console.WriteLine(""+i.MoveNext()+": "+i.Current);
        Console.WriteLine(""+i.MoveNext()+": "+i.Current);
        Console.WriteLine(""+i.MoveNext()+": "+i.Current);
        Console.WriteLine(""+i.MoveNext()+": "+i.Current);
    }
}
Run Code Online (Sandbox Code Playgroud)

(dotnetfiddle)

当MoveNext到达结尾时,它具有预期的python行为.