天坑,这样一个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.* ...
随机推荐
- filebeat测试output连通性
在默认的情况下,直接运行filebeat的话,它选择的默认的配置文件是当前目录下的filebeat.yml文件. filebeat.yml文件内容 filebeat.inputs: - type: l ...
- GitLab基础知识
GitLab基本介绍 GitLab是利用Ruby on Rails一个开源的版本管理系统,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目. 与Github类似,GitLab ...
- 跳转控制语句break
执行某些循环时,当满足了某个条件,使其提早退出循环,便可以使用break跳出循环 流程图如下: 其他循环均可以以此类推 例子:
- PAT (Basic Level) Practice 1004 成绩排名 分数 20
读入 n(>0)名学生的姓名.学号.成绩,分别输出成绩最高和成绩最低学生的姓名和学号. 输入格式: 每个测试输入包含 1 个测试用例,格式为 第 1 行:正整数 n 第 2 行:第 1 个学生的 ...
- 自学Spring
Spring官网地址https://spring.io/ springManven官网地址:https://mvnrepository.com------------------------ spri ...
- vue中的v-if查找数组中最后一个,给他加上新的样式
vue: var app=new Vue({ el:".xiaomi", data: { typeInfo: [{img:"image/type/phone_1.webp ...
- Java学习之路:流程控制
2022-10-11 10:58:41 前言 本文开始流程控制方面的学习,主要包括用户交互和流程控制语句,适合新手学习. 1 用户交互Scanner 1.1 Scanner对象 Java提供了一个可以 ...
- Codeforces 1670 E. Hemose on the Tree
题意 给你个数p,n = 2^p: 有一棵树有n个节点,告诉你怎么连边: 每个点有个权值,每条边也有个权值,权值需要自行分配,[1,2,3..n...2n-1],总共2n-1个权值: 你需要选一个节点 ...
- [Android开发学iOS系列] Auto Layout
[Android开发学iOS系列] Auto Layout 内容: 介绍什么是Auto Layout. 基本使用方法 在代码中写约束的方法 Auto Layout的原理 尺寸和优先级 Auto Lay ...
- xlwings 模块总结
基本使用 在子线程中使用时,有时需要在子线程函数中加入以下.有时不需要加入,目前还不明白具体的原因 import pythoncom # 导入的库 pythoncom.CoInitialize() # ...