我想在LINQ中做相同的以下内容,但我无法弄清楚如何:
IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());
真正的语法是什么?
答案 0 :(得分:801)
IEnumerable
没有ForEach扩展名;仅适用于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);
}
}
答案 1 :(得分:345)
Fredrik提供了修复程序,但值得考虑为什么这不在框架中开始。我相信这个想法是LINQ查询运算符应该是无副作用的,适合用于查看世界的合理功能的方式。显然ForEach正好相反 - 一个纯基于副作用的构造。
这并不是说这是一件坏事 - 只是想一想这个决定背后的哲学原因。
答案 2 :(得分:36)
更新 2012年7月17日:显然,从C#5.0开始,下面描述的foreach
的行为已更改,“the use of a foreach
iteration variable in a nested lambda expression no longer produces unexpected results.”此答案不适用于C#≥5.0。
@John Skeet以及所有喜欢foreach关键字的人。
C# prior to 5.0 中“foreach”的问题在于它与其他语言中的“for comprehension”的等效方式不一致,以及我对它的期望如何工作(这里所说的个人意见只是因为其他人提到了他们对可读性的看法)。查看有关“Access to modified closure”的所有问题 以及“Closing over the loop variable considered harmful”。这只是“有害的”,因为在C#中实现了“foreach”的方式。
使用与@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的代码是异步执行的,并且可能在另一个线程中执行。如果使用“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()));
}
以下因错误而失败:
预期:相当于&lt; 0,1,2,3,4,5,6,7,8,9> 但是:&lt; 9,9,9,9,9,9,9,9,9,9&gt;
[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()));
}
答案 3 :(得分:34)
您可以使用适用于FirstOrDefault()
的{{1}}扩展程序。通过从谓词返回IEnumerable<T>
,它将针对每个元素运行,但不会关心它实际上没有找到匹配项。这样可以避免false
开销。
ToList()
答案 4 :(得分:20)
我采用了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枚举。
答案 5 :(得分:18)
我想在LINQ中做相同的以下内容,但我无法弄清楚如何:
正如其他人已经指出here和国外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))
可能性的列表(双关语并非完全有意)一直在继续。
答案 6 :(得分:11)
如果您想充当枚举名单,则应该产生每个项目。
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;
}
}
}
答案 7 :(得分:10)
Microsoft发布了Interactive Extensions to LINQ的实验性发布(也是on NuGet,有关更多链接,请参阅RxTeams's profile)。 Channel 9 video解释得很清楚。
其文档仅以XML格式提供。我运行了这个documentation in Sandcastle以允许它以更易读的格式。解压缩文档存档并查找 index.html 。
在许多其他好东西中,它提供了预期的ForEach实现。它允许您编写如下代码:
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };
numbers.ForEach(x => Console.WriteLine(x*x));
答案 8 :(得分:8)
根据PLINQ(自.Net 4.0以来可用),你可以做一个
IEnumerable<T>.AsParallel().ForAll()
在IEnumerable上执行并行foreach循环。
答案 9 :(得分:6)
答案如此之多,但都无法通过自定义泛型 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
扩展名(它也受delegate allocation的影响)更快并且占用的内存更少。
对于强类型的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
进行编译时)验证了相同的行为。
答案 10 :(得分:6)
ForEach的目的是引起副作用。 IEnumerable用于集合的延迟枚举。
当你考虑它时,这种概念上的区别非常明显。
SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));
在您执行“count”或“ToList()”或其他操作之前,这不会执行。 显然不是表达的内容。
您应该使用IEnumerable扩展来设置迭代链,根据各自的来源和条件定义内容。表达树是强大而有效的,但你应该学会欣赏它们的本性。而且不只是围绕它们进行编程以保存一些字符来覆盖惰性评估。
答案 11 :(得分:5)
很多人提到它,但我不得不把它写下来。这不是最清晰/最可读吗?
IEnumerable<Item> items = GetItems();
foreach (var item in items) item.DoStuff();
简短(st)。
答案 12 :(得分:5)
众多答案已经指出,您可以自己轻松添加这样的扩展方法。但是,如果您不想这样做,虽然我在BCL中没有发现类似的情况,但System
命名空间中仍有一个选项,如果您已经引用{{3} (如果你不这样做,你应该有):
using System.Reactive.Linq;
items.ToObservable().Subscribe(i => i.DoStuff());
虽然方法名称略有不同,但最终结果正是您所需要的。
答案 13 :(得分:3)
现在我们可以选择......
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 4;
#if DEBUG
parallelOptions.MaxDegreeOfParallelism = 1;
#endif
Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));
当然,这开辟了一整套新的线虫。
ps(对不起字体,这是系统决定的)
答案 14 :(得分:2)
受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);
}
答案 15 :(得分:2)
ForEach也可以链接,只需在行动后回到桩线即可。 保持流利
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();});
渴望版本的实施。
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
}
修改: 懒惰版本正在使用收益率返回,例如this。
public static IEnumerable<T> ForEachLazy<T>(this IEnumerable<T> enu, Action<T> action)
{
foreach (var item in enu)
{
action(item);
yield return item;
}
}
实现Lazy版本 NEEDs ,例如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()。
答案 16 :(得分:1)
这种“功能方法”抽象漏掉了大量时间。语言层面没有任何东西可以防止副作用。只要您可以为容器中的每个元素调用lambda / delegate,您将获得“ForEach”行为。
这里举例说明将srcDictionary合并到destDictionary的一种方法(如果key已存在 - 覆盖)
这是一个黑客攻击,不应该在任何生产代码中使用。
var b = srcDictionary.Select(
x=>
{
destDictionary[x.Key] = x.Value;
return true;
}
).Count();
答案 17 :(得分:1)
对于VB.NET,您应该使用:
listVariable.ForEach(Sub(i) i.Property = "Value")
答案 18 :(得分:1)
另一个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;
}
答案 19 :(得分:1)
MoreLinq具有ensure
和大量其他有用的扩展名。仅仅为else
来获取依赖关系可能不值得,但是里面有很多有用的东西。
答案 20 :(得分:1)
要保持流利,可以使用以下技巧:
http://api.loc/modifiers
答案 21 :(得分:1)
我非常不同意链接扩展方法应该是无副作用的概念(不仅因为它们不是,任何代表都可以执行副作用)。
请考虑以下事项:
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);
}
}
这个例子显示的实际上只是一种后期绑定,允许人们调用许多可能对一系列元素产生副作用的动作之一,而不必编写一个大的开关构造来解码定义该元素的值。行动并将其转化为相应的方法。