C#Asp.net开发.NET Core

Linq.Select方法拼接字符串出现多个问题研究,以及Sel

2018-06-17  本文已影响79人  Weidaicheng

引子

最近在做公司的微信公众号,其中一个页面要展示从平台接口获取到的用户头像,而平台接口返回的图片链接是不包含服务器地址的,类似这种:/img/headImg/xxx.png ,所以需要获取之后在图片链接前拼接服务器地址,首先想到的就是使用Select方法,但是调试的时候发现拼接之后图片链接中包含多个服务器地址,所以就研究了一下这个问题。

问题重现

编写如下代码:

static void Main(string[] args)
{
    var people = PeopleService.GetPeople();

    people = people.Select(p =>
    {
        p.Name = "Mr. " + p.Name;
        return p;
    });

    for (int i = 0; i < 3; i++)
    {
        Console.WriteLine(JsonConvert.SerializeObject(people)); 
    }
    Console.ReadKey();
}

其中,GetPeople 方法返回一个包含姓名和年龄的对象列表。
在Select方法中对姓名前添加“Mr.”。
在最后的for循环中,对people对象使用3次,输出结果如下:

select without to list.png
每次输出都会在上一次的基础上添加一个“Mr.”。

源码查看

找到Linq Select方法的源代码,其实现了IEnumerator接口,每次使用数据时都会执行Ienumerator中的MoveNext方法,具体实现如下:

MoveNext.png
其中 _selector 即我们传入的委托,在每次执行该方法的时候都会执行该委托,而且没有对源数据对象进行深复制,那么问题的原因就很明显了:我们一共执行了3次for循环每执行一次都会执行一次 p.Name = "Mr. " + p.Name;
至于该方法为什么这样编写,不添加对源数据的深复制,我的猜想如下:

性能问题。
如果在该方法中对对象进行深复制就只能使用反射或者是序列化,而这两种方式对性能都是有很大损耗的,就算是源数据类型实现了 ICloneable 接口,对性能损耗也是极大。
而且,单就此操作(在姓名前面添加“Mr.”)来看,Select的性能还不如foreach好,在后边的性能测试中会有具体时间。

解决方法

那么是不是意味着我们没有其他办法来避免上述问题了呢?
答案肯定是否定的,我们只要在Select之后添加 ToList() 方法就可以避免上述问题。
使用过Linq to Sql的同学应该很熟悉在Linq中添加ToList()方法会立即执行数据库查询,在Linq to object中也是同样的,上述Select方法在没添加ToList()方法的情况下不会对源数据产生任何修改,而且执行速度很快(在后边的性能测试中会有详细说明)。
修改代码如下:

people = people.Select(p =>
{
    p.Name = "Mr. " + p.Name;
    return p;
}).ToList();

执行结果如下:


select with to list.png

输出结果正是我们所预期的。

Linq.Select性能测试

之后仅针对上述操作(在姓名前添加“Mr.”)做一个简单的性能测试(包含linq、foreach、for),输出结果如下:

1条:
----------第1轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 0.008ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 0.0015ms

----------Select With For Start----------
----------Select With For End----------
        Use time: 0.0019ms

----------第2轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 0.0024ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 0.0017ms

----------Select With For Start----------
----------Select With For End----------
        Use time: 0.0053ms

----------第3轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 0.0053ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 0.0027ms

----------Select With For Start----------
----------Select With For End----------
        Use time: 0.0064ms
    
100条:
----------第1轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 0.0183ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 0.0078ms

----------Select With For Start----------
----------Select With For End----------
        Use time: 0.1241ms

----------第2轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 0.0093ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 0.0107ms

----------Select With For Start----------
----------Select With For End----------
        Use time: 0.1366ms

----------第3轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 0.0156ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 0.0093ms

----------Select With For Start----------
----------Select With For End----------
        Use time: 0.1207ms

10000条:
----------第1轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 0.8658ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 0.7258ms

----------Select With For Start----------
----------Select With For End----------
        Use time: 505.8625ms

----------第2轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 0.4633ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 0.356ms

----------Select With For Start----------
----------Select With For End----------
        Use time: 370.0292ms

----------第3轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 0.5515ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 0.5157ms

----------Select With For Start----------
----------Select With For End----------
        Use time: 364.456ms

1000000条:
----------第1轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 134.1061ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 195.2453ms

----------第2轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 196.3807ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 102.8961ms

----------第3轮----------
----------Select With Linq Start----------
----------Select With Linq End----------
        Use time: 167.6561ms

----------Select With Foreach Start----------
----------Select With Foreach End----------
        Use time: 176.2142ms

平均值:

1条:
    Linq:   0.0052ms
    Foreach:0.0019ms
    For:    0.0045ms
100条:
    Linq:   0.0144ms
    Foreach:0.0092ms
    For:    0.1271ms
10000条:
    Linq:   0.6268ms
    Foreach:0.5325ms
    For:    413.4492ms
1000000条:
    Linq:   166.0476ms
    Foreach:158.1185ms

在100万条数据内,foreach的执行速度都要胜于linq.select,for在100条以内还是比较快的,但是到10000条数据之后时间出现了暴涨,所以后边的100万条数据测试就没有测试for。
在上述测试中,linq中都添加了ToList方法,去掉ToList执行100万条数据平均值为 0.0087ms

End

本文所有源代码可在Github查看。

上一篇 下一篇

猜你喜欢

热点阅读