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

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

问题描述:

我想演变我对副作用的理解以及它们应该如何被控制和应用。C#范式:对列表的副作用

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

IEnumerable<FlightResults> fResults = getResultsFromProvider(); 

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

在这种表情,我有我的名单上的副作用。从我有限的知识中我知道前例。 “LINQ仅用于查询”和“列表只有少数操作,指定或设置值不是其中之一”,“列表应该是不可变的”。

  • 我上面的LINQ声明有什么问题,应该如何改变?
  • 从哪里可以获得关于上述场景的基本范例的更多信息?
+12

使用“foreach”循环引起副作用。选择用于投影,而不用于更新。这就是为什么它被称为“选择”而不是“更新”。 – 2011-06-17 14:12:20

+1

相关问题在这里http://*.com/questions/5632222/linq-side-effects – nawfal 2012-11-21 14:12:31

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

然而,驱动这些指南的主要反对意见仍然存在:您不应该使用LINQ修改数据(同时,您正在滥用Select来执行您的副作用)。

不修改任何数据可以很容易地证明。考虑这个片段:

fResults.Where(flight => flight.NonStop) 

您是否看到这是修改飞行属性的位置?许多维护程序员都不会,因为他们会在Where之后停止阅读 - 后面的代码是显然没有副作用,因为这是查询,对吧? [Nitpick:当然,看到一个查询的返回值没有被保留,这是一个死牌,查询确实有副作用或代码应该被删除;无论如何,那“有些事情是错误的”。但它是如此容易得多地说,当只有2行代码在页面的,而不是网页的外观]

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

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

很容易写和读。

我喜欢使用foreach当我真的改变了一些东西。像

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

东西也是如此埃里克利珀在为什么LINQ没有一个foreach辅助方法,他article

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

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

+0

好文章,解释一些关于我的问题的核心思想感谢。 – Pierre 2011-06-17 14:31:21

你必须实现它的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应该是表达查询操作的一种功能性方式,这尤其意味着没有一个操作员应该有副作用。设计团队决定不要将ForEach运算符添加到堆栈中,因为唯一的用法是其副作用。

一个通常的实施ForEach运营商将是这样的:

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

    foreach(T obj in source) 
     action(obj); 

    } 
} 
+0

+1:但查询是正确的。 :) – leppie 2011-06-17 13:14:02

+0

哦,我不知道。有趣。 – Femaref 2011-06-17 13:15:41

没有什么不对的地方深灰色,除非你需要以某种方式重复它,就像调用就可以了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!"); 

该方法的一个问题是它根本无法工作。查询是懒惰的,这意味着它不会执行Select中的代码,直到您实际从查询中读取某些内容,而且您从不这样做。

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

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