您的位置:首页 > 编程语言 > C#

C#_深入理解Linq的延迟执行

2018-03-27 14:14 330 查看

    一、Linq简介

    Linq的查询三部曲:一、获取数据源;二、创建查询;三、执行查询。

    二、延迟查询带来的好处

    延迟查询有什么好处呢?请看下面一段代码: private static void Main()
{
var list = new List<int>();
var rd = new Random();
for (var i = 0; i < 100000000; i++)
{
var num = rd.Next(-10000000, 10000000);
list.Add(num);
}
            RecordingTime(list);
        }
private static void RecordingTime(IEnumerable<int> list)
{
var watch = new Stopwatch();
watch.Start(); //开始计时

var nums1 = list.Where(n => n > 0);

var nums2 = new List<int>();
foreach (var i in list)
{
if (i > 0)
{
nums2.Add(i);
}
}
            watch.Stop(); //停止计时
var time = watch.ElapsedMilliseconds;
Console.WriteLine($"耗时:{time}毫秒");
}
    大家可以对比一下num1和num2的运行效率,一亿条数据中,使用Linq求集合中的正数耗时仅为2毫秒(可能会有十几毫秒的误差);而使用普通方法(遍历加判断)耗时却达到了2秒多。效率提升近2000倍!
    Linq的效率为什么会这么快呢?
    这是因为Linq在上面的代码中仅仅做了两个工作:1、获取数据源,2、创建查询;而使用普通方法不光做了这两个工作,它还多做了执行查询工作,因此Linq在此处运行效率奇高。

    三、延迟执行会有什么影响

    用代码说话
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
var enumList = list.Where(x => x > -4);
foreach (var num in enumList)
{
Console.WriteLine(num);
}
Console.WriteLine("■■■■■■■■■■■■");
list.Add(-1);
foreach (var num in enumList)
{
Console.WriteLine(num);
}
    此处我上下遍历的enumList是同一个集合,尽管我在遍历完第一次的时候改变了List,正常来讲我上下两次遍历输出的内容应该是一致的,然而结果却不尽然。



    刚刚提到Linq仅仅做了两部工作:1、获取数据源,2、创建查询。那么执行查询是在何时何处被执行的呢?答案就是:当enumList被用到的时候才会去执行,也就是说这段代码中,var enumList = list.Where(x => x > -4);这行代码被执行了两次。第一次执行的时候是第一次遍历enumList的时候,第二次执行的时候是第二次遍历enumList的时候(大家可以打断点观察一下)。   

    四、延迟执行的原理

    继续上代码
private static void Main()
{
            var list = new List<int> { -1, 1, 2, 3, 4, 5 };
var list1 = Delay(list);
foreach (var num in list1)
{
Console.WriteLine(num);
}
list.Add(-3);
list.Add(7);
Console.WriteLine("■■■■■■■■■■■■");
foreach (var num in list1)
{
Console.WriteLine(num);
}
        }
private static IEnumerable<int> Delay(IEnumerable<int> list)
{
foreach (var num in list)
{
if (num>0)
{
yield return num;
}
}
}
    其实Linq的延迟执行原理很简单,如果你理解yield return,那么你就会明白这两者其实原理是一模一样的。


    可以发现这段代码跟上面的那段代码运行模式是一样的,当你将断点打在var list1 = Delay(list);时,单步调试是不会进入Delay方法的,仅当你去遍历list1的时候它才会去执行Delay方法,而且是你用几次list1,它就会去执行几次Delay方法。yield return与return不同之处就在于它是“按需供给”,即你什么时候用它就什么时候(执行)返回给你值。
    之前说到延迟执行可能会影响结果,那么当我多次使用list1时,Delay这个方法就会多次执行,程序的运行效率是否会非常低呢?看一下我的测试,一万个整形数据打印七次。    yield return(模拟延迟执行):




直接打印:



    普通打印的运行时间甚至更低(因为普通打印是打印所有数字,Delay方法过滤掉负数了)!由此,可以看出:当延迟执行的结果被多次调用时,并不会影响运行效率。

    五、哪些Linq查询操作符是延迟执行的

    转到Linq查询操作符的定义看一下




    当返回值为IEnumerable<TSource>、IEnumerable<IGrouping<TKey, TSource>>和IOrderedEnumerable<TSource>的时候,Linq为延迟执行。实际上上述三个返回值类型都实现了IEnumerable<T>(公开枚举数)这个接口,yield return的返回值就是IEnumerable<T>,所以,当Linq查询操作符的返回值为上述三个类型时,查询为延迟查询,其他为立即执行。

    六、如何避免延迟执行

    可以看到延迟执行既有好处又有一定的影响,当延迟执行会对自己的程序产生影响时,如何去避免这个影响呢。很简单,将返回值转换为另外一种类型即可,Linq的查询操作符中包含了转换操作的定义:ToArray(转换为数组)、ToDictionary(转换为字典)、ToList(转换为集合),使用这些转换运算就可以将延迟执行转换为立即执行,也就可以避免延迟执行带来的影响了。

本人的第一篇博文,初学C#不到3个月,写得不好的地方请在评论区指出,我会及时修正。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C# Linq 延迟执行