• 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个元素的方法了。

    你在说什么?##

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

  • 相关阅读:
    深入浅出理解索引结构
    SQL数据库碎片检查DBCC SHOWCONTIG含义
    NHibernate和SqlImage
    C#中验证sql语句的方法 (SET PARSEONLY 与SET NOEXEC )
    Webbrowser控件判断网页加载完毕的简单方法
    如何将一个HTML页面嵌套在另一个页面中
    WCF REST Service: InstanceContextMode.PerCall 不管用,无法实现并发
    linux中修改ssh端口
    linux修改主机名(不重启)
    centos使用光盘作为本地的yum源
  • 原文地址:https://www.cnblogs.com/johnwii/p/4513440.html
Copyright © 2020-2023  润新知