前几天,一位网友跟我说他编写的一段很简单的代码遇到了奇怪的Bug,他要达到的效果是从一个List中随机取出来一条数据,代码如下:

1 var random = new Random();
2 var users = Enumerable.Range(0, 10).Select(p => new User(p, "A" + p)).ToList();
3 var user = users.Find(p => p.Id == random.Next(0, 10));
4 Debug.Assert(user != null);
5
6 record User(int Id,string Name);

第2行代码生成了一个包含10个User对象的List,这些User的Id值从0递增到9;第3行代码中调用List的Find方法来根据lambda表达式来查找一条数据,这里通过random.Next()来获取一个[0,10)之间的随机数,然后用这个随机数来和Id进行比较。按照逻辑来讲,Find一定可以找到一条数据,所以在第4行代码中断言user一定不为null。但是这段代码有的时候运行正常,有的时候则会断言失败,从而程序抛出异常,令人不解。

当然,他的这段代码写的过于复杂,其实改成users[random.Next(0, 10)]就简单又高效。但是为了揭示问题的本质,我这里继续分析为什么用Find+lambda方法会出现问题。

我们查看一下Find方法的源代码,如下:

public T? Find(Predicate<T> match)
{
for (int i = 0; i < _size; i++)
{
if (match(_items[i]))//注意这里
{
return _items[i];
}
}
return default;
}

Find方法的逻辑很简单,就是遍历List中的数据,对于每条数据都调用match这个委托来判断当前这条数据是否满足条件,如果找到一条满足条件的数据,就把它返回。如果走到最后都没有找到,就返回默认值(比如null)。这个逻辑简单到貌似看不到任何问题。

问题的关键就在if (match(_items[i]))这一句代码。它是在每一次循环都调用一下match的委托来判断当前数据的匹配性。而match指向的委托的方法体是p => p.Id == random.Next(0, 10),也就是每次匹配判断都要获取一个新的随机数来进行比较。假设在循环的时候生成的10个随机数为:9,8,8,7,9,1,1,2,3,4,那么就会每次match(_items[i])判断的结果都为false,从而导致最后返回null,也就是找不到任何的数据。

明白了原理之后,解决这个问题的思路就是不要在lambda中生成待比较的随机数,而是提前生成随机数,代码如下:

int randId = random.Next(0, 10);
var user = users.Find(p => p.Id == randId);

同样的原理也适用于Single()、Where()等LINQ操作。在这些操作中也要避免在lambda表达式中再进行复杂的计算,这样不仅可以避免类似这篇文章中提到的bug,而且可以提升程序的运行效率。

欢迎阅读我编写的《ASP.NET Core技术内幕与项目实战》,这本书的宗旨就是“讲微软文档中没有的内容,讲原理、讲实践、讲架构”。具体见右边公告。

最新文章

  1. Swagger .Net配置
  2. dubbo源码学习(一)之ExtensionLoader
  3. JQ的基本架构
  4. 强连通+二分匹配(hdu4685 Prince and Princess)
  5. 富文本HTML编辑器UEditor
  6. Airbnb创始人:屌丝的逆袭之路
  7. 【Node.js 自己封装的库 http_parse, libuv】
  8. 自定义UICollectionView
  9. 【转】int &amp;&amp; 非常量右值
  10. r.js合并实践 --项目中用到require.js做生产时模块开发 r.js build.js配置详解
  11. 测者的性能测试手册:Web压力测试工具webbench
  12. InstallShield Limited Edition for Visual Studio 使用
  13. erlang证书加密
  14. 关于java文件下载文件名乱码问题解决方案
  15. Java并发编程(四)synchronized
  16. JDBC(5)—DatabaseMetaData
  17. Android百度地图2.0运行定位到当前位置时“服务没有启动”
  18. Spring拦截器和过滤器
  19. canconfig 配置命令
  20. nodejs初印象

热门文章

  1. Logstash集成GaussDB(高斯DB)数据到Elasticsearch
  2. Linux实例常用内核网络参数介绍与常见问题处理---重要
  3. CentOS8本地安装Redash中文版,并且配置为生产环境
  4. Codeforces Round #822 (Div. 2) A-F
  5. spring boot集成redis基础入门
  6. python今日分享(内置方法)
  7. windows C++ 异常调用栈简析
  8. 在vue中的form表单中下拉框中的数据来自数据库查询到的数据
  9. java:找不到符号
  10. app自动化测试环境安装