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

时间:2015-08-14 00:35:14

标签: c# 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;

我不确定是否有更简洁的方法对此进行编码?

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

2 个答案:

答案 0 :(得分:2)

不,这不严格"不良做法" (比如使用用户输入的字符串连接或使用goto构建SQL查询)。

有时,此类代码比几个查询/ foreach或无副作用Aggregate调用更具可读性。另外,最好至少尝试编写foreach和无副作用版本,以查看哪一个更易读/更容易证明正确。

请注意:

  • 通常很难推断出这样的代码会发生什么/何时发生。即你可以通过.ToList()调用来懒散地执行LINQ查询这样的事实,但不会计算该值。
  • 纯函数可以并行运行,一旦副作用需要很多照顾
  • 如果您需要将LINQ-to-Object转换为LINQ-to-SQL,则必须重写此类查询
  • 一般来说LINQ查询都支持函数式编程风格而没有副作用(因此按照惯例,读者不会在代码中产生副作用)。

答案 1 :(得分: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 7.x中的ToList更改为返回一个半延迟转换IQueryable的对象,该怎么办?如果更改为并行运行lambda会怎么样?突然间,你的staleCount出现了并发问题。据我所知,由于您的代码所做的错误假设,这些都可能会破坏您的代码。

现在只要用一个id重复调用MultiMerger.GetMerger,那真的应该重新加工成一个连接,因为进行连接的逻辑(w | c)应该比你在那里编写的更有效率会扩展得更好,特别是如果MultiMerger的实现实际上是从数据库中提取数据(或者可能会更改为这样做)。

在将ToList()传递给Datasource之前调用ToList(),如果Datasource不使用新对象中的所有字段,那么您将(更快)并且占用更少的内存来跳过ToList并让它datasource只拉取它需要的字段。您所做的是将数据高度耦合到视图的确切要求,应尽可能避免这些要求。例如,如果您突然需要显示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);