受到另一个关于缺少 Zip
函数的问题的启发:
为什么 IEnumerable
接口上没有 ForEach
扩展方法?还是在任何地方?获得 ForEach
方法的唯一类是 List<>
。是否有它丢失的原因,也许是性能?
Parallel.ForEach
换成 Enumerable.ForEach
,只是为了重新发现后者不存在。 C# 在这里错过了一个让事情变得简单的技巧。
大部分时间完成这项工作的语言中已经包含一个 foreach
语句。
我不想看到以下内容:
list.ForEach( item =>
{
item.DoSomething();
} );
代替:
foreach(Item item in list)
{
item.DoSomething();
}
后者在大多数情况下更清晰、更容易阅读,尽管打字时间可能会更长一些。
但是,我必须承认我在这个问题上改变了立场; ForEach()
扩展方法在某些情况下确实很有用。
以下是语句和方法之间的主要区别:
类型检查: foreach 在运行时完成, ForEach() 在编译时完成(大加!)
调用委托的语法确实简单得多:objects.ForEach(DoSomething);
ForEach() 可以被链接:尽管这种功能的邪恶/有用性有待讨论。
这些都是这里很多人提出的重要观点,我可以理解为什么人们缺少这个功能。我不介意微软在下一次框架迭代中添加标准的 ForEach 方法。
ForEach 方法是在 LINQ 之前添加的。如果您添加 ForEach 扩展,由于扩展方法的限制,它将永远不会为 List 实例调用。我认为没有添加它的原因是不干扰现有的。
但是,如果您真的错过了这个小功能,您可以推出自己的版本
public static void ForEach<T>(
this IEnumerable<T> source,
Action<T> action)
{
foreach (T element in source)
action(element);
}
您可以编写此扩展方法:
// Possibly call this "Do"
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action)
{
foreach (var e in source)
{
action(e);
yield return e;
}
}
优点
允许链接:
MySequence
.Apply(...)
.Apply(...)
.Apply(...);
缺点
它实际上不会做任何事情,直到你做一些事情来强制迭代。因此,不应将其称为 .ForEach()
。你可以在最后写 .ToList()
,或者你也可以写这个扩展方法:
// possibly call this "Realize"
IEnumerable<T> Done<T> (this IEnumerable<T> source)
{
foreach (var e in source)
{
// do nothing
;
}
return source;
}
这可能与交付的 C# 库相差太大;不熟悉您的扩展方法的读者将不知道如何处理您的代码。
{foreach (var e in source) {action(e);} return source;}
numbers.Select(n => n*2);
Select()
表示将函数应用于每个元素并返回函数的值,其中 Apply()
表示将 Action
应用于每个元素并返回 original 元素。
Select(e => { action(e); return e; })
The discussion here 给出了答案:
实际上,我目睹的具体讨论确实取决于功能纯度。在表达式中,经常会假设没有副作用。拥有 ForEach 会特别引起副作用,而不仅仅是忍受它们。 ——基思·法默(合伙人)
基本上决定保持扩展方法在功能上“纯”。 ForEach 在使用 Enumerable 扩展方法时会鼓励副作用,这不是本意。
虽然我同意在大多数情况下最好使用内置的 foreach
构造,但我发现在 ForEach<>扩展比必须自己在常规 foreach
中管理索引更好一点:
public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> action)
{
if (action == null) throw new ArgumentNullException("action");
var index = 0;
foreach (var elem in list)
action(index++, elem);
return index;
}
var people = new[] { "Moe", "Curly", "Larry" };
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));
会给你:
Person #0 is Moe
Person #1 is Curly
Person #2 is Larry
一种解决方法是编写 .ToList().ForEach(x => ...)
。
优点
易于理解 - 读者只需要知道 C# 附带的内容,而不需要任何其他扩展方法。
句法噪音非常温和(仅添加了一些无关代码)。
通常不会花费额外的内存,因为无论如何,本机 .ForEach()
都必须实现整个集合。
缺点
操作顺序并不理想。我宁愿意识到一个元素,然后采取行动,然后重复。该代码首先实现所有元素,然后依次作用于它们。
如果意识到列表引发异常,您将永远无法对单个元素采取行动。
如果枚举是无限的(如自然数),那么你就不走运了。
Enumerable.ForEach<T>
方法,但有 List<T>.ForEach
方法,这个响应会更清楚。 c)“通常不会花费额外的内存,因为本机 .ForEach() 无论如何都必须实现整个集合。” ——完全的胡说八道。这是 Enumerable.ForEach<T> 的实现。如果这样做,C# 会提供:public static void ForEach<T>(this IEnumerable<T> list, Action<T> action) { foreach (T item in list) action(item); }
我一直想知道自己,这就是为什么我总是随身携带这个:
public static void ForEach<T>(this IEnumerable<T> col, Action<T> action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
foreach (var item in col)
{
action(item);
}
}
不错的小扩展方法。
因此,有很多关于 ForEach 扩展方法不合适的评论,因为它不像 LINQ 扩展方法那样返回值。虽然这是一个事实陈述,但并不完全正确。
LINQ 扩展方法都返回一个值,因此它们可以链接在一起:
collection.Where(i => i.Name = "hello").Select(i => i.FullName);
但是,仅仅因为 LINQ 是使用扩展方法实现的,并不意味着扩展方法必须以相同的方式使用并返回值。编写扩展方法来公开不返回值的常见功能是一种完全有效的用途。
关于 ForEach 的具体论点是,基于对扩展方法的约束(即扩展方法从不覆盖具有相同签名的继承方法),可能存在自定义扩展方法在所有 impelement IEnumerable<T
> 的类上都可用的情况;除了列表<T
>。当方法开始表现不同时,这可能会导致混淆,具体取决于是否调用了扩展方法或继承方法。
您可以使用(可链接,但延迟评估)Select
,首先执行您的操作,然后返回身份(或其他如果您愿意)
IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });
您需要确保它仍然被评估,无论是使用 Count()
(枚举 afaik 的最便宜的操作)还是您需要的其他操作。
不过,我希望看到它被引入标准库:
static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
return src.Select(i => { action(i); return i; } );
}
然后上面的代码变成了 people.WithLazySideEffect(p => Console.WriteLine(p))
,它实际上等同于 foreach,但是惰性且可链接。
Select
不再做它应该做的事情了。这绝对是 OP 所要求的解决方案 - 但在主题可读性方面:没有人会期望 select 确实执行一个函数。
Select
没有做它应该做的事情,那是 Select
实现的问题,而不是调用 Select
的代码,它对 Select
的作用没有影响。
请注意,MoreLINQ NuGet 提供了您正在寻找的 ForEach
扩展方法(以及执行委托并产生其结果的 Pipe
方法)。看:
https://www.nuget.org/packages/morelinq
https://code.google.com/p/morelinq/wiki/OperatorsOverview
@币币
foreach 扩展方法的真正强大之处在于 Action<>
的可重用性,而无需向您的代码添加不必要的方法。假设您有 10 个列表,并且您想对它们执行相同的逻辑,并且相应的函数不适合您的类并且没有被重用。您可以将所有逻辑保存在一个地方(Action<>
),而不是有十个 for 循环,或者一个显然不属于的通用函数。因此,几十行被替换为
Action<blah,blah> f = { foo };
List1.ForEach(p => f(p))
List2.ForEach(p => f(p))
ETC...
逻辑在一个地方,你没有污染你的班级。
f(p)
一样简单,则省略 { }
的简写:for (var p in List1.Concat(List2).Concat(List2)) f(p);
。
大多数 LINQ 扩展方法都返回结果。 ForEach 不适合此模式,因为它不返回任何内容。
public IEnumerable<T> Foreach<T>(this IEnumerable<T> items, Action<T> action) { /* error check */ foreach (var item in items) { action(item); yield return item; } }
如果你有 F#(将在 .NET 的下一个版本中),你可以使用
Seq.iter doSomething myIEnumerable
部分原因是语言设计者从哲学的角度不同意它。
没有(和测试......)一个功能比拥有一个功能要少。
它并不是真的更短(有一些传递函数的情况,但这不是主要用途)。
它的目的是产生副作用,这不是 linq 的意义所在。
为什么要用另一种方法来做与我们已经拥有的功能相同的事情? (foreach 关键字)
https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/
当你想返回某些东西时,你可以使用 select 。如果不这样做,您可以先使用 ToList,因为您可能不想修改集合中的任何内容。
我写了一篇关于它的博文:http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx
如果您想在 .NET 4.0 中看到此方法,可以在这里投票:http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093
在 3.5 中,添加到 IEnumerable 的所有扩展方法都用于 LINQ 支持(注意它们是在 System.Linq.Enumerable 类中定义的)。在这篇文章中,我解释了为什么 foreach 不属于 LINQ:Existing LINQ extension method similar to Parallel.For?
是我还是 List
foreach(X x in Y)
其中 Y 必须是 IEnumerable (Pre 2.0),并实现 GetEnumerator()。如果您查看生成的 MSIL,您会发现它与
IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
int i = enumerator.Current;
Console.WriteLine(i);
}
(有关 MSIL,请参阅 http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx)
然后在 DotNet2.0 中出现了泛型和列表。在我看来,Foreach 一直是 Vistor 模式的实现,(请参阅 Gamma、Helm、Johnson、Vlissides 的设计模式)。
现在当然在 3.5 中我们可以使用 Lambda 来实现相同的效果,例如尝试 http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh-my/
我想扩展 Aku's answer。
如果你想调用一个方法只是为了它的副作用而不首先迭代整个可枚举,你可以使用这个:
private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
foreach (var x in xs) {
f(x); yield return x;
}
}
Select(x => { f(x); return x;})
相同
我的版本是一种扩展方法,允许您在 T 的 IEnumerable 上使用 ForEach
public static class EnumerableExtension
{
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
source.All(x =>
{
action.Invoke(x);
return true;
});
}
}
还没有人指出 ForEach
在代码中使用了这两种方法的地方进行了一些重构之后,我更喜欢 .ForEach,因为我必须寻找测试失败/运行时失败才能找到 foreach 问题。
foreach
如何减少编译时间检查。当然,如果您的集合是非泛型 IEnumerable,则循环变量将是 object
,但对于假设的 ForEach
扩展方法也是如此。
foreach
如果可枚举参数化,则在编译时进行类型检查。它只缺少对非泛型集合的编译时类型检查,假设的ForEach
扩展方法也是如此。col.Where(...).OrderBy(...).ForEach(...)
。更简单的语法,没有多余的局部变量或 foreach() 中的长括号造成的屏幕污染。list.ForEach(item => item.DoSomething())
没有那么可恨。谁说这是一个列表?明显的用例位于链的末端;使用 foreach 语句,您必须将整个链放在in
之后,并且在块内执行操作,这远不自然或一致。