故意依赖 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 查询喜欢没有副作用的函数式编程风格(因此按照惯例,读者不会期望代码中有副作用)。
像这样的编程模式经常出现:
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 查询喜欢没有副作用的函数式编程风格(因此按照惯例,读者不会期望代码中有副作用)。