故意依赖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;

我不确定是否有更简洁的方法来编写此代码?

即使是这样,这样做是不是不好呢?

故意依赖Linq副作用是不好的做法吗?

不,这并不是严格意义上的"坏习惯"(比如用用户输入的字符串连接构建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);