日期:2014-05-20  浏览次数:21079 次

C# 中 Linq查询结果的循环性能改进
对linq查询结果进行迭代时,linq才去执行查询,就是说{在迭代时,才真正执行}.这是延迟操作的一种性能提升的表现.

但试想下,需要对结果进行第2次,第3次...,那么这种延迟执行,却是性能的一个瓶颈.


测试如下代码:

        public IEnumerable<int> GetValue()
        {
            List<int> lst = new List<int> { 0, 1, 2 };
            return
                lst.Where
                (
                    i =>
                    {
                        Console.WriteLine(string.Format("where:  {0}", i));
                        return i % 2 == 1;
                    }
                ).Select(
                    i =>
                    {
                        Console.WriteLine(string.Format("return: {0}", i));
                        return i;
                    }
                );
        }

	
            var itor = this.GetValue();
            Console.WriteLine("------第一次查询--------");
            foreach (var i in itor) ; //第一次for
            Console.WriteLine("");

            Console.WriteLine("-----第二次查询---------");
            foreach (var i in itor) ; //第二次for
            Console.WriteLine("");

            Console.WriteLine("------tolist,第三次查询--------");
            var lst = itor.ToList();  //又执行了一次查询

            Console.WriteLine("");
            Console.WriteLine("------in list--------");
            foreach (var i in lst) ; //第三次for,总算没有再进行查询


输出结果,如下图:


得出一个结论:
查询结果返回后,马上调用 ToList() 方法,那么以后再循环时就不会再去执行查询了.
可从上面的代码看, ToList() 执行一次查询,遍历了所有元素,第三次for时,后遍历了一次(虽然不是所有元素).
试想下,集合中如果有100,1000,1万个元素时...


解决思路:
在循环迭代查询结果时,便将结果保存到一个结果集合中,下次循环迭代时,从这个结果集合中进行循环迭代

    class FixedEnumerable<T> : IEnumerable<T>
    {
        bool isFirst;
        T[] ary;
        public FixedEnumerable()
        {
            this.isFirst = true;
        }

        IEnumerable<T> Iterator;
        public FixedEnumerable(IEnumerable<T> iterator)
            : this()
        {
            this.Iterator = iterator;
        }

        #region IEnumerable<T> 成员

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            if (!this.isFirst)
            {
                foreach (var t in ary)
                    yield return t;
                yield break;
            }

            if (this.isFirst)
            {
                if (this.Iterator == null)
                {
                    this.isFirst = false;
                    yield break;
                }

                List<T> lst = new List<T>();
                foreach (var t in this.Iterator)
                {
                    lst.Add(t);
                    yield return t;
                }

                ary = lst.ToArray();
                this.isFirst = false;
                this.Iterator = null;
            }
        }

        #endregion

        #region IEnumerable 成员

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return (this as IEnumerable<T>).GetEnumerator() as System.Collections.IEnumerator;
        }

        #endregion
    }


测试代码如下:

	public IEnumerable<int> GetFixedValue()
        {
            return new FixedEnumerable<int>(this.GetValue());
        }

            var itor = this.GetFixedValue();
            Console.WriteLine("------第一次for,并查询--------");
            foreach (var i in itor)
            {
                Console.WriteLine(i);
            }
            Console.WriteLine("");

            Console.WriteLine("------第二次for,不再执行查询--------");
            foreach (var i in itor)
            {
                Console.WriteLine(i);
            }

测试结果如下图:



性能测试代码:

           

    public class User
    {
        public string Name
        {
            get;
            set;
        }

        public bool Sex
        {
            get;
            set;
        }

        public DateTime Birthday
        {
            get;
            set;
        }
    }


        public static IEnumerable<T> ToFixed<T>(this IEnumerable<T> iterator)
        {
            return new FixedEnumerable<T>(iterator);
        }


            List<User> lst = new List<.User>();
            for (int i = 0; i < 1; i++)
            {
                lst.Add(new Classes.User { Birthday = DateTime.Now, Name = "a" + i, Sex = false });
            }

            var r1 = from u in lst where u.Sex == false && u.Name.StartsWith("A".ToLower()) && u.Birthday <= DateTime.Now select new { u.Sex, u.Name };
            var r2 = r1.ToFixed();

            int times = 3;
            var sw = System.Diagnostics.Stopwatch.StartNew();
            sw.Start();
            for (int i = 0; i < times; i++)
            {
                //Console.WriteLine(i);
                foreach (var u in r1)
                {
                    var s = "a" + u.ToString();
                    s += "";
                }
            }
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
            sw.Reset();

            sw.Start();
            for (int i = 0; i < times; i++)
            {
                //Console.WriteLine(i);
                foreach (var u in r2)
                {
                    var s = "a" + u.ToString();
                    s += "";
                }
            }
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
            sw.Reset();

以损失第一次迭代性能,提高再次迭代性能.

如果小数据量,对于LINQ来说,无所谓性能!


而对于另一种方式,可以采用如下方法

        public static List<T> ToList<T>(this IEnumerable<T> iterator, Func<IEnumerable<T>, IList<object>> action)
        {
            var lst = action(iterator);
            return lst.Cast<T>().ToList();
        }


//ToLis时,第一次循环
                var r3 = r1.ToList(
                ie =>
                    {
                        var l = new List<object>();
                        foreach (var a in ie)
                        {
                            l.Add(a);
                        }
                        return l;
                    }
            );


//第二次循环
            sw.Start();
            for (int i = 0; i < times; i++)
            {
                //Console.WriteLine(i);
                foreach (var u in r3)
                {
                    var s = "a" + u.ToString();
                    s += "";
                }
            }
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
            sw.Reset();