对于IEnumerable <T>的foreach相当于LINQ
我想在LINQ中做以下的等价物,但我不知道如何:
IEnumerable<Item> items = GetItems(); items.ForEach(i => i.DoStuff());
什么是真正的语法?
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); } }
Fredrik已经提供了这个修复,但是可能值得考虑一下为什么这个框架不是从头开始的。 我相信这个想法是,LINQ查询操作符应该是无副作用的,适合于查看世界的合理function的方式。 很明显ForEach正好相反 – 一个纯粹的副作用为基础的构造。
这并不是说这是一件坏事,只是考虑决定背后的哲学原因。
2012年7月17日更新 :显然,从C#5.0开始,下面描述的foreach
的行为已经发生了变化,“ 在嵌套的lambdaexpression式中使用foreach
迭代variables不再会产生意外的结果。 ”此答案不适用于C# ≥5.0。
@John Skeet和喜欢foreach关键字的人。
在5.0之前的 C#中的“foreach”的问题是,它与其他语言中等效的“理解”是如何工作的以及我期望它如何工作是不一致的(个人意见仅仅是因为其他人提到过关于可读性的意见)。 查看所有关于“ 访问修改的closures ”以及“ closures被认为有害的循环variables ”的问题。 这只是“有害的”,因为“foreach”在C#中实现的方式。
下面的例子使用function相当的扩展方法在@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,因为它并不是完全可以做这样的事情。 显然有更好的方法来创build这个可观察的,我只是试图展示一个观点。 通常,订阅observable的代码是asynchronous执行的,可能在另一个线程中执行。 如果使用“foreach”,这可能会产生非常奇怪的和潜在的非确定性结果。
以下使用“ForEach”扩展方法的testing通过:
[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; });
我采取了弗雷德里克的方法,并修改了返回types。
这样,该方法像其他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; } }
这里是testing,以帮助看到它:
[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() ,您将看到testing失败,因为StringBuilder包含一个空string。 这是因为没有方法强迫ForEach枚举。
让我的IEnumerable的副作用
我想在LINQ中做以下的等价物,但我不知道如何:
正如其他人已经指出的那样,LINQ和IEnumerable
方法预计是无副作用的。
你真的想为IEnumerable中的每个项目“做些什么”吗? 那么foreach
是最好的select。 当副作用发生在这里时,人们并不感到惊讶。
foreach (var i in items) i.DoStuff();
我敢打赌,你不想要副作用
但在我的经验中,副作用通常不是必需的。 更多的时候,Jon Skeet,Eric Lippert或者Marc Gravell都会发现一个简单的LINQ查询,并伴随着一个StackOverflow.com的解答,解释如何做你想做的事情!
一些例子
如果你实际上只是聚合(积累)一些价值,那么你应该考虑Aggregate
扩展方法。
items.Aggregate(initial, (acc, x) => ComputeAccumulatedValue(acc, x));
也许你想从现有的值创build一个新的IEnumerable
。
items.Select(x => Transform(x));
或者,也许你想创build一个查找表:
items.ToLookup(x, x => GetTheKey(x))
这个名单(双关不完全是打算)的可能性继续下去。
如果您可以使用IQueryable <T>而不是IEnumerable <T>,那么Select方法应该做你想做的。
IQueryable<Item> items = GetItems(); IQueryable<Item> modifiedItems = items.Select(i => i.DoStuff());
虽然正如Martin Harris所指出的那样,但是在枚举集合之前,Select()将不会被实际评估,所以如果您依靠DoStuff()来执行一些副作用,那么最好使用类似于
var modifiedItems = items.Select(i => i.DoStuff()).ToList()
LINQ的select方法与SQL SELECT关键字没有任何共同之处; 它所做的是对一个集合中的每个元素应用一个函数,并返回一个包含这些函数结果的(懒惰评估!)集合。
有一个由微软发布的LINQ交互式扩展的实验版本( 在NuGet上 ,参见RxTeams的更多链接)。 Channel 9的video很好的解释了这一点。
其文档仅以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()”或其他东西。 这显然不是什么expression。
您应该使用IEnumerable扩展来设置迭代链,通过各自的来源和条件来定义内容。 expression树是强大和高效的,但你应该学会欣赏自己的本性。 而不仅仅是为了节省他们的繁重的懒惰评估几个字符。
如果你想作为枚举卷,你应该产生每个项目。
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; } } }
由于众多的答案已经指出,你可以很容易地自己添加这样的扩展方法。 然而,如果你不想这样做,尽pipe我没有在BCL中知道这样的事情,但是如果你已经有了Reactive Extension的引用,那么在System
命名空间中还是有一个选项的(如果你不想这样做,吨,你应该有):
using System.Reactive.Linq; items.ToObservable().Subscribe(i => i.DoStuff());
虽然方法名称有点不同,但最终的结果正是你要找的。
很多人提到它,但我不得不把它写下来。 这不是最清楚/最可读吗?
IEnumerable<Item> items = GetItems(); foreach (var item in items) item.DoStuff();
简单(st)。
现在我们有select…
ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 4; #if DEBUG parallelOptions.MaxDegreeOfParallelism = 1; #endif Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));
当然,这会打开一个全新的线虫的jar头。
ps(对不起,关于字体,这是系统决定)
受Jon Skeet的启发,我已经将他的解决scheme扩展到以下内容:
扩展方法:
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); }
我相当不同意链接扩展方法应该是无副作用的观念(不仅因为它们不是,任何代表都可以产生副作用)。
考虑以下几点:
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); } }
这个例子显示的实际上只是一种后期绑定,它允许一个调用对元素序列有副作用的许多可能的操作之一,而不必编写一个大的开关构造来对定义操作的值进行解码,并且翻译把它变成相应的方法。
如果你这样做,例如因为你在迭代中需要索引,你总是可以使用Where构造:
linqObject.Where((obj, index) => { DoWork(obj, index); return true; }).ToArray(); //MUST CALL ToArray() or ToList() or something to execute the lazy query, or the loop won't actually execute
这还有一个额外的好处,原始数组返回“不变”(列表引用的对象是相同的,虽然它们可能不具有相同的数据),这在LINQ等function/链式编程方法中经常是需要的。
这种“function性方法”抽象性大泄漏。 语言层面没有任何东西可以防止副作用。 只要你可以调用你的lambda / delegate容器中的每个元素 – 你将得到“ForEach”的行为。
这里举一个合并srcDictionary到destDictionary的方法(如果键已经存在 – 覆盖)
这是一个破解,不应该在任何生产代码中使用。
var b = srcDictionary.Select( x=> { destDictionary[x.Key] = x.Value; return true; } ).Count();
对于VB.NET,你应该使用:
listVariable.ForEach(Sub(i) i.Property = "Value")
ForEach也可以被链接 ,只要在行动之后放回桩线。 保持stream畅
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 }
上面的代码Edit2正在工作,但更好的版本是使用这个 。
Taemyr指出,下面的编辑是一个错误的例子。 非常感谢
Employees.ForEach(e=>e.Salary = e.Salary * 2) .Where (e=> e.Salary > 10000) .Average(e=> e.Salary);
我想知道为什么没有人通过使用委托function呢? 在我的编码中,我发现它代表了一个你想要在迭代列表中执行的子程序的更加简洁明了的方式。
注意:根据经验,如果您的原始列表发生更改,我总是build议使用ToList()以避免索引编制方面的问题,请参阅下面的示例:
public class MyObject { public string MyString; public int MyInt; } List<MyObject> list = new List<MyObject> { new MyObject { MyString = "Test1", MyInt = 1970 }, new MyObject { MyString = "Test2", MyInt = 2010 }, new MyObject { MyString = "Test3", MyInt = 2011 }, new MyObject { MyString = "Test4", MyInt = 1767 } }; // simple filter, but notice the Remove() will work with ToList(), remove ToList() // and the statement will fail on execution list.ToList().ForEach( delegate(MyObject item) { if (item.MyInt > 1999) list.Remove(item); } );
又一个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; }