LINQ对于IEnumerable的等效foreach循环

33 浏览
0 Comments

LINQ对于IEnumerable的等效foreach循环

我想在LINQ中实现以下相同的功能,但是我搞不清楚如何做到:

IEnumerable items = GetItems();
items.ForEach(i => i.DoStuff());

真正的语法是什么?

0
0 Comments

在C# 5.0之前的版本中,使用foreach关键字存在一些问题。这个问题的根源在于foreach在C#中的实现方式与其他语言中等效的“for comprehension”方式不一致,并且与我对其工作方式的期望也不一致。这个问题也涉及到了闭包的访问修改的问题。

为了解决这个问题,可以使用与foreach等效的LINQ扩展方法来遍历一个IEnumerable集合。下面是一个示例代码:

public static class Enumerables
{
    public static void ForEach(this IEnumerable collection, Action action)
    {
        foreach (T item in collection)
        {
            action(item);
        }
    }
}

这个示例代码定义了一个名为ForEach的扩展方法,可以在IEnumerable集合上循环遍历并对每个元素执行指定的操作。

然而,如果仍然使用foreach关键字来遍历集合,可能会导致奇怪且非确定性的结果。下面是一个使用ForEach扩展方法的测试代码:

[Test]
public void ForEachExtensionWin()
{
    var values = Enumerable.Range(0, 10);
    var observable = Observable.Create>(source =>
                            {
                                values.ForEach(value => 
                                    source.OnNext(() => value));
                                source.OnCompleted();
                                return () => { };
                            });
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

这个测试通过了,证明使用ForEach扩展方法可以正确地遍历集合并执行操作。

然而,下面是一个使用foreach关键字的测试代码:

[Test]
public void ForEachKeywordFail()
{
    var values = Enumerable.Range(0, 10);
    var observable = Observable.Create>(source =>
                            {
                                foreach (var value in values)
                                {
                                    source.OnNext(() => value);
                                }
                                source.OnCompleted();
                                return () => { };
                            });
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

这个测试失败了,并且产生了一个错误。这个错误的原因是foreach循环中的闭包访问了修改后的值。

总结一下,使用ForEach扩展方法可以解决使用foreach关键字遍历集合时可能产生的问题。这种问题的根源在于foreach在C#中的实现方式与其他语言的实现方式不一致,并且与一些开发者对其工作方式的期望也不一致。通过使用LINQ扩展方法,可以更加灵活地遍历集合并执行操作,避免了使用foreach关键字可能导致的问题。

0
0 Comments

(LINQ equivalent of foreach for IEnumerable)这个问题的出现的原因是因为LINQ查询操作符应该是无副作用的,符合一种合理的函数式思维方式。而ForEach恰恰相反,它是一个纯粹基于副作用的构造。这并不意味着这样做是不好的,只是在考虑这个决定背后的哲学原因。

有人提出过同样的问题,并从“权威人士”那里得到了答案。然而,LINQ函数通常提供了一个foreach()所没有的非常有用的功能,就是匿名函数可以接受一个索引作为参数,而foreach必须重建经典的for (int i..)结构。函数式语言必须允许具有副作用的迭代,否则你将无法完成任何工作 🙂 我想指出的是,典型的函数式方法会返回原始的可枚举对象。

实际上,任何一种语言都需要输出或传递一些内容才能完成工作(输出到文件、进度通知等等)。这就是foreach在函数环境中有用的地方-执行这些操作。因此,说它没有副作用就等于说你不需要foreach。

LINQ并不是学术上的函数式,对吗?我认为它是C#中的一个实用函数式构造。实用性(不幸的是?)需要副作用... 因此,我不认为出于副作用的原因将这个构造添加到LINQ中是在哲学上不合适的。然而,在foreach体内提供一个获取索引的方法可能更合适。但在两个地方都有这个方法会很好。

实际上,并不是到处都需要副作用。通常情况下,我会使用LINQ来处理无副作用的部分,然后使用foreach循环来处理具有副作用的代码部分。如果你需要在foreach中使用索引,你可以使用foreach (var pair in sequence.Select((value, index) => new { value, index }))或者使用我在MiscUtil中的SmartEnumerable类,它基本相同。

将ForEach添加到LINQ并不鼓励在每个地方使用它。你是对的,它们确实不需要出现在每个地方。我知道有等效的用户代码;个人而言,我更喜欢使用for()循环而不是构造一个全新的对象列表来访问索引。语言/官方库的支持会很好。但是,是的,用户代码是一种目前可行的选择。

是的,F#有Seq.iter,但是F#也有不可变的数据结构。当在这些数据结构上使用Seq.iter时,原始的Seq不会被修改;而是返回一个具有副作用元素的新Seq。

Seq.iter不返回任何东西,更不用说具有副作用元素的新seq了。它真的只是关于副作用。你可能是在想Seq.map,因为Seq.iter正好等价于ForEach扩展方法的IEnumerable<_>。

我确实认为你的回答很有帮助,但你没有提到何时需要关闭循环变量。在这种情况下,可以考虑使用"ForEach"而不是"foreach"。请参阅我的答案。我今天在使用moq进行单元测试时遇到了这种情况。我实际上是来这里了解为什么在IEnumerable之前没有提供"ForEach",然后自己实现它。

老实说,我可能仍然会使用foreach。

我阅读了Eric Lippert的文章,并阅读了这个问题的评论,但仍然不明白为什么在IList上引发副作用是可以接受的,而在IEnumerable上引发副作用却不允许。我同意其他所有反对ForEach的观点,但ForEach可以提供流畅的API。有人能解释一下为什么副作用对IEnumerable不好,而对IList却没有问题吗?

并不是一个确切的答案,但请注意Lippert博客底部的更新...Metro显然删除了List.ForEach:"虽然这个方法看起来很简单,但当列表在传递给ForEach的方法中被修改时,它可能会产生一些潜在的问题。因此,建议您只使用foreach循环。" - Wes Haggard, BCL Team。这是一个实际的例子,证明了Skeet所解释的"反对"副作用的哲学。

Eric Lippert的文章链接:https://web.archive.org/web/20190118075220/https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/

“这并不意味着这样做是不好的”。12年后,我强烈不同意。在IEnumerable上的ForEach扩展方法是灾难性的(请参阅我的答案),社区应该知道这一点。

我认为"灾难性"这个词用得太夸张了,当你在循环中实际上做了一些事情时,你可能会发现性能差异变得微不足道。我并不特别在乎做"没有意义的事情"需要多长时间。

你们两个都对又对又错 🙂 这取决于上下文,显然。如果一些Unity开发者来到这里,决定复制一个好用的扩展方法,当他们的游戏因为垃圾回收而突然停顿时,会出现不愉快的情况。

所以,是的,就像你在互联网上找到的"每一段代码"一样,你需要检查它是否适合你的上下文使用。我对你提出两种方法之间的性能差异没有异议,但是没有必要夸大其词。请注意,使用LINQ做任何事情都会有相同的性能特征-你会把LINQ也称为"灾难"吗?

是的,确实:D。这是一个复杂的问题。我会在未来的某一天写一篇博客,然后再回答你。

0
0 Comments

LINQ equivalent of foreach for IEnumerable问题的出现原因是没有针对IEnumerable的ForEach扩展方法,只有针对List的ForEach方法。因此,可以使用ToList()方法将IEnumerable转换为List,然后调用List的ForEach方法。另一种解决方法是编写自己的ForEach扩展方法。该方法接受一个IEnumerable的参数和一个Action的参数,使用foreach循环遍历集合并执行指定的动作。

然而,使用ToList()方法会创建集合的副本,可能会引起性能和内存问题。所以,使用ToList().ForEach()并不适用于需要修改每个元素状态的情况。

此外,使用ForEach方法的优点包括支持多线程操作(通过PLINQ实现)和异常处理。而且,编写一个具有延迟执行的ForEach方法可能更加方便,但可能会引起副作用的问题。

对于需要集合中的每个元素返回一个值的情况,可以考虑使用Func委托而不是Action委托。

使用扩展方法实现的ForEach方法更加清晰和可读,而ToList().ForEach()则是一个快速解决方法,但在功能上并没有任何价值。

0