故意依赖Linq副作用是不好的做法吗?
本文关键字:依赖 Linq 副作用 故意 | 更新日期: 2023-09-27 18:07:48
这样的编程模式经常出现:
int staleCount = 0;
fileUpdatesGridView.DataSource = MultiMerger.TargetIds
.Select(id =>
{
FileDatabaseMerger merger = MultiMerger.GetMerger(id);
if (merger.TargetIsStale)
staleCount++;
return new
{
Id = id,
IsStale = merger.TargetIsStale,
// ...
};
})
.ToList();
fileUpdatesGridView.DataBind();
fileUpdatesMergeButton.Enabled = staleCount > 0;
我不确定是否有更简洁的方法来编写此代码?
即使是这样,这样做是不是不好呢?
不,这并不是严格意义上的"坏习惯"(比如用用户输入的字符串连接构建SQL查询或使用goto
)。
有时这样的代码比几个查询/foreach
或无副作用的Aggregate
调用更具可读性。此外,至少尝试编写foreach
和无副作用版本,看看哪一个更可读/更容易证明正确性,这是一个好主意。
请注意:
- 通常很难推断这样的代码会发生什么/什么时候发生。也就是说,你可以在
.ToList()
调用时延迟执行LINQ查询,否则该值将不会计算。 - 纯函数可以并行运行,一旦有副作用需要非常小心这样做
- 如果你需要将LINQ-to-Object转换为LINQ-to-SQL,你必须重写这样的查询 一般来说,LINQ查询倾向于没有副作用的函数式编程风格(因此,按照惯例,读者不会期望代码中出现副作用)。
为什么不这样写呢:
var result=MultiMerger.TargetIds
.Select(id =>
{
FileDatabaseMerger merger = MultiMerger.GetMerger(id);
return new
{
Id = id,
IsStale = merger.TargetIsStale,
// ...
};
})
.ToList();
fileUpdatesGridView.DataSource = result;
fileUpdatesGridView.DataBind();
fileUpdatesMergeButton.Enabled = result.Any(r=>r.IsStale);
我认为这是一个不好的做法。您假设lambda表达式因为调用了ToList而被强制执行。这是当前版本ToList的实现细节。如果。net 7中的ToList。x更改为返回一个对象,该对象半惰性地转换IQueryable?如果改成并行运行呢?突然之间,您的staleCount上出现了并发问题。据我所知,这两种情况都有可能破坏你的代码,因为你的代码做出了错误的假设。
现在就重复调用multimerge。getmerge与单个id,这真的应该被重新设计为一个连接,因为做连接的逻辑(w|c)会比你在那里编码的更有效,并且会更好地扩展,特别是如果实现multimerge实际上是从数据库中提取数据(或者可能被更改为这样做)。
在将ToList()传递给Datasource之前调用它,如果Datasource没有使用新对象中的所有字段,那么跳过ToList并让数据源只提取它需要的字段将会(更快)并且占用更少的内存。您所做的是将数据与视图的确切需求高度耦合,应该尽可能避免这种情况。例如,如果您突然需要显示存在于FileDatabaseMerger中的字段,但不在当前的匿名对象中,该怎么办?现在你需要同时修改控制器和视图来添加它,如果你只传入一个IQueryable,你只需要修改视图。同样,更快,内存更少,更灵活,更易于维护。
希望这对你有帮助。这个问题应该在代码审查中发布,而不是在stackoverflow上。
进一步检查更新,下面的代码会更好:
var result=MultiMerger.GetMergersByIds(MultiMerger.TargetIds);
fileUpdatesGridView.DataSource = result;
fileUpdatesGridView.DataBind();
fileUpdatesMergeButton.Enabled = result.Any(r=>r.TargetIsStale);
或
var result=MultiMerger.GetMergers().Where(m=>MultiMerger.TargetIds.Contains(m.Id));
fileUpdatesGridView.DataSource = result;
fileUpdatesGridView.DataBind();
fileUpdatesMergeButton.Enabled = result.Any(r=>r.TargetIsStale);