我想在 LINQ 中执行以下等效操作,但我不知道如何:
IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
真正的语法是什么?
我想在 LINQ 中执行以下等效操作,但我不知道如何:
IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
真正的语法是什么?
没有 ForEach 扩展IEnumerable
;只为List<T>
。所以你可以做
items.ToList().ForEach(i => i.DoStuff());
或者,编写您自己的 ForEach 扩展方法:
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
foreach(T item in enumeration)
{
action(item);
}
}
Fredrik 提供了修复程序,但可能值得考虑为什么这不在框架中。我相信这个想法是 LINQ 查询运算符应该是无副作用的,适合以合理的功能看待世界的方式。显然 ForEach 正好相反——一个纯粹基于副作用的构造。
这并不是说这是一件坏事——只是想想这个决定背后的哲学原因。
2012 年 7 月 17 日更新:显然从 C# 5.0 开始,foreach
下面描述的行为已经改变,“在嵌套 lambda 表达式中使用foreach
迭代变量不再产生意外结果。 ”这个答案不适用于 C# ≥ 5.0 .
@John Skeet 和喜欢 foreach 关键字的每个人。
在 5.0 之前的 C# 中,“foreach”的问题在于,它与其他语言中等效的“用于理解”的工作方式以及我期望它的工作方式不一致(个人观点仅在此处陈述,因为其他人提到了他们的关于可读性的意见)。请参阅有关“访问修改后的闭包”以及“关闭被认为有害的循环变量”的所有问题。这只是“有害的”,因为“foreach”是在 C# 中实现的。
使用与@Fredrik Kalseth 的答案中的功能等效的扩展方法来获取以下示例。
public static class Enumerables
{
public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
{
foreach (T item in @this)
{
action(item);
}
}
}
为过度做作的例子道歉。我只使用 Observable 是因为做这样的事情并不完全牵强。显然有更好的方法来创建这个 observable,我只是试图证明一个观点。通常,订阅 observable 的代码是异步执行的,并且可能在另一个线程中执行。如果使用“foreach”,这可能会产生非常奇怪且可能不确定的结果。
以下使用“ForEach”扩展方法的测试通过:
[Test]
public void ForEachExtensionWin()
{
//Yes, I know there is an Observable.Range.
var values = Enumerable.Range(0, 10);
var observable = Observable.Create<Func<int>>(source =>
{
values.ForEach(value =>
source.OnNext(() => value));
source.OnCompleted();
return () => { };
});
//Simulate subscribing and evaluating Funcs
var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();
//Win
Assert.That(evaluatedObservable,
Is.EquivalentTo(values.ToList()));
}
以下失败并出现错误:
预期:相当于 < 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 > 但为: < 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 >
[Test]
public void ForEachKeywordFail()
{
//Yes, I know there is an Observable.Range.
var values = Enumerable.Range(0, 10);
var observable = Observable.Create<Func<int>>(source =>
{
foreach (var value in values)
{
//If you have resharper, notice the warning
source.OnNext(() => value);
}
source.OnCompleted();
return () => { };
});
//Simulate subscribing and evaluating Funcs
var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();
//Fail
Assert.That(evaluatedObservable,
Is.EquivalentTo(values.ToList()));
}
您可以使用FirstOrDefault()
可用于IEnumerable<T>
. 通过false
从谓词返回,它将为每个元素运行,但不会关心它实际上没有找到匹配项。这将避免ToList()
开销。
IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
我想在 LINQ 中执行以下等效操作,但我不知道如何:
正如其他人在国内外指出的那样,LINQ 和IEnumerable
方法预计不会产生副作用。
您真的想对 IEnumerable 中的每个项目“做点什么”吗?那么foreach
是最好的选择。当副作用发生在这里时,人们并不感到惊讶。
foreach (var i in items) i.DoStuff();
然而,根据我的经验,通常不需要副作用。通常有一个简单的 LINQ 查询等待被发现,并附有由 Jon Skeet、Eric Lippert 或 Marc Gravell 提供的 StackOverflow.com 答案,解释如何做你想做的事!
如果您实际上只是聚合(累积)一些值,那么您应该考虑Aggregate
扩展方法。
items.Aggregate(initial, (acc, x) => ComputeAccumulatedValue(acc, x));
也许您想IEnumerable
从现有值创建一个新值。
items.Select(x => Transform(x));
或者,也许您想创建一个查找表:
items.ToLookup(x, x => GetTheKey(x))
可能性的列表(双关语并非完全有意)还在继续。
我采用了 Fredrik 的方法并修改了返回类型。
这样,该方法像其他 LINQ 方法一样支持延迟执行。
编辑:如果不清楚,则此方法的任何使用都必须以 ToList()或任何其他方式结束,以强制该方法在完整的可枚举上工作。否则,该动作将不会被执行!
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
foreach (T item in enumeration)
{
action(item);
yield return item;
}
}
这是帮助查看它的测试:
[Test]
public void TestDefferedExecutionOfIEnumerableForEach()
{
IEnumerable<char> enumerable = new[] {'a', 'b', 'c'};
var sb = new StringBuilder();
enumerable
.ForEach(c => sb.Append("1"))
.ForEach(c => sb.Append("2"))
.ToList();
Assert.That(sb.ToString(), Is.EqualTo("121212"));
}
如果最后删除ToList(),您将看到测试失败,因为 StringBuilder 包含一个空字符串。这是因为没有方法强制 ForEach 枚举。
如果您想充当枚举卷,您应该让出每个项目。
public static class EnumerableExtensions
{
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
foreach (var item in enumeration)
{
action(item);
yield return item;
}
}
}
这么多的答案,但都未能指出自定义通用 ForEach
扩展的一个非常重要的问题:性能!更具体地说,内存使用和GC。
考虑下面的示例。目标是.NET Framework 4.7.2
或.NET Core 3.1.401
,配置是Release
和平台是Any CPU
。
public static class Enumerables
{
public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
{
foreach (T item in @this)
{
action(item);
}
}
}
class Program
{
private static void NoOp(int value) {}
static void Main(string[] args)
{
var list = Enumerable.Range(0, 10).ToList();
for (int i = 0; i < 1000000; i++)
{
// WithLinq(list);
// WithoutLinqNoGood(list);
WithoutLinq(list);
}
}
private static void WithoutLinq(List<int> list)
{
foreach (var item in list)
{
NoOp(item);
}
}
private static void WithLinq(IEnumerable<int> list) => list.ForEach(NoOp);
private static void WithoutLinqNoGood(IEnumerable<int> enumerable)
{
foreach (var item in enumerable)
{
NoOp(item);
}
}
}
乍一看,所有三个变体都应该表现同样出色。但是,当ForEach
扩展方法被多次调用时,最终会产生垃圾,这意味着代价高昂的 GC。事实上,ForEach
在热路径上使用这种扩展方法已被证明会完全破坏我们循环密集型应用程序的性能。
类似地,弱类型foreach
循环也会产生垃圾,但它仍然会比ForEach
扩展更快且内存占用更少(它也受到委托分配的影响)。
对于强类型foreach
,编译器能够使用类的任何优化枚举器(例如基于值的),而泛型ForEach
扩展必须回退到将在每次运行时分配的泛型枚举器。此外,实际代表也将意味着额外的分配。
使用该方法您会得到类似的不良结果WithoutLinqNoGood
。在那里,参数是类型IEnumerable<int>
而不是List<int>
暗示相同类型的枚举器分配。
以下是IL
. 基于值的枚举器当然更可取!
IL_0001: callvirt instance class
[mscorlib]System.Collections.Generic.IEnumerator`1<!0>
class [mscorlib]System.Collections.Generic.IEnumerable`1<!!T>::GetEnumerator()
对比
IL_0001: callvirt instance valuetype
[mscorlib]System.Collections.Generic.List`1/Enumerator<!0>
class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
OP 询问如何ForEach()
调用IEnumerable<T>
. 原始答案清楚地表明了如何做到这一点。当然你可以做到,但又一次;我的回答清楚地表明你不应该。
验证目标时的相同行为.NET Core 3.1.401
(使用 编译Visual Studio 16.7.2
)。
Microsoft 有一个交互式扩展 LINQ的实验版本(也在NuGet 上,有关更多链接,请参阅RxTeams 的配置文件)。第9 频道的视频很好地解释了这一点。
它的文档仅以 XML 格式提供。我已在 Sandcastle 中运行此文档,以使其具有更易读的格式。解压缩文档存档并查找index.html。
在许多其他好处中,它提供了预期的 ForEach 实现。它允许您编写如下代码:
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };
numbers.ForEach(x => Console.WriteLine(x*x));
根据 PLINQ(自 .Net 4.0 起可用),您可以执行
IEnumerable<T>.AsParallel().ForAll()
在 IEnumerable 上执行并行 foreach 循环。
ForEach 的目的是引起副作用。IEnumerable 用于集合的惰性枚举。
当您考虑它时,这种概念差异是非常明显的。
SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));
在您执行“计数”或“ToList()”或其他操作之前,这不会执行。这显然不是所表达的。
您应该使用 IEnumerable 扩展来设置迭代链,通过它们各自的来源和条件定义内容。表达式树功能强大且高效,但您应该学会欣赏它们的本质。不仅仅是为了围绕它们进行编程以保存一些覆盖惰性求值的字符。
很多人提到它,但我不得不写下来。这不是最清晰/最易读的吗?
IEnumerable<Item> items = GetItems();
foreach (var item in items) item.DoStuff();
简短而简单(st)。
现在我们可以选择...
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 4;
#if DEBUG
parallelOptions.MaxDegreeOfParallelism = 1;
#endif
Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));
当然,这开辟了一个全新的线虫罐头。
ps(抱歉字体,这是系统决定的)
正如许多答案已经指出的那样,您可以自己轻松地添加这样的扩展方法。但是,如果您不想这样做,尽管我在 BCL 中不知道有这样的事情,但System
如果您已经有对Reactive Extension的引用(并且如果您没有, 你应该有):
using System.Reactive.Linq;
items.ToObservable().Subscribe(i => i.DoStuff());
尽管方法名称有些不同,但最终结果正是您要寻找的。
ForEach 也可以是Chained,只是在动作之后放回 pileline 。保持流利
Employees.ForEach(e=>e.Act_A)
.ForEach(e=>e.Act_B)
.ForEach(e=>e.Act_C);
Orders //just for demo
.ForEach(o=> o.EmailBuyer() )
.ForEach(o=> o.ProcessBilling() )
.ForEach(o=> o.ProcessShipping());
//conditional
Employees
.ForEach(e=> { if(e.Salary<1000) e.Raise(0.10);})
.ForEach(e=> { if(e.Age >70 ) e.Retire();});
一个Eager版本的实现。
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enu, Action<T> action)
{
foreach (T item in enu) action(item);
return enu; // make action Chainable/Fluent
}
编辑:一个懒惰的版本正在使用产量回报,就像这样。
public static IEnumerable<T> ForEachLazy<T>(this IEnumerable<T> enu, Action<T> action)
{
foreach (var item in enu)
{
action(item);
yield return item;
}
}
Lazy 版本需要具体化,例如ToList (),否则,什么也不会发生。请参阅下面来自 ToolmakerSteve 的精彩评论。
IQueryable<Product> query = Products.Where(...);
query.ForEachLazy(t => t.Price = t.Price + 1.00)
.ToList(); //without this line, below SubmitChanges() does nothing.
SubmitChanges();
我将 ForEach() 和 ForEachLazy() 都保存在我的库中。
受 Jon Skeet 的启发,我扩展了他的解决方案:
扩展方法:
public static void Execute<TSource, TKey>(this IEnumerable<TSource> source, Action<TKey> applyBehavior, Func<TSource, TKey> keySelector)
{
foreach (var item in source)
{
var target = keySelector(item);
applyBehavior(target);
}
}
客户:
var jobs = new List<Job>()
{
new Job { Id = "XAML Developer" },
new Job { Id = "Assassin" },
new Job { Id = "Narco Trafficker" }
};
jobs.Execute(ApplyFilter, j => j.Id);
. . .
public void ApplyFilter(string filterId)
{
Debug.WriteLine(filterId);
}
这种“功能性方法”抽象泄露了很多时间。语言层面上没有任何东西可以防止副作用。只要您可以让它为容器中的每个元素调用您的 lambda/delegate - 您将获得“ForEach”行为。
这里例如将 srcDictionary 合并到 destDictionary 的一种方法(如果键已经存在 - 覆盖)
这是一个 hack,不应在任何生产代码中使用。
var b = srcDictionary.Select(
x=>
{
destDictionary[x.Key] = x.Value;
return true;
}
).Count();
MoreLinq 有IEnumerable<T>.ForEach
很多其他有用的扩展。可能不值得将依赖项仅仅用于ForEach
,但其中有很多有用的东西。
我不同意链接扩展方法应该没有副作用的观点(不仅因为它们不是,任何委托都可以执行副作用)。
考虑以下:
public class Element {}
public Enum ProcessType
{
This = 0, That = 1, SomethingElse = 2
}
public class Class1
{
private Dictionary<ProcessType, Action<Element>> actions =
new Dictionary<ProcessType,Action<Element>>();
public Class1()
{
actions.Add( ProcessType.This, DoThis );
actions.Add( ProcessType.That, DoThat );
actions.Add( ProcessType.SomethingElse, DoSomethingElse );
}
// Element actions:
// This example defines 3 distict actions
// that can be applied to individual elements,
// But for the sake of the argument, make
// no assumption about how many distict
// actions there may, and that there could
// possibly be many more.
public void DoThis( Element element )
{
// Do something to element
}
public void DoThat( Element element )
{
// Do something to element
}
public void DoSomethingElse( Element element )
{
// Do something to element
}
public void Apply( ProcessType processType, IEnumerable<Element> elements )
{
Action<Element> action = null;
if( ! actions.TryGetValue( processType, out action ) )
throw new ArgumentException("processType");
foreach( element in elements )
action(element);
}
}
该示例显示的实际上只是一种后期绑定,它允许调用对一系列元素具有副作用的许多可能动作中的一个,而无需编写一个大的 switch 构造来解码定义动作的值并翻译将其转化为相应的方法。
为了保持流利,可以使用这样的技巧:
GetItems()
.Select(i => new Action(i.DoStuf)))
.Aggregate((a, b) => a + b)
.Invoke();
对于 VB.NET,您应该使用:
listVariable.ForEach(Sub(i) i.Property = "Value")
又一个ForEach
例子
public static IList<AddressEntry> MapToDomain(IList<AddressModel> addresses)
{
var workingAddresses = new List<AddressEntry>();
addresses.Select(a => a).ToList().ForEach(a => workingAddresses.Add(AddressModelMapper.MapToDomain(a)));
return workingAddresses;
}