C#范例:对列表的副作用

我试图进一步了解副作用以及如何控制和应用它们。

在下面的航class列表中,我想设置满足条件的每个航class的属性:

IEnumerable fResults = getResultsFromProvider(); //Set all non-stop flights description fResults.Where(flight => flight.NonStop) .Select(flight => flight.Description = "Fly Direct!"); 

在这个表达式中,我对我的列表有副作用。 从我有限的知识,我知道前。 “LINQ仅用于查询 ”和“列表中只有少数操作, 分配或设置值不是其中之一”,“列表应该是不可变的”。

  • 上面的LINQ语句有什么问题,应该如何更改?
  • 我在哪里可以获得有关上述场景的基本范例的更多信息?

您的LINQ代码不会“直接”违反您提到的指南,因为您没有修改列表本身; 你只是修改列表内容的一些属性。

但是,驱动这些指导原则的主要反对意见仍然存在:您不应该使用LINQ修改数据(同样,您正在滥用Select以执行您的副作用)。

不修改任何数据可以很容易地certificate是合理的。 考虑以下代码段:

 fResults.Where(flight => flight.NonStop) 

你知道这是在修改飞行物吗? 许多维护程序员也不会,因为他们会在Where之后停止阅读 – 后面的代码显然没有副作用,因为这是一个查询,对吧?

[Nitpick:当然,看到一个不保留返回值的查询是一个死的赠品,查询确实有副作用或代码应该被删除; 无论如何,“有些事情是错的”。 但是,当只有两行代码而不是页面上的页面时,更容易说出来。

作为一个正确的解决方案,我建议这样做:

 foreach (var x in fResults.Where(flight => flight.NonStop)) { x.Description = "Fly Direct!"; } 

写入和读取都很容易。

您有两种方法可以实现LINQ方式:

  1. 显式的foreach循环

     foreach(Flight f in fResults.Where(flight => flight.NonStop)) f.Description = "Fly Direct!"; 
  2. 使用ForEach操作符,为副作用:

     fResults.Where(flight => flight.NonStop) .ForEach(flight => flight.Description = "Fly Direct!"); 

第一种方式对于这么简单的任务来说非常沉重,第二种方式只能用于非常短的物体。

现在,您可能会问自己为什么LINQ堆栈中没有ForEach运算符。 这很简单 – LINQ应该是表达查询操作的一种function方式,这尤其意味着没有任何操作符应该有副作用。 设计团队决定ForEach堆栈中添加ForEach操作符,因为唯一的用法是它的副作用。

ForEach运算符的通常实现如下:

 public static class EnumerableExtension { public static void ForEach (this IEnumerable source, Action action) { if(source == null) throw new ArgumentNullException("source"); foreach(T obj in source) action(obj); } } 

这种方法的一个问题是它根本不起作用。 该查询是惰性的,这意味着它不会执行Select中的代码,直到您实际从查询中读取内容,而您永远不会这样做。

您可以通过在查询结尾添加.ToList()来解决这个问题,但代码仍在使用副作用并丢弃实际结果。 您应该使用结果来执行更新:

 //Set all non-stop flights description foreach (var flight in fResults.Where(flight => flight.NonStop)) { flight.Description = "Fly Direct!"; } 

除了你需要以某种方式迭代它,比如在它上面调用Count()之外,它没有任何问题。

从“风格”的角度来看,它并不好。 人们不会指望迭代器改变列表值/属性。

IMO以下会更好:

 foreach (var x in fResults.Where(flight => flight.NonStop)) { x.Description = "Fly Direct!"; } 

代码的读者或维护者的意图更加清晰。

您应该将其分解为两个代码块,一个用于检索,另一个用于设置值:

 var nonStopFlights = fResults.Where(f => f.NonStop); foreach(var flight in nonStopFlights) flight.Description = "Fly Direct!"; 

或者,如果你真的讨厌foreach的样子,你可以尝试:

 var nonStopFlights = fResults.Where(f => f.NonStop).ToList(); // ForEach is a method on List that is acceptable to make modifications inside. nonStopFlights.ForEach(f => f.Description = "Fly Direct!"); 

当我真正改变某些东西时,我喜欢使用foreach 。 就像是

 foreach (var flight in fResults.Where(f => f.NonStop)) { flight.Description = "Fly Direct!"; } 

Eric Lippert在他的文章中也谈到了为什么LINQ没有ForEach辅助方法。

但我们可以在这里更深入一点。 出于两个原因,我在哲学上反对提供这样的方法。

第一个原因是这样做违反了所有其他序列运算符所基于的函数式编程原则。 显然,调用此方法的唯一目的是引起副作用。