C#的惰性枚举

Ruby 2.0有一个新的特性是惰性枚举器,Soi Mort 的博客举了一个例子:可以将下面的代码

File.open(path) {|fp|
    fp.each_line. \
    select {|line| # 生成了临时数组
    /regexp/ =~ line
    }. \
    each_with_index.map {|line, no| # 生成了临时数组
    sprintf("%d: %s\n", no, line)
    }. \
    first(10).each {|str| # 生成了临时数组
        puts(str)
    }
}

转换为

File.open(path) {|fp|
    fp.each_line.lazy \
    select {|line| # 没有临时数组产生
    /regexp/ =~ line
    }. \
    each_with_index.map {|line, no| # 没有临时数组产生
    sprintf("%d: %s\n", no, line)
    }. \
    first(10).each {|str| # 没有临时数组产生
        puts(str)
    }
} # 甚至在到达EOF之前都不读取数据

这样来避免产生多余的临时对象。这里谈到了惰性枚举,其实这个概念并不算太新鲜,在.NET引以为傲的Linq中,惰性枚举其实越来越重要。

初学C#的时候其实并不容易搞清楚所谓的IEnumerable和IEnumerator,有个时候就糊弄一下觉得大多数情况很少手工操作迭代器和枚举器,用一个foreach就巧妙的解决并自鸣得意。但是看了《CLR via C#》以及一些关于C#的案例和图书似乎都很少出现foreach,有时候还纳闷特么这些人是蠢的么...当然,后来发现foreach的实现方式导致其本身效率是不高的所以...。

回头看.NET的IEnumerable接口:

public interface IEnumerable
{
    //
    // Methods
    //
    [DispId (-4)]
    IEnumerator GetEnumerator ();
}

这个接口只需要实现一个GetEnumerator的方法,非常简洁。

IEnumerator接口:

public interface IEnumerator
{
    //
    // Properties
    //
    object Current {
        get;
    }

    //
    // Methods
    //
    bool MoveNext ();

    void Reset ();
}

于是我们便可以实现一个仅能duang出来一个的“列表”:

class OnlyOne : IEnumerable, IEnumerator
{
    public IEnumerator GetEnumerator () => this;
    public object Current => "caocaoda";
    public bool MoveNext () => false;
    public void Reset () {}
}

如果把false改为true那就可以一直艹艹哒啦。

但是这样的话,还是很麻烦,毕竟要我们手工实现,说好的C#简单呢...所以M$引入了一个迭代器,用以实现IEnumerable/IEnumerator。

class OnlyOne : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        Int32 value = 0;
        do {
            yield return value++;
        } while (false);
    }
}

省事太多,通过DILASM可以看到其实编译器帮我们实现了前面我们自己写的方法。

通过IL不难看出,其实MoveNext()是一个Switch...

废话那么多回到惰性枚举上来,其实我们发现,IEnumerable和IEnumerator两个接口的实现其实是惰性的,也就是在需要的时候才会获取数据,而不会产生临时的数据,就像前面Ruby一样,使用迭代器不会产生额外的开销。如果我们把false改成了true,还没有“惰性”那玩意儿可够呛...

为什么说Linq其实很依赖惰性枚举呢...举个例子:

public static IEnumerable Take (Int32 much, IEnumerable s)
{
    for (int i = 0; i < much; i++) {
        yield return s [i];
    }
}

我们就可以实现一个在数据源中抓much个元素的方法了。

你在说什么?

其实我就是打算复习一下迭代器而已...

posted @ 2015-05-19 00:19 Johnwii 阅读(...) 评论(...) 编辑 收藏