通过LINQ将函数应用于集合的所有元素

156

我最近开始使用LINQ,它非常棒。我想知道,是否可以使用LINQ将一个函数(任何函数)应用于集合的所有元素,而无需使用foreach循环。就像Python中的Lambda函数一样。

例如,如果我有一个整数列表,我是否可以使用LINQ将常数添加到每个元素中。

如果我有一个数据库表,我能否使用LINQ为所有记录设置一个字段。

我正在使用C#。

8个回答

163

常见的处理方法是在 IEnumerable<T> 上添加自己的 ForEach 泛型方法。这是我们在 MoreLINQ 中拥有的方法:

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
    source.ThrowIfNull("source");
    action.ThrowIfNull("action");
    foreach (T element in source)
    {
        action(element);
    }
}

(ThrowIfNull是任何引用类型的扩展方法,它会执行显而易见的操作。)

有趣的是看看这是否是.NET 4.0的一部分。它与LINQ的函数式风格相违背,但毫无疑问很多人发现它很有用。

一旦你掌握了它,你就可以编写像下面这样的代码:

people.Where(person => person.Age < 21)
      .ForEach(person => person.EjectFromBar());

15
我从未真正理解人们有这种冲动的原因。对我来说,使用foreach循环更易读;将获取数据的“功能”部分和处理数据的“操作”部分分开有助于澄清算法的结构。 - mqp
17
是的,我通常更喜欢使用 foreach 循环。但是如果你已经有了一个委托来应用于每个元素,那么在明确的 foreach 循环中调用该委托是否更易读并不确定。我不会经常使用这种方式,但偶尔它似乎是一个好的解决方案。 - Jon Skeet
2
@Lodewijk:使用必须具有副作用才能发挥作用的方法,有什么功能性呢?Where部分是功能性的...但是ForEach部分不太是,我个人认为。 - Jon Skeet
3
将类型直接更改而不返回新类型绝对是糟糕的做法。这将会让很多程序员感到困惑。虽然可以减少嵌套foreach或者写丑陋代码的情况,但是好处已经不值得了。一行代码也可能非常易读。 - Lodewijk
1
我知道这是一个老问题。我发现在RxJS中,forEach非常有用,它感觉像是Linq目前的一个遗漏,而且我认为它不会让人们感到困惑,因为forEach lambda的目的是修改项目或针对它们执行特定操作,特别是在处理EF实体时非常有用,您绝对不希望出现新类型。我们的RxJs代码非常易读和紧凑。谢谢@JonSkeet提供的答案。我们不会一直使用它,但有时它将节省相当多的foreach样板。 - Max
显示剩余10条评论

106

使用 LINQ 的惯用方法是处理集合并返回映射为所需格式的新集合。例如,要将常量添加到每个元素中,您需要类似于以下内容:

var newNumbers = oldNumbers.Select(i => i + 8);

以函数式的方式完成操作,而不是频繁地改变现有集合的状态,这样有助于将不同的操作分离开来,既容易阅读,又容易让编译器推理。

如果您需要对集合中的每个元素应用一项作用(具有与集合内容无关的副作用),那么LINQ并不是最适合的选择,尽管您可以使用Select来模拟它(或者编写您自己的IEnumerable扩展方法,就像许多人所做的那样)。在这种情况下,最好使用foreach循环。


3
更不用说在迭代过程中改变集合元素会带来危险,这在不同的编程语言中可能是不可能完成的。 - Soviut
16
在 C# 中,修改集合元素是允许的,但添加或删除集合中的元素可能会失败或产生意料之外的结果。 - mqp
仅使用Select方法只对某些类型(如int)有帮助。如果您想将列表中对象的属性设置为特定值,使用.ForEach()是否更简单、更可扩展? - andy
LINQ 延迟执行可能会导致 Select 存在问题,如果您不在结果上稍后进行迭代。 - Michel Feinstein
@Soviut ForEach不会改变集合本身。它可以 - 并且通常用于 - 编辑集合中的元素。个人而言,我将编辑后的集合作为结果返回,但您的情况可能有所不同。 - Suncat2000
显示剩余2条评论

51

如果您不关心顺序,更在意为每个项目完成任务,您还可以考虑并行处理:

SomeIEnumerable<T>.AsParallel().ForAll( Action<T> / Delegate / Lambda )
例如:
var numbers = new[] { 1, 2, 3, 4, 5 };
numbers.AsParallel().ForAll( Console.WriteLine );

希望对你有所帮助。


1
请注意AsParallel().ForAll(),因为它会导致不可预测的结果。例如,当单击按钮时,我有一个执行此代码的按钮:myEnumerable.AsParallel().ForAll(i as string => otherDictionary.Add(i, 0))。它将null作为键添加到otherDictionary中。我不得不重写使用foreach循环。奇怪的事情。 - YukiSakura
5
@YukiSakura并不是AsParallel().ForAll()的问题,危险的部分在于在多个线程之间使用共享状态,然后将“i as string”的转换作为可能导致传递空键的原因。是否考虑分享一份完整的代码样本,以便获得改进意见? - Jaans

43

哈哈,伙计,我几个小时前刚刚问了这个问题(差不多)……试试这个:

示例:

someIntList.ForEach(i=>i+5);

ForEach() 是 .NET 中的一个内置方法。

这会修改列表,而不是返回一个新的列表。


15
请注意,这仅适用于实际的List<T>集合,除非您定义了自己的ForEach方法来执行此操作。 - mqp
4
我认为mquander指的是,ForEach方法在IEnumerable<T>上未定义,只能在List<T>上使用。 - Odrade
1
是的,在大多数情况下它不起作用,因为通常您会从链接查询中获得一个IEnumerable。 - Torben Junker Kjær
12
我发现大部分时间添加 .ToList().ForEach(...) 就足够了。 - Tod
为什么这会给我一个错误:error CS0201:只有赋值、调用、增量、减量、等待和新对象表达式可以用作语句。 - abdou93

14

或者你可以对其进行黑客攻击。

Items.All(p => { p.IsAwesome = true; return true; });

14
这是将“所有”的代码改写为“ForEach”形式。我担心这样会误导一些新手,让他们认为“所有”意味着“在所有元素上执行”。 - ToolmakerSteve
有趣。这实际上会编辑底层集合吗?返回的对象是副本吗? - Zimano
1
@Zimano 它确实编辑了底层集合,但不会返回副本。 - Jack_2060

6

如果集合不支持 ForEach,您可以使用 Parallel 类中的静态 ForEach 方法:

var options = new ParallelOptions() { MaxDegreeOfParallelism = 1 };
Parallel.ForEach(_your_collection_, options, x => x._Your_Method_());

这真的很不直观。 - Yola

2
你可以尝试类似以下的方法:
var foo = (from fooItems in context.footable select fooItems.fooID + 1);

返回id的列表+1,您可以使用一个函数来处理select子句中的任何内容。

更新:根据Jon Skeet的建议,这是我刚刚发布的代码片段的更好版本:

var foo = context.footable.Select(foo => foo.fooID + 1);

1
我建议在只需要进行简单投影的情况下,查询表达式有些过头了。我会使用“var foo = context.footable.Select(foo => foo.fooID + 1);”。 - Jon Skeet

-1

我找到了一些方法来在包含我的自定义类方法的字典中执行它们。

foreach (var item in this.Values.Where(p => p.IsActive == false))
            item.Refresh();

'this' 是从哪里派生的:Dictionary<string, MyCustomClass>

class MyCustomClass 
{
   public void Refresh(){}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接