代码改变世界

【More Effective C#】延迟求值

2010-10-14 18:30  空逸云  阅读(927)  评论(5编辑  收藏  举报

LinQ查询时,我们不会立即得到返回的数据并生成一个序列.定义该查询只是为了得到查询结果所要的执行的步骤,只有开始迭代查询时,查询才会真正的执行.这样,每次执行查询,都会从头开始执行查询的每个步骤,每次遍历/迭代都会得到全新的结果.这就叫延迟求值.相反.很多时候我们要所见即所得.定义什么查询都得到唯一的数据列表.这叫主动求值.

理解延迟查询.举个不太恰当的例子.如C#的Const.我们定义了一个常量.编译时其实都是常量值.是不变的.这就像主动求值.而如果是用变量.则每次变量的内容都不同.这是把这个"变量"(得到查询结果的步骤)插入到代码之中而已.直到真正迭代调用的时候才执行该步骤,从而得到结果.

 private static IEnumerable<TResult> Generate<TResult>(int number, Func<TResult> generator)

        {

            for (int i = 0; i < number; i++)

                yield return generator();

        }

        private static void LazyEvaluation()

        {

            Console.WriteLine("Start time for Test One:{0}"DateTime.Now);

            var sequence = Generate(10, () => DateTime.Now);

            Console.WriteLine("Waiting...\tPress Return");

            Console.ReadLine();

            Console.WriteLine("Iterating...");

            foreach (var value in sequence)

                Console.WriteLine(value);

            Console.WriteLine("Waiting...\tPress Return");

            Console.ReadLine();

            Console.WriteLine("Iterating...");

            foreach (var value in sequence)

                Console.WriteLine(value);

        }

 

上面这段代码生成了一个序列.迭代该序列2次.可以看到.2次生成的数据不同..这也说明.序列1与序列2共享的是其构造方式,而不是数据.若要主动求值.只需要

var sequence = Generate(10, () => DateTime.Now).ToList();

随后你可以发现2次生成的数据是一致的. 

 

由于延迟求值的本质,查询表达式可以操作在无限长度的序列之上.

private IEnumerable<int> AllNumbers()

        {

            int number = 0;

            while (number < int.MaxValue)

                yield return number++;

        }

        public void DisplayMaxNum()

        {

            var answer = from num in AllNumbers()

                         select num;

            var smallNumbers = answer.Take(10);

            foreach (var num in smallNumbers)

                Console.WriteLine(num);

        }

实验后可以知道.这段程序的执行速度非常快.因为它实际上只获取前10个数据.而如果是主动求值.则必须要先得到int.MaxValue的所有值之后再获取前10个元素.性能可想而知.

如果序列的长度可能为无限的话,那么则必须避免使用需要整个序列的方法,采用延迟查询能很完美的解决.如果序列并不是无限的.但有需要过滤的条件.则应该把过滤的条件放在最前执行.

var sortedProductsSlow =

                from p in products

                orderby p.UnitsInStock descending

                where p.xx > 10

                select p;

var sortedProductsSlow =

                from p in products

                where p.xx > 10

                orderby p.UnitsInStock descending

                select p;

第一种实现将比第二种实现有效率,不过在Linq to object的实现中.所有的产品都会被读取出来再排序.所以两者并没有区别.这主要是Linq to object和 Linq to sql的内部实现不同(将会在后面的lambda中介绍).

利用延迟查询,我们可以创造出更灵活的代码.更简洁的实现.所以.除非特别有必要使用主动求值,否则最好使用延迟求值.