我想在LINQ中执行以下等效操作,但我无法确定如何执行:
IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
真正的语法是什么?
我想在LINQ中执行以下等效操作,但我无法确定如何执行:
IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
真正的语法是什么?
当前回答
MoreLinq有IEnumerable<T>.ForEach和许多其他有用的扩展。仅为ForEach使用依赖关系可能不值得,但其中有很多有用的东西。
https://www.nuget.org/packages/morelinq/
https://github.com/morelinq/MoreLINQ
其他回答
您可以使用FirstOrDefault()扩展,该扩展可用于IEnumerable<T>。通过从谓词返回false,它将为每个元素运行,但不会在意它实际上没有找到匹配项。这将避免ToList()开销。
IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
又一个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;
}
MoreLinq有IEnumerable<T>.ForEach和许多其他有用的扩展。仅为ForEach使用依赖关系可能不值得,但其中有很多有用的东西。
https://www.nuget.org/packages/morelinq/
https://github.com/morelinq/MoreLINQ
这么多的答案,但所有人都未能找到自定义通用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:内存使用
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()
vs
IL_0001: callvirt instance valuetype
[mscorlib]System.Collections.Generic.List`1/Enumerator<!0>
class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
结论
OP询问如何在IEnumerable<T>上调用ForEach()。最初的答案清楚地表明了如何做到这一点。当然你能做到,但再说一遍;我的回答清楚地表明你不应该。
在针对.NET Core 3.1.401(使用Visual Studio 16.7.2编译)时验证了相同的行为。
对于VB.NET,应使用:
listVariable.ForEach(Sub(i) i.Property = "Value")