Linq.Select方法拼接字符串出现多个问题研究,以及Sel
引子
最近在做公司的微信公众号,其中一个页面要展示从平台接口获取到的用户头像,而平台接口返回的图片链接是不包含服务器地址的,类似这种:/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次,输出结果如下:

每次输出都会在上一次的基础上添加一个“Mr.”。
源码查看
找到Linq Select方法的源代码,其实现了IEnumerator接口,每次使用数据时都会执行Ienumerator中的MoveNext方法,具体实现如下:

其中
_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();
执行结果如下:

输出结果正是我们所预期的。
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查看。