故意依赖 Linq Side Effects 是不好的做法吗?

Is it bad practice to purposely rely on Linq Side Effects?

像这样的编程模式经常出现:

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;

我不确定是否有更简洁的编码方式?

即使是这样,这样做是不好的做法吗?

为什么不直接这样编码:

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 中的 ToList 7.x 更改为 return 半延迟转换 IQueryable 的对象怎么办?如果它并行更改为 运行 lambda 怎么办?突然之间,您的 staleCount 出现了并发问题。据我所知,由于您的代码所做的错误假设,这两种可能性都会破坏您的代码。

现在,就使用单个 id 重复调用 MultiMerger.GetMerger 而言,确实应该将其重新设计为一个连接,因为执行连接 (w|c) 的逻辑比您的效率高得多在那里编码并且会更好地扩展,特别是如果 MultiMerger 的实现实际上是从数据库中提取数据(或者可能会更改为这样做)。

就在将 ToList() 传递给数据源之前调用 ToList() 而言,如果数据源不使用新对象中的所有字段,则跳过 ToList 和让数据源只提取它需要的字段。您所做的是将数据与视图的确切要求高度耦合,应尽可能避免这种情况。例如,如果您突然需要显示一个存在于 FileDatabaseMerger 中但不在当前匿名对象中的字段,该怎么办?现在您必须更改控制器和视图才能添加它,如果您只是传入 IQueryable,则只需更改视图。同样,速度更快、内存更少、更灵活且更易于维护。

希望这有帮助..这个问题真的应该发布在代码审查上,而不是在 Whosebug 上。

进一步审查更新,下面的代码会更好

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);

不,它不是严格的 "bad practice"(例如使用用户输入的字符串连接或使用 goto 构造 SQL 查询)。

有时这样的代码比多个查询/foreach 或无副作用的 Aggregate 调用更具可读性。另外,最好至少尝试编写 foreach 和无副作用版本,看看哪个更 readable/easier 来证明正确性。

请注意:

  • 通常很难推断 what/when 会发生这种代码。 IE。您对使用 .ToList() 调用延迟执行的 LINQ 查询的事实进行了黑客攻击,否则将不会计算该值。
  • 纯函数可以运行并行,一旦有副作用就需要格外小心
  • 如果您需要将 LINQ-to-Object 转换为 LINQ-to-SQL,您必须重写此类查询
  • 通常 LINQ 查询喜欢没有副作用的函数式编程风格(因此按照惯例,读者不会期望代码中有副作用)。