天坑,这样一个lambda随机取数据也有Bug
前几天,一位网友跟我说他编写的一段很简单的代码遇到了奇怪的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技术内幕与项目实战》,这本书的宗旨就是“讲微软文档中没有的内容,讲原理、讲实践、讲架构”。具体见右边公告。
天坑,这样一个lambda随机取数据也有Bug的更多相关文章
- MySQL随机取数据
// 随机取9个 $rand_sql = "SELECT * FROM `tf_product` WHERE (`id` >= ((SELECT MAX(`id`) FROM `tf_ ...
- MySQL 随机取数据效率问题
本文详细解说了MySQL Order By Rand()效率优化的方案,并给出了优化的思路过程,是篇不可多得的MySQL Order By Rand()效率美文. 最近由于需要大概研究了一下MYSQL ...
- SQL 在表中随机取数据
在一张10万行产品表(Product)中,随机取10条数据的几种方式: SET STATISTICS IO ON SELECT TOP 10 ID FROM dbo.Product(NOLOCK) W ...
- mysql实现高效率随机取数据
从数据库中(mysql)随机获取几条数据很简单,但是如果一个表的数据基数很大,比如一千万,从一千万中随机产生10条数据,那就相当慢了,如果同时一百个人访问网站,处理这些个进程,对于一般的服务器来说,肯 ...
- Sql Server随机取数据
select top 10 * from tablename order by NEWID()
- oracle随机取数据
select * from (select rownum,KEYWORD, CATEGORY,CREATE_DATE,UPDATE_DATE from (select * from knet_keyw ...
- mysql 随机取数据
SELECT * FROM table WHERE id >= (SELECT FLOOR(RAND()*MAX(id)) FROM table ) ORDER BY idLIMIT 1; 这样 ...
- MySQL随机获取数据的方法,支持大数据量
最近做项目,需要做一个从mysql数据库中随机取几条数据出来. 总所周知,order by rand 会死人的..因为本人对大数据量方面的只是了解的很少,无解,去找百度老师..搜索结果千篇一律.特发到 ...
- 【MySQL】随机获取数据的方法,支持大数据量
在mysql中带了随机取数据的函数,在mysql中我们会有rand()函数,很多朋友都会直接使用,如果几百条数据肯定没事,如果几万或百万时你会发现,直接使用是错误的.下面我来介绍随机取数据一些优化方法 ...
- Oracle的trunc和dbms_random.value随机取n条数据
今天在review项目代码的时候看到这样一个问题,有一张号码表,每次需要从这样表中随机取6个空闲的号码,也就是每次取出来的6个号码应该都会有所不同.然后我就看到了这样的SQL select t.* ...
随机推荐
- Kibana控制台(Dev Tools) Console
控制台插件提供一个用户界面来和 Elasticsearch 的 REST API 交互.控制台有两个主要部分: editor ,用来编写提交给 Elasticsearch 的请求: response ...
- Elasticsearch:管理 Elasticsearch 内存并进行故障排除
文章转载自:https://elasticstack.blog.csdn.net/article/details/116974695
- 使用国内镜像源安装kubelet kubeadm kubectl
由于官网未开放同步方式, 可能会有索引gpg检查失败的情况, 这时请用 yum install -y --nogpgcheck kubelet kubeadm kubectl 安装 Debian / ...
- 在Portainer上管理其他docker主机(这只是其中一种方式),另一种方式看这个文档:使用Portainer管理其他主机的docker应用有两种方式
其他主机开启远程连接docker端口 需要设置一下2375端口的监听.通过修改docker配置文件方式进行监听. 修改配置文件修改监听端口 使用Centos7安装的docker,所以下面的配置是适用于 ...
- docker还原挂载在宿主机的mysql数据
ps:有个项目的数据库之前是docker的mysql镜像,数据都去mysql的镜像里面拿数据,但是目前镜像里面的数据库是空的,所以现在来说明一下如何恢复数据 1.创建数据卷 docker volume ...
- 5N的多次方
N=eval(input(" 请输入一个数:")) for i in range (5): print(pow(N,i))
- IDEA中直接将 SpringBoot项目打包成 Docker镜像时 pom.xml的配置
<plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactI ...
- 说说 Redis pipeline
更多技术文章,请关注我的个人博客 www.immaxfang.com 和小公众号 Max的学习札记. Redis 客户端和服务端之间是采用 TCP 协议进行通信的,是基于 Request/Respon ...
- 教你用canvas打造一个炫酷的碎片切图效果
前言 今天分享一个炫酷的碎片式切图效果,这个其实在自己的之前的博客上有实现过,本人觉得这个效果还是挺炫酷的,这次还是用我们的canvas来实现,代码量不多,但有些地方还是需要花点时间去理解的,需要点数 ...
- 3.CBV视图之csrf补充
CBV使用csrf装饰器关闭/开启 csrf验证,直接在函数上加装饰器无效的 #方法1 from django.views import View from django.views.decorato ...